diff --git a/.dockerignore b/.dockerignore index a11022eb2b..09428cfe68 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,4 +3,5 @@ .gitignore **/node_modules **/target +target-otel dist diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index 1e90f2ee45..14f4dd8a7a 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -6,6 +6,8 @@ on: jobs: build: runs-on: arc-ubuntu-22.04 + env: + NEXT_PUBLIC_SITE_URL: https://nymtech.net/docs defaults: run: working-directory: documentation/docs @@ -41,6 +43,8 @@ jobs: run: pnpm i - name: Build project run: pnpm run build + - name: Generate sitemap + run: npx next-sitemap - name: Move files to /dist/ run: ../scripts/move-to-dist.sh diff --git a/.github/workflows/ci-build-upload-binaries.yml b/.github/workflows/ci-build-upload-binaries.yml index 24b680c908..d66189281b 100644 --- a/.github/workflows/ci-build-upload-binaries.yml +++ b/.github/workflows/ci-build-upload-binaries.yml @@ -3,13 +3,28 @@ name: ci-build-upload-binaries on: workflow_dispatch: inputs: + feature_profile: + description: "Select a predefined cargo feature profile" + required: false + default: "none" + type: choice + options: + - none + - tokio-console + - otel + - otel,tokio-console + extra_features: + description: "Additional comma-separated cargo features (e.g. feat1,feat2)" + required: false + default: "" + type: string add_tokio_unstable: - description: 'True to add RUSTFLAGS="--cfg tokio_unstable"' - required: true + description: 'Force RUSTFLAGS="--cfg tokio_unstable" (auto-set when tokio-console is selected)' + required: false default: false type: boolean enable_deb: - description: "True to enable cargo-deb installation and .deb package building" + description: "Enable cargo-deb installation and .deb package building" required: false default: false type: boolean @@ -21,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - platform: [ arc-linux-latest ] + platform: [arc-linux-latest] runs-on: ${{ matrix.platform }} env: @@ -36,38 +51,62 @@ jobs: OUTPUT_DIR: ci-builds/${{ github.ref_name }} run: | rm -rf ci-builds || true - mkdir -p $OUTPUT_DIR - echo $OUTPUT_DIR + mkdir -p "$OUTPUT_DIR" + echo "$OUTPUT_DIR" + - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get -y install libudev-dev - - name: Sets env vars for tokio if set in manual dispatch inputs - if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true + - name: Resolve cargo features and RUSTFLAGS + if: github.event_name == 'workflow_dispatch' + shell: bash run: | - echo "RUSTFLAGS=--cfg tokio_unstable" >> $GITHUB_ENV - echo "CARGO_FEATURES=--features tokio-console" >> $GITHUB_ENV + FEATURES="" + PROFILE="${{ inputs.feature_profile }}" + EXTRA="${{ inputs.extra_features }}" + + if [[ "$PROFILE" != "none" && -n "$PROFILE" ]]; then + FEATURES="$PROFILE" + fi + + if [[ -n "$EXTRA" ]]; then + if [[ -n "$FEATURES" ]]; then + FEATURES="${FEATURES},${EXTRA}" + else + FEATURES="$EXTRA" + fi + fi + + if [[ -n "$FEATURES" ]]; then + echo "CARGO_FEATURES=--features ${FEATURES}" >> "$GITHUB_ENV" + echo "::notice::Selected cargo features: $FEATURES" + else + echo "::notice::No additional cargo features selected" + fi + + if [[ "$FEATURES" == *"tokio-console"* ]] || [[ "${{ inputs.add_tokio_unstable }}" == "true" ]]; then + echo "RUSTFLAGS=--cfg tokio_unstable" >> "$GITHUB_ENV" + echo "::notice::Enabled RUSTFLAGS --cfg tokio_unstable" + fi + - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }} - name: Build all binaries - uses: actions-rs/cargo@v1 - with: - command: build - args: --workspace --release ${{ env.CARGO_FEATURES }} + shell: bash + run: cargo build --workspace --release ${{ env.CARGO_FEATURES }} - name: Install cargo-deb - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-deb if: github.event_name == 'workflow_dispatch' && inputs.enable_deb == true + shell: bash + run: cargo install cargo-deb - name: Build deb packages + if: github.event_name == 'workflow_dispatch' && inputs.enable_deb == true shell: bash run: make deb - if: github.event_name == 'workflow_dispatch' && inputs.enable_deb == true - name: Upload Artifact if: github.event_name == 'workflow_dispatch' @@ -84,24 +123,22 @@ jobs: target/release/nym-node retention-days: 30 - # If this was a pull_request or nightly, upload to build server - - name: Prepare build output - # if: github.event_name == 'schedule' || github.event_name == 'pull_request' shell: bash env: OUTPUT_DIR: ci-builds/${{ github.ref_name }} run: | - cp target/release/nym-client $OUTPUT_DIR - cp target/release/nym-socks5-client $OUTPUT_DIR - cp target/release/nym-api $OUTPUT_DIR - cp target/release/nym-network-requester $OUTPUT_DIR - cp target/release/nymvisor $OUTPUT_DIR - cp target/release/nym-node $OUTPUT_DIR - cp target/release/nym-cli $OUTPUT_DIR - if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then - cp target/debian/*.deb $OUTPUT_DIR + cp target/release/nym-client "$OUTPUT_DIR" + cp target/release/nym-socks5-client "$OUTPUT_DIR" + cp target/release/nym-api "$OUTPUT_DIR" + cp target/release/nym-network-requester "$OUTPUT_DIR" + cp target/release/nymvisor "$OUTPUT_DIR" + cp target/release/nym-node "$OUTPUT_DIR" + cp target/release/nym-cli "$OUTPUT_DIR" + if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.enable_deb }}" == "true" ]]; then + cp target/debian/*.deb "$OUTPUT_DIR" fi + - name: Deploy branch to CI www continue-on-error: true uses: easingthemes/ssh-deploy@main diff --git a/.github/workflows/ci-build-vpn-api-wasm.yml b/.github/workflows/ci-build-vpn-api-wasm.yml deleted file mode 100644 index 8de8b02c80..0000000000 --- a/.github/workflows/ci-build-vpn-api-wasm.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: ci-build-vpn-api-wasm - -on: - pull_request: - paths: - - 'common/**' - - 'nym-credential-proxy/**' - - '.github/workflows/ci-build-vpn-api-wasm.yml' - -jobs: - wasm: - runs-on: arc-linux-latest - env: - CARGO_TERM_COLOR: always - RUSTUP_PERMIT_COPY_RENAME: 1 - steps: - - name: Check out repository code - uses: actions/checkout@v6 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }} - target: wasm32-unknown-unknown - override: true - components: rustfmt, clippy - - - name: Install wasm-pack - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - - - name: Install wasm-opt - uses: ./.github/actions/install-wasm-opt - with: - version: '116' - - - name: Install wasm-bindgen-cli - run: cargo install wasm-bindgen-cli - - - name: "Build" - run: make - working-directory: nym-credential-proxy/vpn-api-lib-wasm diff --git a/.github/workflows/ci-check-ns-api-version.yml b/.github/workflows/ci-check-ns-api-version.yml index 19a48d10ff..a42958c590 100644 --- a/.github/workflows/ci-check-ns-api-version.yml +++ b/.github/workflows/ci-check-ns-api-version.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v6 - name: Get version from cargo.toml - uses: mikefarah/yq@v4.52.2 + uses: mikefarah/yq@v4.52.4 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/ci-check-nym-stats-api-version.yml b/.github/workflows/ci-check-nym-stats-api-version.yml index 0f0eb6f8a9..dd4cd3d64e 100644 --- a/.github/workflows/ci-check-nym-stats-api-version.yml +++ b/.github/workflows/ci-check-nym-stats-api-version.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v6 - name: Get version from cargo.toml - uses: mikefarah/yq@v4.52.2 + uses: mikefarah/yq@v4.52.4 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/ci-nym-wallet-storybook.yml b/.github/workflows/ci-nym-wallet-storybook.yml index 212c748386..e669de8fff 100644 --- a/.github/workflows/ci-nym-wallet-storybook.yml +++ b/.github/workflows/ci-nym-wallet-storybook.yml @@ -51,25 +51,3 @@ jobs: REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }} EXCLUDE: "/dist/, /node_modules/" - - - name: Matrix - Node Install - run: npm install - working-directory: .github/workflows/support-files - - - name: Matrix - Send Notification - env: - NYM_NOTIFICATION_KIND: nym-wallet - NYM_PROJECT_NAME: "nym-wallet" - NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}" - NYM_CI_WWW_LOCATION: "wallet-${{ env.GITHUB_REF_SLUG }}" - GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" - GIT_BRANCH: "${GITHUB_REF##*/}" - IS_SUCCESS: "${{ job.status == 'success' }}" - MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" - MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}" - MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" - MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" - MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" - uses: docker://keybaseio/client:stable-node - with: - args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 918603b449..e2d23a4d5f 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -10,8 +10,8 @@ jobs: strategy: fail-fast: false matrix: - rust: [stable, beta] - os: [ubuntu-22.04, windows-latest, macos-latest] + rust: [ stable, beta ] + os: [ ubuntu-22.04, windows-latest, macos-latest ] runs-on: ${{ matrix.os }} env: CARGO_TERM_COLOR: always @@ -93,38 +93,3 @@ jobs: with: command: clippy args: --workspace --all-targets -- -D warnings - - notification: - needs: build - runs-on: custom-linux - steps: - - name: Collect jobs status - uses: technote-space/workflow-conclusion-action@v3 - - name: Check out repository code - uses: actions/checkout@v6 - - name: install npm - uses: actions/setup-node@v4 - if: env.WORKFLOW_CONCLUSION == 'failure' - with: - node-version: 20 - - name: Matrix - Node Install - if: env.WORKFLOW_CONCLUSION == 'failure' - run: npm install - working-directory: .github/workflows/support-files - - name: Matrix - Send Notification - if: env.WORKFLOW_CONCLUSION == 'failure' - env: - NYM_NOTIFICATION_KIND: nightly - NYM_PROJECT_NAME: "Nym nightly build" - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" - GIT_BRANCH: "${GITHUB_REF##*/}" - IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}" - MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" - MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}" - MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" - MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" - MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" - uses: docker://keybaseio/client:stable-node - with: - args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/nightly-nym-wallet-build.yml b/.github/workflows/nightly-nym-wallet-build.yml index c81b6bc712..b4d4f69a1f 100644 --- a/.github/workflows/nightly-nym-wallet-build.yml +++ b/.github/workflows/nightly-nym-wallet-build.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-22.04, macos-latest, windows-latest] + os: [ ubuntu-22.04, macos-latest, windows-latest ] runs-on: ${{ matrix.os }} env: CARGO_TERM_COLOR: always @@ -55,38 +55,3 @@ jobs: with: command: clippy args: ${{ env.MANIFEST_PATH }} --workspace --all-targets -- -D warnings - - notification: - needs: build - runs-on: custom-linux - steps: - - name: Collect jobs status - uses: technote-space/workflow-conclusion-action@v3 - - name: Check out repository code - uses: actions/checkout@v6 - - name: install npm - uses: actions/setup-node@v4 - if: env.WORKFLOW_CONCLUSION == 'failure' - with: - node-version: 20 - - name: Matrix - Node Install - if: env.WORKFLOW_CONCLUSION == 'failure' - run: npm install - working-directory: .github/workflows/support-files - - name: Matrix - Send Notification - if: env.WORKFLOW_CONCLUSION == 'failure' - env: - NYM_NOTIFICATION_KIND: nightly - NYM_PROJECT_NAME: "nym-wallet-nightly-build" - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" - GIT_BRANCH: "${GITHUB_REF##*/}" - IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}" - MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" - MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}" - MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" - MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" - MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" - uses: docker://keybaseio/client:stable-node - with: - args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/nightly-security-audit.yml b/.github/workflows/nightly-security-audit.yml index 52decfd3d0..8d03483a9c 100644 --- a/.github/workflows/nightly-security-audit.yml +++ b/.github/workflows/nightly-security-audit.yml @@ -24,34 +24,3 @@ jobs: with: name: report path: .github/workflows/support-files/notifications/deny.message - notification: - needs: cargo-deny - runs-on: custom-linux - steps: - - name: Check out repository code - uses: actions/checkout@v6 - - name: Download report from previous job - uses: actions/download-artifact@v7 - with: - name: report - path: .github/workflows/support-files/notifications - - name: install npm - uses: actions/setup-node@v4 - with: - node-version: 20 - - name: Matrix - Node Install - run: npm install - working-directory: .github/workflows/support-files - - name: Matrix - Send Notification - env: - NYM_NOTIFICATION_KIND: security - NYM_PROJECT_NAME: "Daily security report" - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" - MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_AUDIT }}" - MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" - MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" - MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" - uses: docker://keybaseio/client:stable-node - with: - args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/push-credential-proxy.yaml b/.github/workflows/push-credential-proxy.yaml index 89198c6c3a..a45e8be309 100644 --- a/.github/workflows/push-credential-proxy.yaml +++ b/.github/workflows/push-credential-proxy.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.52.2 + uses: mikefarah/yq@v4.52.4 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml diff --git a/.github/workflows/push-data-observatory.yaml b/.github/workflows/push-data-observatory.yaml index 34b9c7fcb3..4af505b9a5 100644 --- a/.github/workflows/push-data-observatory.yaml +++ b/.github/workflows/push-data-observatory.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.52.2 + uses: mikefarah/yq@v4.52.4 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/push-network-monitor.yaml b/.github/workflows/push-network-monitor.yaml index 9295e4769b..a277d6431e 100644 --- a/.github/workflows/push-network-monitor.yaml +++ b/.github/workflows/push-network-monitor.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.52.2 + uses: mikefarah/yq@v4.52.4 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml diff --git a/.github/workflows/push-nym-api.yaml b/.github/workflows/push-nym-api.yaml index 9bc7074df0..86477f6da0 100644 --- a/.github/workflows/push-nym-api.yaml +++ b/.github/workflows/push-nym-api.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.52.2 + uses: mikefarah/yq@v4.52.4 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-api/Cargo.toml diff --git a/.github/workflows/push-nym-node.yaml b/.github/workflows/push-nym-node.yaml index 94ec7eac4b..9081795a5a 100644 --- a/.github/workflows/push-nym-node.yaml +++ b/.github/workflows/push-nym-node.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.52.2 + uses: mikefarah/yq@v4.52.4 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/push-nym-statistics-api.yaml b/.github/workflows/push-nym-statistics-api.yaml index 68968d1f02..505fc87cd0 100644 --- a/.github/workflows/push-nym-statistics-api.yaml +++ b/.github/workflows/push-nym-statistics-api.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.52.2 + uses: mikefarah/yq@v4.52.4 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/push-nyx-chain-watcher.yaml b/.github/workflows/push-nyx-chain-watcher.yaml index 2a77459392..7db8161813 100644 --- a/.github/workflows/push-nyx-chain-watcher.yaml +++ b/.github/workflows/push-nyx-chain-watcher.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.52.2 + uses: mikefarah/yq@v4.52.4 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/push-validator-rewarder.yaml b/.github/workflows/push-validator-rewarder.yaml index b7b2ac830b..c8ba6415a8 100644 --- a/.github/workflows/push-validator-rewarder.yaml +++ b/.github/workflows/push-validator-rewarder.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.52.2 + uses: mikefarah/yq@v4.52.4 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/support-files/README.md b/.github/workflows/support-files/README.md index 9cac90aecc..4fddc794f6 100644 --- a/.github/workflows/support-files/README.md +++ b/.github/workflows/support-files/README.md @@ -4,51 +4,23 @@ This is a collection of scripts and files to support GitHub Actions. ## Sending Notifications -These scripts send CI notifications to Matrix by creating messages from templates and env vars passed from GitHub Actions. - -### Adding notifications to a GitHub Action - -``` -jobs: - build: - ... - - name: Notifications - Node Install - run: npm install - working-directory: .github/workflows/support-files/notifications - - name: Notifications - Send - env: - NYM_NOTIFICATION_KIND: "my-component" - GIT_BRANCH: "${GITHUB_REF##*/}" - MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" - MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}" - MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" - MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" - MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" - IS_SUCCESS: "${{ job.status == 'success' }}" - uses: docker://keybaseio/client:stable-node - with: - args: .github/workflows/support-files/notifications/entry_point.sh -``` - -Notifications are run by adding the snippet above to a GitHub Action, and: - -1. Installing node packages needed at run time -2. Set the env vars as required: - - `NYM_NOTIFICATION_KIND` matches the directory in `.github/workflows/support-files/${NYM_NOTIFICATION_KIND}` to provide the templates and extra scripting in `index.js` - - Matrix credentials, room and other env vars for the status of the build and repo -3. Replacing the default entry point shell script on the `keybaseio/client:stable-node` docker image to run `.github/workflows/support-files/notifications/entry_point.sh` +These scripts send CI notifications to Matrix by creating messages from templates and env vars passed from GitHub +Actions. ### Running locally You will need: + - Node 16 LTS - npm -Copy `.github/workflows/support-files/.env.example` to `.github/workflows/support-files/.env` and valid Matrix credentials. +Copy `.github/workflows/support-files/.env.example` to `.github/workflows/support-files/.env` and valid Matrix +credentials. Then run `npm install` to get dependencies. -Start development mode for the notification type you want either by passing the value as an env var called `NYM_NOTIFICATION_KIND` or set the `.env` file values correctly. +Start development mode for the notification type you want either by passing the value as an env var called +`NYM_NOTIFICATION_KIND` or set the `.env` file values correctly. ```bash cd .github/workflows/support-files diff --git a/.github/workflows/support-files/notifications/entry_point.sh b/.github/workflows/support-files/notifications/entry_point.sh deleted file mode 100755 index 04607fb979..0000000000 --- a/.github/workflows/support-files/notifications/entry_point.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -# pass exit codes out to GitHub Actions -set -euxo pipefail - -# change to the directory that contains this script -cd "${0%/*}" - -# run the node script -node send_message.js \ No newline at end of file diff --git a/.github/workflows/support-files/notifications/send_message.js b/.github/workflows/support-files/notifications/send_message.js deleted file mode 100644 index 12eec724e1..0000000000 --- a/.github/workflows/support-files/notifications/send_message.js +++ /dev/null @@ -1,126 +0,0 @@ -require('dotenv').config(); - -const { sendMatrixMessage } = require('./send_message_to_matrix'); - -let context = { - kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect','security','ci-docs','cd-docs','ci-dev','cd-dev'], -}; - -/** - * Validate that all required env and context vars are available - */ -function validateContext() { - if (!context.env.NYM_NOTIFICATION_KIND) { - throw new Error( - 'Please set env var NYM_NOTIFICATION_KIND with the project kind that matches a directory in ".github/workflows/support-files"', - ); - } - if (!context.kinds.includes(context.env.NYM_NOTIFICATION_KIND)) { - throw new Error(`Env var NYM_NOTIFICATION_KIND is not in ${context.kinds}`); - } - if (!context.env.NYM_PROJECT_NAME) { - throw new Error( - 'Please set env var NYM_PROJECT_NAME with the project name for displaying in notification messages', - ); - } - if (context.env.MATRIX_ROOM) { - if (!context.env.MATRIX_SERVER) { - throw new Error( - 'Matrix server is not defined. Please set env var MATRIX_SERVER', - ); - } - if (!context.env.MATRIX_USER_ID) { - throw new Error( - 'Matrix user id is not defined. Please set env var MATRIX_USER_ID', - ); - } - if (!context.env.MATRIX_TOKEN) { - throw new Error( - 'Matrix token is not defined. Please set env var MATRIX_TOKEN', - ); - } - if (!context.env.MATRIX_DEVICE_ID) { - throw new Error( - 'Matrix device id is not defined. Please set env var MATRIX_DEVICE_ID', - ); - } - } -} - -/** - * Creates a context that will be available in the templates for rendering notifications - */ -function createTemplateContext() { - const options = { dateStyle: 'full', timeStyle: 'long' }; - context.timestamp = new Date().toLocaleString(undefined, options); - - // add environment to template context and validate - context.env = process.env; - try { - validateContext(); - } catch (e) { - if(process.env.SHOW_DEBUG) { - // recursively print the context for easy debugging and rethrow the error - console.dir({ context }, { depth: null }); - } - throw e; - } - - context.kind = context.env.NYM_NOTIFICATION_KIND; - - if (!context.env.GIT_BRANCH_NAME) { - context.env.GIT_BRANCH_NAME = context.env.GITHUB_REF.split('/') - .slice(2) - .join('/'); - } - - context.status = process.env.IS_SUCCESS === 'true' ? 'success' : 'failure'; -} - -/** - * Uses the `kind` set in the context to process the context and generate a notification message - * @returns {Promise} A string notification message body - */ -async function processKindScript() { - const script = require(`../${context.kind}`); - if (!script.addToContextAndValidate) { - throw new Error( - `"./${context.kind}/index.js" does not export a method called "async addToContextAndValidate(context)"`, - ); - } - if (!script.getMessageBody) { - throw new Error( - `"./${context.kind}/index.js" does not export a method called "async getMessageBody(context)"`, - ); - } - - // call the script to modify and validate the context - await script.addToContextAndValidate(context); - - // let the script create a message body and return the result as a string for sending - return await script.getMessageBody(context); -} - -/** - * The main function, as async so that await syntax is available - */ -async function main() { - createTemplateContext(); - console.log(`Sending notification for kind "${context.kind}"...`); - const messageBody = await processKindScript(); - if(process.env.SHOW_DEBUG) { - console.log('-----------------------------------------'); - console.log(messageBody); - console.log('-----------------------------------------'); - } - if(context.env.MATRIX_ROOM) { - await sendMatrixMessage(context, messageBody, context.env.MATRIX_ROOM) - } - if(context.env.MATRIX_ROOM_OF_SHAME && context.env.IS_SUCCESS !== 'true') { - // when a job fails - await sendMatrixMessage(context, messageBody, context.env.MATRIX_ROOM_OF_SHAME) - } -} - -// call main function and let NodeJS handle the promise -main(); diff --git a/.github/workflows/support-files/notifications/send_message_to_matrix.js b/.github/workflows/support-files/notifications/send_message_to_matrix.js deleted file mode 100644 index 002af92350..0000000000 --- a/.github/workflows/support-files/notifications/send_message_to_matrix.js +++ /dev/null @@ -1,67 +0,0 @@ -const sdk = require('matrix-js-sdk'); -global.Olm = require('olm'); -const { LocalStorage } = require('node-localstorage'); -const localStorage = new LocalStorage('./scratch'); -const { - LocalStorageCryptoStore, -} = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store'); -var showdown = require('showdown'); - -// hide all matrix client output -console.error = (error) => console.log('❌ error: ', error); -process.stderr.write = () => {}; -process.stdout.write = () => {}; - - -function createClient(context, room, message) { - const server = context.env.MATRIX_SERVER; - const token = context.env.MATRIX_TOKEN; - const deviceId = context.env.MATRIX_DEVICE_ID; - const userId = context.env.MATRIX_USER_ID; - - const client = sdk.createClient({ - baseUrl: server, - accessToken: token, - userId, - deviceId, - sessionStore: new sdk.WebStorageSessionStore(localStorage), - cryptoStore: new LocalStorageCryptoStore(localStorage), - }); - - client.on('sync', async function(state, prevState, res) { - if (state !== 'PREPARED') return; - client.setGlobalErrorOnUnknownDevices(false); - try { - await client.joinRoom(room); - await client.sendEvent( - room, - 'm.room.message', - { - msgtype: 'm.text', - format: 'org.matrix.custom.html', - body: message, - formatted_body: message, - }, - '', - ); - } catch (error) { - console.error('Job failed: ' + error.message); - } - client.stopClient(); - process.exit(0); - }); - - return client; -} - -async function sendMatrixMessage(contextArg, messageAsMarkdown, roomId) { - const converter = new showdown.Converter(); - const messageAsHtml = converter.makeHtml(messageAsMarkdown); - const client = createClient(contextArg, roomId, messageAsHtml); - await client.initCrypto(); - await client.startClient({ initialSyncLimit: 1 }); -} - -module.exports = { - sendMatrixMessage, -}; diff --git a/.gitignore b/.gitignore index 988c9e1691..9c639d33b2 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ CLAUDE.md .claude/settings.json /notes +/target-otel diff --git a/CHANGELOG.md b/CHANGELOG.md index 11ed1bb017..e21f10f884 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,52 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2026.5-raclette] (2026-03-10) + +- bugfix: correctly populate gateway probe LP data ([#6533]) +- chore: introduce additional prometheus metrics for registration times ([#6532]) +- bugfix: lp information to have proper snake_case on API endpoints ([#6531]) +- removed redundant LP states ([#6509]) +- chore: removed all matrix notifications from github actions ([#6495]) +- feat: Lewes Protocol with PSQv2 ([#6491]) +- build(deps): bump minimatch from 3.1.2 to 3.1.4 in /documentation/docs ([#6486]) +- build(deps): bump bn.js from 4.12.2 to 4.12.3 in /documentation/docs ([#6484]) +- build(deps): bump bn.js from 4.12.2 to 4.12.3 ([#6483]) +- build(deps): bump ajv from 8.17.1 to 8.18.0 in /clients/native/examples/js-examples/websocket ([#6478]) +- build(deps): bump ajv from 6.12.6 to 6.14.0 in /documentation/docs ([#6477]) +- build(deps): bump minimatch and glob in /documentation/scripts/post-process ([#6476]) +- build(deps): bump hono from 4.11.9 to 4.12.0 ([#6475]) +- build(deps): bump keccak from 0.1.5 to 0.1.6 ([#6472]) +- build(deps-dev): bump qs from 6.14.1 to 6.14.2 in /clients/native/examples/js-examples/websocket ([#6466]) +- build(deps): bump mikefarah/yq from 4.52.2 to 4.52.4 ([#6465]) +- Otel minimal v2 ([#6464]) +- build(deps): bump qs and express in /wasm/client/internal-dev ([#6461]) +- bugfix: restore 'latest_measurement' field for nym-node /verloc endpoint ([#6452]) +- build(deps-dev): bump webpack from 5.77.0 to 5.104.1 in /wasm/node-tester/internal-dev ([#6451]) +- Max/mixfetch concurrent test ([#6417]) + +[#6533]: https://github.com/nymtech/nym/pull/6533 +[#6532]: https://github.com/nymtech/nym/pull/6532 +[#6531]: https://github.com/nymtech/nym/pull/6531 +[#6509]: https://github.com/nymtech/nym/pull/6509 +[#6495]: https://github.com/nymtech/nym/pull/6495 +[#6491]: https://github.com/nymtech/nym/pull/6491 +[#6486]: https://github.com/nymtech/nym/pull/6486 +[#6484]: https://github.com/nymtech/nym/pull/6484 +[#6483]: https://github.com/nymtech/nym/pull/6483 +[#6478]: https://github.com/nymtech/nym/pull/6478 +[#6477]: https://github.com/nymtech/nym/pull/6477 +[#6476]: https://github.com/nymtech/nym/pull/6476 +[#6475]: https://github.com/nymtech/nym/pull/6475 +[#6472]: https://github.com/nymtech/nym/pull/6472 +[#6466]: https://github.com/nymtech/nym/pull/6466 +[#6465]: https://github.com/nymtech/nym/pull/6465 +[#6464]: https://github.com/nymtech/nym/pull/6464 +[#6461]: https://github.com/nymtech/nym/pull/6461 +[#6452]: https://github.com/nymtech/nym/pull/6452 +[#6451]: https://github.com/nymtech/nym/pull/6451 +[#6417]: https://github.com/nymtech/nym/pull/6417 + ## [2026.4-quark] (2026-02-24) - Enhance CI workflow with feature inputs ([#6462]) diff --git a/Cargo.lock b/Cargo.lock index f6bb76641b..29f9d86c89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -24,6 +24,15 @@ 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" @@ -36,7 +45,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common 0.1.7", + "crypto-common", "generic-array 0.14.7", ] @@ -47,18 +56,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "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", + "cipher", "cpufeatures", ] @@ -69,8 +67,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", - "aes 0.8.4", - "cipher 0.4.4", + "aes", + "cipher", "ctr", "ghash", "subtle 2.6.1", @@ -83,24 +81,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" dependencies = [ "aead", - "aes 0.8.4", - "cipher 0.4.4", + "aes", + "cipher", "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" @@ -115,9 +103,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -156,6 +144,12 @@ 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" @@ -182,9 +176,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -197,9 +191,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" @@ -212,47 +206,44 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.5" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.11" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" -version = "1.4.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] [[package]] name = "arc-swap" -version = "1.8.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" -dependencies = [ - "rustversion", -] +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "argon2" @@ -405,7 +396,20 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4744ed2eef2645831b441d8f5459689ade2ab27c854488fbab1fbe94fce1a7" dependencies = [ - "askama_derive", + "askama_derive 0.13.1", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive 0.14.0", "itoa", "percent-encoding", "serde", @@ -418,7 +422,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d661e0f57be36a5c14c48f78d09011e67e0cb618f269cca9f2fd8d15b68c46ac" dependencies = [ - "askama_parser", + "askama_parser 0.13.0", "basic-toml", "memchr", "proc-macro2", @@ -426,7 +430,24 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.114", + "syn 2.0.106", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser 0.14.0", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn 2.0.106", ] [[package]] @@ -441,6 +462,18 @@ dependencies = [ "winnow", ] +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -451,37 +484,29 @@ dependencies = [ "serde_json", ] -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" dependencies = [ - "compression-codecs", - "compression-core", + "brotli", + "flate2", "futures-core", + "memchr", "pin-project-lite", "tokio", + "zstd", + "zstd-safe", ] [[package]] name = "async-lock" -version = "3.4.2" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] @@ -505,18 +530,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -583,9 +608,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" dependencies = [ "cc", "cmake", @@ -600,17 +625,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", - "axum-core", + "axum-core 0.4.5", "axum-macros", "bytes", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.6.0", "hyper-util", "itoa", - "matchit", + "matchit 0.7.3", "memchr", "mime", "percent-encoding", @@ -628,13 +653,38 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core 0.5.6", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit 0.8.4", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper 1.0.2", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-client-ip" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eefda7e2b27e1bda4d6fa8a06b50803b8793769045918bc37ad062d48a6efac" dependencies = [ - "axum", + "axum 0.7.9", "forwarded-header-value", "serde", ] @@ -648,7 +698,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "mime", @@ -660,19 +710,37 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-extra" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" dependencies = [ - "axum", - "axum-core", + "axum 0.7.9", + "axum-core 0.4.5", "bytes", - "fastrand 2.3.0", + "fastrand", "futures-util", "headers", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "mime", @@ -692,7 +760,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -704,13 +772,13 @@ dependencies = [ "anyhow", "assert-json-diff", "auto-future", - "axum", + "axum 0.7.9", "bytes", "bytesize", "cookie", - "http 1.4.0", + "http 1.3.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.6.0", "hyper-util", "mime", "pretty_assertions", @@ -725,6 +793,21 @@ 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" @@ -751,9 +834,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "base85rs" @@ -772,9 +855,9 @@ dependencies = [ [[package]] name = "bech32" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "bincode" @@ -810,9 +893,9 @@ dependencies = [ [[package]] name = "bip39" -version = "2.2.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" +checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" dependencies = [ "bitcoin_hashes", "rand 0.8.5", @@ -823,11 +906,18 @@ dependencies = [ ] [[package]] -name = "bitcoin_hashes" -version = "0.14.1" +name = "bitcoin-internals" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" dependencies = [ + "bitcoin-internals", "hex-conservative", ] @@ -839,11 +929,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ - "serde_core", + "serde", ] [[package]] @@ -949,9 +1039,9 @@ checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" [[package]] name = "brotli" -version = "8.0.2" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -980,9 +1070,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-tools" @@ -1023,11 +1113,11 @@ checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" [[package]] name = "camino" -version = "1.2.2" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ - "serde_core", + "serde", ] [[package]] @@ -1047,7 +1137,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.27", + "semver 1.0.26", "serde", "serde_json", "thiserror 1.0.69", @@ -1061,10 +1151,10 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver 1.0.27", + "semver 1.0.26", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -1073,17 +1163,11 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - [[package]] name = "cc" -version = "1.2.51" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -1097,7 +1181,7 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc3ba3c5408fae183329e0d1ac8f8eed3cb7b647590fd93e6d6288f6b09db0be" dependencies = [ - "phf 0.11.3", + "phf", "serde", ] @@ -1109,9 +1193,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -1136,7 +1220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures", ] @@ -1148,23 +1232,24 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20", - "cipher 0.4.4", + "cipher", "poly1305", "zeroize", ] [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link 0.1.3", ] [[package]] @@ -1200,26 +1285,16 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common 0.1.7", - "inout 0.1.4", + "crypto-common", + "inout", "zeroize", ] -[[package]] -name = "cipher" -version = "0.5.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d708bac5451350d56398433b19a7889022fa9187df1a769c0edbc3b2c03167" -dependencies = [ - "crypto-common 0.2.0-rc.9", - "inout 0.2.2", -] - [[package]] name = "clap" -version = "4.5.54" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -1227,9 +1302,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -1239,9 +1314,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.65" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" +checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a" dependencies = [ "clap", ] @@ -1258,28 +1333,29 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "classic-mceliece-rust" -version = "3.2.0" -source = "git+https://github.com/georgio/classic-mceliece-rust#7bdf0c3c8c727cbc4784e05b0972641ad0791ab9" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62a9b6d27e553269a76625911aa8cf6afaa8659f1b0c85b410cb5f51a87183d9" dependencies = [ - "rand 0.9.2", + "rand 0.8.5", "sha3", "zeroize", ] @@ -1332,35 +1408,15 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.2.1" +version = "7.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" +checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" dependencies = [ - "crossterm 0.29.0", + "crossterm 0.28.1", "unicode-segmentation", - "unicode-width 0.2.2", + "unicode-width 0.2.1", ] -[[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" @@ -1372,15 +1428,15 @@ dependencies = [ [[package]] name = "console" -version = "0.16.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.2", - "windows-sys 0.61.2", + "unicode-width 0.2.1", + "windows-sys 0.60.2", ] [[package]] @@ -1390,7 +1446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857" dependencies = [ "futures-core", - "prost", + "prost 0.13.5", "prost-types", "tonic 0.12.3", "tracing-core", @@ -1409,7 +1465,7 @@ dependencies = [ "hdrhistogram", "humantime", "hyper-util", - "prost", + "prost 0.13.5", "prost-types", "serde", "serde_json", @@ -1446,9 +1502,9 @@ checksum = "3618cccc083bb987a415d85c02ca6c9994ea5b44731ec28b9ecf09658655fba9" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" dependencies = [ "const_format_proc_macros", ] @@ -1508,8 +1564,8 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-models" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "hax-lib", "pastey 0.2.1", @@ -1523,7 +1579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95ac39be7373404accccaede7cc1ec942ccef14f0ca18d209967a756bf1dbb1f" dependencies = [ "informalsystems-pbjson", - "prost", + "prost 0.13.5", "serde", "tendermint-proto", "tonic 0.13.1", @@ -1552,15 +1608,15 @@ dependencies = [ [[package]] name = "cosmwasm-core" -version = "2.3.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63efd882086668c3bad621b5b897a0e058d0b5fae027c984e1a6230bf284ab85" +checksum = "35b6dc17e7fd89d0a0a58f12ef33f0bbdf09a6a14c3dfb383eae665e5889250e" [[package]] name = "cosmwasm-crypto" -version = "2.3.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0969e6f569f867725bf89e672058ae336e130c1fcf914def4f7c6c0380b2bc83" +checksum = "aa2f53285517db3e33d825b3e46301efe845135778527e1295154413b2f0469e" dependencies = [ "ark-bls12-381", "ark-ec", @@ -1582,13 +1638,13 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "2.3.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0da0db99715fc86e6a5ad7be9c19d0d9e5a92179862b17cae7406b729a402c2" +checksum = "a782b93fae93e57ca8ad3e9e994e784583f5933aeaaa5c80a545c4b437be2047" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -1612,7 +1668,7 @@ checksum = "e01c9214319017f6ebd8e299036e1f717fa9bb6724e758f7d6fb2477599d1a29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -1650,9 +1706,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -1777,15 +1833,14 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.29.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.9.1", "crossterm_winapi", - "document-features", "parking_lot", - "rustix", + "rustix 0.38.44", "winapi", ] @@ -1818,24 +1873,15 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 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" @@ -1855,7 +1901,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.11.3", + "phf", "smallvec", ] @@ -1866,26 +1912,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "csv" -version = "1.4.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", "ryu", - "serde_core", + "serde", ] [[package]] name = "csv-core" -version = "0.1.13" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" dependencies = [ "memchr", ] @@ -1902,7 +1948,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.4.4", + "cipher", ] [[package]] @@ -1915,36 +1961,6 @@ dependencies = [ "rustc_version 0.2.3", ] -[[package]] -name = "curl" -version = "0.4.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe 0.1.6", - "openssl-sys", - "schannel", - "socket2 0.6.1", - "windows-sys 0.59.0", -] - -[[package]] -name = "curl-sys" -version = "0.4.84+curl-8.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abc4294dc41b882eaff37973c2ec3ae203d0091341ee68fbadd1d06e0c18a73b" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "windows-sys 0.59.0", -] - [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1970,7 +1986,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -2014,11 +2030,11 @@ dependencies = [ "cw-storage-plus", "cw-utils", "itertools 0.14.0", - "prost", + "prost 0.13.5", "schemars 0.8.22", "serde", "sha2 0.10.9", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -2055,7 +2071,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus", "schemars 0.8.22", - "semver 1.0.27", + "semver 1.0.26", "serde", "thiserror 1.0.69", ] @@ -2107,18 +2123,8 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "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", + "darling_core", + "darling_macro", ] [[package]] @@ -2132,21 +2138,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "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", + "syn 2.0.106", ] [[package]] @@ -2155,20 +2147,9 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core 0.20.11", + "darling_core", "quote", - "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", + "syn 2.0.106", ] [[package]] @@ -2193,9 +2174,9 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "defguard_boringtun" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e226ae51c414f475137460063382d0bb6b5a1b1b1a2b135d4b3b830ab43f06ec" +checksum = "4b7c7f465dde186f958a0a0e4ae823af623451ba26817b8b366b9968286df7a1" dependencies = [ "aead", "base64 0.22.1", @@ -2206,13 +2187,13 @@ dependencies = [ "ip_network", "ip_network_table", "libc", - "nix 0.30.1", + "nix 0.31.1", "parking_lot", "ring", - "socket2 0.6.1", - "thiserror 2.0.17", + "socket2 0.6.0", + "thiserror 2.0.12", "tracing", - "uniffi 0.30.0", + "uniffi 0.31.0", "untrusted", "x25519-dalek", ] @@ -2237,7 +2218,7 @@ dependencies = [ "nix 0.30.1", "regex", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "windows 0.62.2", "wireguard-nt", "x25519-dalek", @@ -2254,7 +2235,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -2270,12 +2251,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", - "serde_core", + "serde", ] [[package]] @@ -2291,13 +2272,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -2315,10 +2296,10 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling 0.20.11", + "darling", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -2328,7 +2309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -2342,11 +2323,11 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "derive_more-impl 2.1.1", + "derive_more-impl 2.0.1", ] [[package]] @@ -2357,20 +2338,19 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", "unicode-xid", ] [[package]] name = "derive_more-impl" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "rustc_version 0.4.1", - "syn 2.0.114", + "syn 2.0.106", "unicode-xid", ] @@ -2406,7 +2386,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "const-oid", - "crypto-common 0.1.7", + "crypto-common", "subtle 2.6.1", ] @@ -2428,7 +2408,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2439,7 +2419,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -2455,18 +2435,9 @@ dependencies = [ [[package]] name = "doc-comment" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dotenvy" @@ -2476,9 +2447,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" [[package]] name = "dtoa-short" @@ -2497,9 +2468,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clone" -version = "1.0.20" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "easy-addr" @@ -2507,7 +2478,7 @@ version = "1.20.4" dependencies = [ "cosmwasm-std", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -2563,12 +2534,12 @@ dependencies = [ [[package]] name = "ed25519-compact" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ce99a9e19c84beb4cc35ece85374335ccc398240712114c85038319ed709bd" +checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" dependencies = [ "ct-codecs", - "getrandom 0.3.4", + "getrandom 0.2.16", ] [[package]] @@ -2669,14 +2640,14 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "env_filter" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -2703,12 +2674,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.14" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2733,15 +2704,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.3" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -2754,7 +2719,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.1", + "event-listener", "pin-project-lite", ] @@ -2768,7 +2733,7 @@ dependencies = [ "nym-wasm-storage", "nym-wasm-utils", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.17", + "thiserror 2.0.12", "wasm-bindgen", "wasm-bindgen-futures", "zeroize", @@ -2799,16 +2764,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.114", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", + "syn 2.0.106", ] [[package]] @@ -2835,21 +2791,21 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -2859,9 +2815,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -2902,9 +2858,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -3018,21 +2974,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-macro" version = "0.3.31" @@ -3041,7 +2982,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -3080,6 +3021,20 @@ 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 0.61.3", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -3110,24 +3065,37 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasip2", + "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "ghash" version = "0.5.1" @@ -3139,10 +3107,16 @@ dependencies = [ ] [[package]] -name = "glob" -version = "0.3.3" +name = "gimli" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "gloo-net" @@ -3154,7 +3128,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils 0.2.0", - "http 1.4.0", + "http 1.3.1", "js-sys", "pin-project", "serde", @@ -3237,7 +3211,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.13.0", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -3246,17 +3220,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.0", - "indexmap 2.13.0", + "http 1.3.1", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -3265,13 +3239,12 @@ dependencies = [ [[package]] name = "half" -version = "2.7.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", - "zerocopy", ] [[package]] @@ -3315,35 +3288,29 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" 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.5", + "hashbrown 0.15.4", ] [[package]] name = "hax-lib" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d9ba66d1739c68e0219b2b2238b5c4145f491ebf181b9c6ab561a19352ae86" +checksum = "543f93241d32b3f00569201bfce9d7a93c92c6421b23c77864ac929dc947b9fc" dependencies = [ "hax-lib-macros", "num-bigint", @@ -3352,22 +3319,22 @@ dependencies = [ [[package]] name = "hax-lib-macros" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ba777a231a58d1bce1d68313fa6b6afcc7966adef23d60f45b8a2b9b688bf1" +checksum = "f8755751e760b11021765bb04cb4a6c4e24742688d9f3aa14c2079638f537b0f" dependencies = [ "hax-lib-macros-types", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "hax-lib-macros-types" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "867e19177d7425140b417cd27c2e05320e727ee682e98368f88b7194e80ad515" +checksum = "f177c9ae8ea456e2f71ff3c1ea47bf4464f772a05133fcbba56cd5ba169035a2" dependencies = [ "proc-macro2", "quote", @@ -3398,7 +3365,7 @@ dependencies = [ "base64 0.22.1", "bytes", "headers-core", - "http 1.4.0", + "http 1.3.1", "httpdate", "mime", "sha1", @@ -3410,7 +3377,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.4.0", + "http 1.3.1", ] [[package]] @@ -3439,12 +3406,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.2.2" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" -dependencies = [ - "arrayvec", -] +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" [[package]] name = "hex-literal" @@ -3466,18 +3430,18 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "h2 0.4.13", - "http 1.4.0", + "h2 0.4.11", + "http 1.3.1", "idna", "ipnet", "once_cell", "rand 0.9.2", "ring", - "rustls 0.23.36", - "thiserror 2.0.17", + "rustls 0.23.29", + "thiserror 2.0.12", "tinyvec", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls 0.26.2", "tracing", "url", "webpki-roots 0.26.11", @@ -3498,11 +3462,11 @@ dependencies = [ "parking_lot", "rand 0.9.2", "resolv-conf", - "rustls 0.23.36", + "rustls 0.23.29", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls 0.26.2", "tracing", "webpki-roots 0.26.11", ] @@ -3539,9 +3503,9 @@ dependencies = [ [[package]] name = "hmac-sha1-compact" -version = "1.1.6" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a4e188bd5d537801721276d1771f1cea235cd94961ddb2828eb76e16688356" +checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3" [[package]] name = "hmac-sha256" @@ -3563,11 +3527,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.12" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3594,11 +3558,12 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", + "fnv", "itoa", ] @@ -3620,7 +3585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http 1.3.1", ] [[package]] @@ -3631,7 +3596,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "pin-project-lite", ] @@ -3672,9 +3637,9 @@ checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1" [[package]] name = "humantime" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "humantime-serde" @@ -3686,15 +3651,6 @@ 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" @@ -3721,22 +3677,20 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ - "atomic-waker", "bytes", "futures-channel", - "futures-core", - "h2 0.4.13", - "http 1.4.0", + "futures-util", + "h2 0.4.11", + "http 1.3.1", "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -3762,13 +3716,13 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.4.0", - "hyper 1.8.1", + "http 1.3.1", + "hyper 1.6.0", "hyper-util", - "rustls 0.23.36", + "rustls 0.23.29", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls 0.26.2", "tower-service", ] @@ -3778,7 +3732,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.8.1", + "hyper 1.6.0", "hyper-util", "pin-project-lite", "tokio", @@ -3787,23 +3741,23 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", - "hyper 1.8.1", + "hyper 1.6.0", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -3811,9 +3765,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3821,7 +3775,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -3845,7 +3799,7 @@ dependencies = [ "flex-error", "ics23", "informalsystems-pbjson", - "prost", + "prost 0.13.5", "serde", "subtle-encoding", "tendermint-proto", @@ -3862,15 +3816,15 @@ dependencies = [ "bytes", "hex", "informalsystems-pbjson", - "prost", + "prost 0.13.5", "serde", ] [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", @@ -3881,9 +3835,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -3894,10 +3848,11 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ + "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -3908,38 +3863,42 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", + "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", + "stable_deref_trait", + "tinystr", "writeable", "yoke", "zerofrom", @@ -3947,6 +3906,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -3955,9 +3920,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.1.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", @@ -3982,7 +3947,7 @@ checksum = "0ab604ee7085efba6efc65e4ebca0e9533e3aff6cb501d7d77b211e3a781c6d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -4016,9 +3981,9 @@ dependencies = [ [[package]] name = "indenter" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexed_db_futures" @@ -4029,13 +3994,13 @@ dependencies = [ "accessory", "cfg-if", "delegate-display", - "derive_more 2.1.1", + "derive_more 2.0.1", "fancy_constructor", "indexed_db_futures_macros_internal", "js-sys", "sealed", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -4051,7 +4016,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -4067,25 +4032,24 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.15.4", "serde", - "serde_core", ] [[package]] name = "indicatif" -version = "0.18.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" dependencies = [ "console", "portable-atomic", - "unicode-width 0.2.2", + "unicode-width 0.2.1", "unit-prefix", "vt100", "web-time", @@ -4131,15 +4095,6 @@ 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" @@ -4156,21 +4111,6 @@ dependencies = [ "unicode-width 0.1.14", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "integer-encoding" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" - [[package]] name = "integration-tests" version = "0.1.0" @@ -4181,8 +4121,10 @@ dependencies = [ "nym-credential-verification", "nym-credentials-interface", "nym-crypto", - "nym-gateway", - "nym-lp-transport", + "nym-kkt", + "nym-kkt-ciphersuite", + "nym-lp", + "nym-node", "nym-registration-client", "nym-test-utils", "nym-wireguard", @@ -4201,6 +4143,17 @@ 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" @@ -4230,7 +4183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2 0.5.10", - "widestring 1.2.1", + "widestring 1.2.0", "windows-sys 0.48.0", "winreg", ] @@ -4252,9 +4205,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", @@ -4262,45 +4215,20 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.17" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.2" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "event-listener 2.5.3", - "futures-lite", - "http 0.2.12", - "log", - "once_cell", - "polling", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -4322,32 +4250,32 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde_core", + "serde", ] [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -4374,19 +4302,19 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.3.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -4394,9 +4322,9 @@ dependencies = [ [[package]] name = "jwt-simple" -version = "0.12.13" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad8761f175784dfbb83709f322fc4daf6b27afd5bf375492f2876f9e925ef5a" +checksum = "731011e9647a71ff4f8474176ff6ce6e0d2de87a0173f15613af3a84c3e3401a" dependencies = [ "anyhow", "binstring", @@ -4414,7 +4342,7 @@ dependencies = [ "serde", "serde_json", "superboring", - "thiserror 2.0.17", + "thiserror 2.0.12", "zeroize", ] @@ -4434,9 +4362,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] @@ -4476,6 +4404,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "ledger-apdu" version = "0.10.0" @@ -4521,8 +4455,8 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libcrux-aesgcm" -version = "0.0.5" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.7" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-intrinsics", "libcrux-platform", @@ -4532,8 +4466,8 @@ dependencies = [ [[package]] name = "libcrux-chacha20poly1305" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4544,8 +4478,8 @@ dependencies = [ [[package]] name = "libcrux-curve25519" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4555,8 +4489,8 @@ dependencies = [ [[package]] name = "libcrux-ecdh" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-curve25519", "libcrux-p256", @@ -4566,8 +4500,8 @@ dependencies = [ [[package]] name = "libcrux-ed25519" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4579,15 +4513,15 @@ dependencies = [ [[package]] name = "libcrux-hacl-rs" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-macros", ] [[package]] name = "libcrux-hkdf" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-hacl-rs", "libcrux-hmac", @@ -4596,8 +4530,8 @@ dependencies = [ [[package]] name = "libcrux-hmac" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4606,8 +4540,8 @@ dependencies = [ [[package]] name = "libcrux-intrinsics" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "core-models", "hax-lib", @@ -4615,8 +4549,8 @@ dependencies = [ [[package]] name = "libcrux-kem" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-curve25519", "libcrux-ecdh", @@ -4631,16 +4565,16 @@ dependencies = [ [[package]] name = "libcrux-macros" version = "0.0.3" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "libcrux-ml-dsa" -version = "0.0.5" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.7" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "core-models", "hax-lib", @@ -4653,8 +4587,8 @@ dependencies = [ [[package]] name = "libcrux-ml-kem" -version = "0.0.5" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.7" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "hax-lib", "libcrux-intrinsics", @@ -4668,8 +4602,8 @@ dependencies = [ [[package]] name = "libcrux-p256" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4681,7 +4615,7 @@ dependencies = [ [[package]] name = "libcrux-platform" version = "0.0.3" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libc", ] @@ -4689,7 +4623,7 @@ dependencies = [ [[package]] name = "libcrux-poly1305" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4697,9 +4631,10 @@ dependencies = [ [[package]] name = "libcrux-psq" -version = "0.0.5" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.7" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ + "classic-mceliece-rust", "libcrux-aesgcm", "libcrux-chacha20poly1305", "libcrux-ecdh", @@ -4711,22 +4646,23 @@ dependencies = [ "libcrux-ml-kem", "libcrux-sha2", "libcrux-traits", + "rand 0.8.5", "rand 0.9.2", "tls_codec", ] [[package]] name = "libcrux-secrets" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "hax-lib", ] [[package]] name = "libcrux-sha2" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4735,8 +4671,8 @@ dependencies = [ [[package]] name = "libcrux-sha3" -version = "0.0.5" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.7" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "hax-lib", "libcrux-intrinsics", @@ -4746,8 +4682,8 @@ dependencies = [ [[package]] name = "libcrux-traits" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" dependencies = [ "libcrux-secrets", "rand 0.9.2", @@ -4771,13 +4707,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.9.1", "libc", - "redox_syscall 0.7.0", + "redox_syscall", ] [[package]] @@ -4792,22 +4728,16 @@ dependencies = [ ] [[package]] -name = "libz-sys" -version = "1.1.23" +name = "linux-raw-sys" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lioness" @@ -4823,30 +4753,38 @@ dependencies = [ [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.14" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.29" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +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", +] [[package]] name = "lru-slab" @@ -4880,7 +4818,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -4892,7 +4830,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -4905,7 +4843,7 @@ dependencies = [ "macroific_core", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -4933,7 +4871,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -4951,6 +4889,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "md-5" version = "0.10.6" @@ -4963,9 +4907,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memoffset" @@ -4994,9 +4938,9 @@ dependencies = [ [[package]] name = "minicov" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" dependencies = [ "cc", "walkdir", @@ -5015,7 +4959,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", - "simd-adler32", ] [[package]] @@ -5026,24 +4969,24 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.48.0", ] [[package]] name = "mio" -version = "1.1.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", - "windows-sys 0.61.2", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] name = "mix-fetch-wasm" -version = "1.4.1" +version = "1.4.2" dependencies = [ "async-trait", "futures", @@ -5059,7 +5002,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tsify", "url", @@ -5090,21 +5033,23 @@ checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" [[package]] name = "moka" -version = "0.12.12" +version = "0.12.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" +checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" dependencies = [ "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", - "equivalent", - "event-listener 5.4.1", + "event-listener", "futures-util", + "loom", "parking_lot", "portable-atomic", + "rustc_version 0.4.1", "smallvec", "tagptr", + "thiserror 1.0.69", "uuid", ] @@ -5117,7 +5062,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.4.0", + "http 1.3.1", "httparse", "memchr", "mime", @@ -5149,7 +5094,7 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.9.1", "libc", "log", "netlink-packet-core", @@ -5163,7 +5108,7 @@ checksum = "3176f18d11a1ae46053e59ec89d46ba318ae1343615bd3f8c908bfc84edae35c" dependencies = [ "byteorder", "pastey 0.1.1", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -5210,7 +5155,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -5222,13 +5167,25 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", "memoffset", ] +[[package]] +name = "nix" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "no-std-compat" version = "0.4.1" @@ -5277,30 +5234,20 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ "winapi", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5315,10 +5262,11 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.6" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ + "byteorder", "lazy_static", "libm", "num-integer", @@ -5331,9 +5279,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" @@ -5365,16 +5313,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_enum" version = "0.7.5" @@ -5394,7 +5332,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -5408,11 +5346,11 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.74" +version = "1.1.75" dependencies = [ "anyhow", "async-trait", - "axum", + "axum 0.7.9", "axum-test", "bincode", "bip39", @@ -5466,7 +5404,7 @@ dependencies = [ "rand_chacha 0.3.1", "reqwest 0.13.1", "schemars 0.8.22", - "semver 1.0.27", + "semver 1.0.26", "serde", "serde_json", "sha2 0.10.9", @@ -5474,7 +5412,7 @@ dependencies = [ "tempfile", "tendermint", "test-with", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -5516,7 +5454,6 @@ dependencies = [ "nym-serde-helpers", "nym-test-utils", "nym-ticketbooks-merkle", - "rand_chacha 0.3.1", "schemars 0.8.22", "serde", "serde_json", @@ -5525,7 +5462,7 @@ dependencies = [ "strum_macros", "tendermint", "tendermint-rpc", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tracing", "ts-rs", @@ -5557,8 +5494,8 @@ dependencies = [ "nym-service-provider-requests-common", "nym-validator-client", "nym-wireguard-types", - "semver 1.0.27", - "thiserror 2.0.17", + "semver 1.0.26", + "thiserror 2.0.12", "tokio", "tokio-util", "tracing", @@ -5579,11 +5516,11 @@ dependencies = [ "nym-test-utils", "nym-wireguard-types", "rand 0.8.5", - "semver 1.0.27", + "semver 1.0.26", "serde", "sha2 0.10.9", "strum_macros", - "thiserror 2.0.17", + "thiserror 2.0.12", "tracing", "x25519-dalek", ] @@ -5602,7 +5539,7 @@ dependencies = [ "nym-task", "nym-validator-client", "rand 0.8.5", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -5615,14 +5552,15 @@ dependencies = [ "const-str", "log", "opentelemetry", - "opentelemetry-jaeger", + "opentelemetry-otlp", + "opentelemetry_sdk", "schemars 0.8.22", "serde", "serde_json", + "tonic 0.14.4", "tracing", "tracing-opentelemetry", "tracing-subscriber", - "tracing-tree", "utoipa", "vergen 8.3.1", ] @@ -5653,7 +5591,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.71" +version = "1.1.72" dependencies = [ "anyhow", "base64 0.22.1", @@ -5726,7 +5664,7 @@ dependencies = [ "serde_json", "tap", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "toml 0.8.23", @@ -5736,7 +5674,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.71" +version = "1.1.72" dependencies = [ "bs58", "clap", @@ -5763,7 +5701,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-tungstenite", @@ -5782,10 +5720,11 @@ dependencies = [ "clap", "comfy-table", "futures", + "getrandom 0.3.3", "gloo-timers", "http-body-util", "humantime", - "hyper 1.8.1", + "hyper 1.6.0", "hyper-util", "nym-bandwidth-controller", "nym-client-core-config-types", @@ -5817,7 +5756,7 @@ dependencies = [ "sha2 0.10.9", "si-scale", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -5843,7 +5782,7 @@ dependencies = [ "nym-sphinx-params", "nym-statistics-common", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "url", ] @@ -5858,7 +5797,7 @@ dependencies = [ "nym-gateway-requests", "serde", "sqlx", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -5878,7 +5817,7 @@ dependencies = [ "nym-sqlx-pool-guard", "nym-task", "sqlx", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -5903,7 +5842,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.6.5", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio_with_wasm", "tsify", "wasm-bindgen", @@ -5962,7 +5901,7 @@ dependencies = [ "serde", "sha2 0.10.9", "subtle 2.6.1", - "thiserror 2.0.17", + "thiserror 2.0.12", "zeroize", ] @@ -5975,7 +5914,7 @@ dependencies = [ "log", "nym-network-defaults", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "toml 0.8.23", "url", ] @@ -5992,7 +5931,7 @@ dependencies = [ "nym-ip-packet-requests", "nym-sdk", "pnet_packet", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tokio-util", "tracing", @@ -6010,7 +5949,7 @@ dependencies = [ "schemars 0.8.22", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", "utoipa", "vergen 8.3.1", ] @@ -6048,7 +5987,7 @@ name = "nym-credential-proxy" version = "0.3.0" dependencies = [ "anyhow", - "axum", + "axum 0.7.9", "bip39", "bs58", "cfg-if", @@ -6079,7 +6018,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -6097,7 +6036,7 @@ name = "nym-credential-proxy-lib" version = "1.20.4" dependencies = [ "anyhow", - "axum", + "axum 0.7.9", "bip39", "bs58", "futures", @@ -6120,7 +6059,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -6171,7 +6110,7 @@ dependencies = [ "nym-test-utils", "serde", "sqlx", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "zeroize", @@ -6190,7 +6129,7 @@ dependencies = [ "nym-credentials-interface", "nym-ecash-time", "nym-validator-client", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", ] @@ -6217,7 +6156,7 @@ dependencies = [ "nym-upgrade-mode-check", "nym-validator-client", "si-scale", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -6242,7 +6181,7 @@ dependencies = [ "nym-validator-client", "rand 0.8.5", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "zeroize", ] @@ -6260,7 +6199,7 @@ dependencies = [ "serde", "strum", "strum_macros", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "utoipa", ] @@ -6270,13 +6209,13 @@ name = "nym-crypto" version = "1.20.4" dependencies = [ "aead", - "aes 0.8.4", + "aes", "aes-gcm-siv", "anyhow", "base64 0.22.1", "blake3", "bs58", - "cipher 0.4.4", + "cipher", "ctr", "curve25519-dalek", "digest 0.10.7", @@ -6285,17 +6224,20 @@ dependencies = [ "hkdf", "hmac", "jwt-simple", + "libcrux-curve25519", + "libcrux-psq", "nym-pemstore", "nym-sphinx-types", "nym-test-utils", "rand 0.8.5", + "rand 0.9.2", "rand_chacha 0.3.1", "serde", "serde_bytes", "serde_json", "sha2 0.10.9", "subtle-encoding", - "thiserror 2.0.17", + "thiserror 2.0.12", "x25519-dalek", "zeroize", ] @@ -6306,7 +6248,7 @@ version = "1.0.1" dependencies = [ "anyhow", "async-trait", - "axum", + "axum 0.7.9", "blake3", "chrono", "clap", @@ -6324,7 +6266,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -6355,7 +6297,7 @@ dependencies = [ "serde", "serde_derive", "sha2 0.10.9", - "thiserror 2.0.17", + "thiserror 2.0.12", "zeroize", ] @@ -6370,7 +6312,7 @@ dependencies = [ "cw-utils", "cw2", "nym-multisig-contract-common", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -6382,9 +6324,9 @@ dependencies = [ "nym-http-api-client", "nym-network-defaults", "nym-validator-client", - "semver 1.0.27", + "semver 1.0.26", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tracing", "url", @@ -6396,9 +6338,9 @@ version = "1.20.4" dependencies = [ "nym-coconut-dkg-common", "nym-crypto", - "semver 1.0.27", + "semver 1.0.26", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tracing", "url", @@ -6420,7 +6362,7 @@ dependencies = [ "reqwest 0.13.1", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", "tracing", "utoipa", ] @@ -6437,8 +6379,8 @@ dependencies = [ "nym-sdk", "nym-sphinx-anonymous-replies", "tokio", - "uniffi 0.29.5", - "uniffi_build 0.29.5", + "uniffi 0.29.3", + "uniffi_build 0.29.3", ] [[package]] @@ -6450,10 +6392,9 @@ dependencies = [ "bincode", "bip39", "bs58", - "bytes", "dashmap", "defguard_wireguard_rs", - "fastrand 2.3.0", + "fastrand", "futures", "humantime-serde", "ipnetwork", @@ -6470,9 +6411,7 @@ dependencies = [ "nym-gateway-storage", "nym-id", "nym-ip-packet-router", - "nym-kcp", "nym-lp", - "nym-lp-transport", "nym-metrics", "nym-mixnet-client", "nym-network-defaults", @@ -6493,7 +6432,7 @@ dependencies = [ "nym-wireguard-types", "rand 0.8.5", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -6527,7 +6466,7 @@ dependencies = [ "rand 0.8.5", "serde", "si-scale", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -6579,7 +6518,9 @@ dependencies = [ "nym-validator-client", "pnet_packet", "rand 0.8.5", + "rand 0.9.2", "reqwest 0.13.1", + "semver 1.0.26", "serde", "serde_json", "time", @@ -6615,7 +6556,7 @@ dependencies = [ "serde_json", "strum", "subtle 2.6.1", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -6634,7 +6575,7 @@ dependencies = [ "nym-statistics-common", "sqlx", "strum", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -6653,7 +6594,7 @@ dependencies = [ "nym-gateway-requests", "nym-sphinx", "sqlx", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -6670,10 +6611,10 @@ dependencies = [ "nym-ffi-shared", "nym-sdk", "nym-sphinx-anonymous-replies", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", - "uniffi 0.29.5", - "uniffi_build 0.29.5", + "uniffi 0.29.3", + "uniffi_build 0.29.3", ] [[package]] @@ -6697,7 +6638,7 @@ dependencies = [ "cfg-if", "encoding_rs", "hickory-resolver", - "http 1.4.0", + "http 1.3.1", "inventory", "itertools 0.14.0", "mime", @@ -6711,7 +6652,7 @@ dependencies = [ "serde_json", "serde_plain", "serde_yaml", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tracing", "tracing-subscriber", @@ -6728,7 +6669,7 @@ dependencies = [ "proc-macro2", "quote", "reqwest 0.13.1", - "syn 2.0.114", + "syn 2.0.106", "uuid", ] @@ -6736,7 +6677,7 @@ dependencies = [ name = "nym-http-api-common" version = "1.20.4" dependencies = [ - "axum", + "axum 0.7.9", "axum-client-ip", "bincode", "bytes", @@ -6760,7 +6701,7 @@ version = "1.20.4" dependencies = [ "nym-credential-storage", "nym-credentials", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tracing", "zeroize", @@ -6786,7 +6727,7 @@ version = "1.20.4" dependencies = [ "log", "rand 0.8.5", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -6798,7 +6739,7 @@ dependencies = [ "futures", "nym-ip-packet-requests", "nym-sdk", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tokio-util", "tracing", @@ -6816,7 +6757,7 @@ dependencies = [ "nym-sphinx", "rand 0.8.5", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -6858,7 +6799,7 @@ dependencies = [ "reqwest 0.13.1", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-tun", @@ -6874,7 +6815,7 @@ dependencies = [ "bytes", "env_logger", "log", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio-util", ] @@ -6883,19 +6824,20 @@ 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", "num_enum", "nym-crypto", "nym-kkt-ciphersuite", + "nym-kkt-context", + "nym-pemstore", "rand 0.9.2", "rand_chacha 0.9.0", "strum", - "thiserror 2.0.17", + "thiserror 2.0.12", "zeroize", ] @@ -6906,9 +6848,19 @@ dependencies = [ "blake3", "libcrux-sha3", "num_enum", + "semver 1.0.26", "strum", "strum_macros", - "thiserror 2.0.17", + "thiserror 2.0.12", +] + +[[package]] +name = "nym-kkt-context" +version = "1.20.4" +dependencies = [ + "num_enum", + "nym-kkt-ciphersuite", + "thiserror 2.0.12", ] [[package]] @@ -6919,7 +6871,7 @@ dependencies = [ "k256", "ledger-transport", "ledger-transport-hid", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -6929,26 +6881,15 @@ dependencies = [ "anyhow", "bs58", "bytes", - "chacha20poly1305", "criterion", - "dashmap", - "libcrux-kem", "libcrux-psq", - "libcrux-traits", - "mock_instant", "num_enum", "nym-crypto", "nym-kkt", - "nym-lp-common", - "nym-lp-transport", + "nym-kkt-ciphersuite", "nym-test-utils", - "parking_lot", - "rand 0.8.5", "rand 0.9.2", - "serde", - "sha2 0.10.9", - "snow", - "thiserror 2.0.17", + "thiserror 2.0.12", "tls_codec", "tokio", "tracing", @@ -6979,6 +6920,7 @@ dependencies = [ "nym-topology", "nym-validator-client", "rand 0.8.5", + "rand 0.9.2", "rand_chacha 0.3.1", "serde", "serde_json", @@ -6990,19 +6932,6 @@ dependencies = [ "url", ] -[[package]] -name = "nym-lp-common" -version = "0.1.0" - -[[package]] -name = "nym-lp-transport" -version = "0.1.0" -dependencies = [ - "nym-test-utils", - "tokio", - "tracing", -] - [[package]] name = "nym-metrics" version = "1.20.4" @@ -7044,10 +6973,10 @@ dependencies = [ "nym-contracts-common", "rand_chacha 0.3.1", "schemars 0.8.22", - "semver 1.0.27", + "semver 1.0.26", "serde", "serde_repr", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "ts-rs", "utoipa", @@ -7073,7 +7002,7 @@ dependencies = [ "nym-task", "rand 0.8.5", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -7092,7 +7021,7 @@ dependencies = [ "cw4", "schemars 0.8.22", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7116,7 +7045,7 @@ name = "nym-network-monitor" version = "1.0.2" dependencies = [ "anyhow", - "axum", + "axum 0.7.9", "clap", "dashmap", "futures", @@ -7148,7 +7077,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.72" +version = "1.1.73" dependencies = [ "addr", "anyhow", @@ -7188,7 +7117,7 @@ dependencies = [ "sqlx", "tap", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-tungstenite", @@ -7198,13 +7127,14 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.26.0" +version = "1.27.0" dependencies = [ "anyhow", "arc-swap", "arrayref", "async-trait", - "axum", + "axum 0.7.9", + "bincode", "bip39", "blake2 0.8.1", "bloomfilter", @@ -7219,6 +7149,7 @@ dependencies = [ "criterion", "csv", "cupid", + "dashmap", "futures", "hex", "hkdf", @@ -7238,6 +7169,7 @@ dependencies = [ "nym-http-api-common", "nym-ip-packet-router", "nym-kkt", + "nym-lp", "nym-metrics", "nym-mixnet-client", "nym-network-requester", @@ -7247,6 +7179,7 @@ dependencies = [ "nym-noise-keys", "nym-nonexhaustive-delayqueue", "nym-pemstore", + "nym-registration-common", "nym-sphinx-acknowledgements", "nym-sphinx-addressing", "nym-sphinx-forwarding", @@ -7256,19 +7189,22 @@ dependencies = [ "nym-sphinx-types", "nym-statistics-common", "nym-task", + "nym-test-utils", "nym-topology", "nym-types", "nym-validator-client", "nym-verloc", "nym-wireguard", "nym-wireguard-types", + "opentelemetry", + "opentelemetry_sdk", "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.2", "serde", "serde_json", "sha2 0.10.9", "sysinfo", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -7311,15 +7247,15 @@ dependencies = [ "nym-http-api-client", "nym-kkt-ciphersuite", "nym-noise-keys", + "nym-test-utils", "nym-upgrade-mode-check", "nym-wireguard-types", - "rand_chacha 0.3.1", "schemars 0.8.22", "serde", "serde_json", "strum", "strum_macros", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "url", @@ -7351,7 +7287,7 @@ version = "4.1.0" dependencies = [ "ammonia", "anyhow", - "axum", + "axum 0.7.9", "axum-test", "bip39", "bs58", @@ -7384,21 +7320,21 @@ dependencies = [ "rand_chacha 0.3.1", "regex", "reqwest 0.13.1", - "semver 1.0.27", + "semver 1.0.26", "serde", "serde_json", "serde_json_path", "sqlx", "strum", "strum_macros", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", "tokio-util", "tower-http", "tracing", - "tracing-log 0.2.0", + "tracing-log", "tracing-subscriber", "utoipa", "utoipa-swagger-ui", @@ -7438,7 +7374,7 @@ dependencies = [ "rand_chacha 0.3.1", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", ] @@ -7454,7 +7390,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tsify", "wasm-bindgen", @@ -7479,7 +7415,7 @@ dependencies = [ "snow", "strum", "strum_macros", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tokio-util", "tracing", @@ -7526,7 +7462,7 @@ name = "nym-ordered-buffer" version = "1.20.4" dependencies = [ "log", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7536,14 +7472,9 @@ dependencies = [ "blake3", "chacha20", "chacha20poly1305", - "criterion", - "fastrand 2.3.0", - "getrandom 0.2.16", - "log", - "rand 0.8.5", - "rayon", + "fastrand", "sphinx-packet", - "thiserror 2.0.17", + "thiserror 2.0.12", "x25519-dalek", "zeroize", ] @@ -7567,7 +7498,7 @@ dependencies = [ "nym-contracts-common", "schemars 0.8.22", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7579,7 +7510,7 @@ dependencies = [ "cw-controllers", "schemars 0.8.22", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", ] @@ -7587,6 +7518,7 @@ dependencies = [ name = "nym-registration-client" version = "1.20.4" dependencies = [ + "bincode", "bytes", "futures", "nym-authenticator-client", @@ -7595,19 +7527,19 @@ dependencies = [ "nym-credentials-interface", "nym-crypto", "nym-ip-packet-client", + "nym-kkt", "nym-lp", - "nym-lp-transport", "nym-registration-common", "nym-sdk", + "nym-test-utils", "nym-validator-client", "nym-wireguard-types", - "rand 0.8.5", - "thiserror 2.0.17", + "rand 0.9.2", + "thiserror 2.0.12", "tokio", "tokio-util", "tracing", "typed-builder", - "url", ] [[package]] @@ -7643,7 +7575,7 @@ dependencies = [ "dotenvy", "futures", "hex", - "http 1.4.0", + "http 1.3.1", "httpcodec", "log", "nym-bandwidth-controller", @@ -7673,7 +7605,7 @@ dependencies = [ "serde", "tap", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -7703,7 +7635,7 @@ version = "1.20.4" dependencies = [ "bincode", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7716,7 +7648,7 @@ dependencies = [ "nym-sphinx-anonymous-replies", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7741,7 +7673,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.71" +version = "1.1.72" dependencies = [ "bs58", "clap", @@ -7765,7 +7697,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "url", @@ -7799,7 +7731,7 @@ dependencies = [ "schemars 0.8.22", "serde", "tap", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "url", ] @@ -7831,7 +7763,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7855,7 +7787,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_distr", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -7874,7 +7806,7 @@ dependencies = [ "nym-topology", "rand 0.8.5", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "zeroize", ] @@ -7888,7 +7820,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7904,7 +7836,7 @@ dependencies = [ "nym-topology", "rand 0.8.5", "rand_chacha 0.3.1", - "thiserror 2.0.17", + "thiserror 2.0.12", "tracing", "wasm-bindgen", ] @@ -7922,7 +7854,7 @@ dependencies = [ "nym-sphinx-types", "rand 0.8.5", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "utoipa", "wasmtimer", ] @@ -7941,7 +7873,7 @@ dependencies = [ "nym-sphinx-types", "nym-topology", "rand 0.8.5", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7952,7 +7884,7 @@ dependencies = [ "nym-sphinx-anonymous-replies", "nym-sphinx-params", "nym-sphinx-types", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7966,7 +7898,7 @@ dependencies = [ "nym-sphinx-forwarding", "nym-sphinx-params", "nym-sphinx-types", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tokio-util", "tracing", @@ -7979,7 +7911,7 @@ dependencies = [ "nym-crypto", "nym-sphinx-types", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7988,7 +7920,7 @@ version = "1.20.4" dependencies = [ "nym-sphinx-addressing", "nym-sphinx-types", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -7997,7 +7929,7 @@ version = "1.20.4" dependencies = [ "nym-outfox", "sphinx-packet", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -8018,7 +7950,7 @@ name = "nym-statistics-api" version = "0.3.1" dependencies = [ "anyhow", - "axum", + "axum 0.7.9", "axum-client-ip", "axum-extra", "celes", @@ -8061,7 +7993,7 @@ dependencies = [ "strum", "strum_macros", "sysinfo", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "utoipa", @@ -8079,7 +8011,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", "zeroize", ] @@ -8092,7 +8024,7 @@ dependencies = [ "futures", "log", "nym-test-utils", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tokio-util", "tracing", @@ -8109,6 +8041,7 @@ dependencies = [ "futures", "nym-bin-common", "rand_chacha 0.3.1", + "rand_chacha 0.9.0", "tokio", "tracing", ] @@ -8145,7 +8078,7 @@ dependencies = [ "reqwest 0.13.1", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tracing", "tsify", @@ -8159,7 +8092,7 @@ dependencies = [ "etherparse", "log", "nym-wireguard-types", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tokio-tun", ] @@ -8188,7 +8121,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.12", "ts-rs", "url", "utoipa", @@ -8207,7 +8140,7 @@ dependencies = [ "reqwest 0.13.1", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tracing", "utoipa", @@ -8248,13 +8181,13 @@ dependencies = [ "nym-performance-contract-common", "nym-serde-helpers", "nym-vesting-contract-common", - "prost", + "prost 0.13.5", "reqwest 0.13.1", "serde", "serde_json", "sha2 0.10.9", "tendermint-rpc", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -8299,7 +8232,7 @@ dependencies = [ "serde_with", "sha2 0.10.9", "sqlx", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -8320,7 +8253,7 @@ dependencies = [ "nym-task", "nym-validator-client", "rand 0.8.5", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -8338,35 +8271,10 @@ dependencies = [ "nym-contracts-common", "nym-mixnet-contract-common", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "ts-rs", ] -[[package]] -name = "nym-vpn-api-lib-wasm" -version = "1.20.4" -dependencies = [ - "bs58", - "getrandom 0.2.16", - "js-sys", - "nym-bin-common", - "nym-compact-ecash", - "nym-credential-proxy-requests", - "nym-credentials", - "nym-credentials-interface", - "nym-crypto", - "nym-ecash-time", - "nym-wasm-utils", - "serde", - "serde-wasm-bindgen 0.6.5", - "serde_json", - "thiserror 2.0.17", - "time", - "tsify", - "wasm-bindgen", - "zeroize", -] - [[package]] name = "nym-wallet-types" version = "1.0.0" @@ -8412,7 +8320,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tsify", "url", @@ -8426,14 +8334,12 @@ name = "nym-wasm-storage" version = "1.20.4" 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", + "thiserror 2.0.12", "wasm-bindgen", ] @@ -8477,7 +8383,7 @@ dependencies = [ "nym-test-utils", "nym-wireguard-types", "rand 0.8.5", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tokio-stream", "tracing", @@ -8499,7 +8405,7 @@ version = "1.20.4" dependencies = [ "anyhow", "async-trait", - "axum", + "axum 0.7.9", "futures", "nym-credential-verification", "nym-credentials-interface", @@ -8517,12 +8423,12 @@ dependencies = [ name = "nym-wireguard-private-metadata-shared" version = "1.20.4" dependencies = [ - "axum", + "axum 0.7.9", "bincode", "nym-credentials-interface", "schemars 0.8.22", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "utoipa", ] @@ -8531,7 +8437,7 @@ name = "nym-wireguard-private-metadata-tests" version = "1.20.4" dependencies = [ "async-trait", - "axum", + "axum 0.7.9", "futures", "nym-credential-verification", "nym-credentials-interface", @@ -8558,13 +8464,13 @@ dependencies = [ "nym-crypto", "rand 0.8.5", "serde", - "thiserror 2.0.17", + "thiserror 2.0.12", "x25519-dalek", ] [[package]] name = "nymvisor" -version = "0.1.36" +version = "0.1.37" dependencies = [ "anyhow", "bytes", @@ -8585,7 +8491,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "tar", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -8598,7 +8504,7 @@ version = "0.1.15" dependencies = [ "anyhow", "async-trait", - "axum", + "axum 0.7.9", "chrono", "clap", "nym-bin-common", @@ -8611,7 +8517,7 @@ dependencies = [ "schemars 0.8.22", "serde", "sqlx", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -8635,7 +8541,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -8653,13 +8559,13 @@ dependencies = [ "futures", "humantime", "ibc-proto", - "prost", + "prost 0.13.5", "serde", "serde_json", "sha2 0.10.9", "tendermint", "tendermint-rpc", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -8676,30 +8582,39 @@ dependencies = [ "async-trait", "nyxd-scraper-shared", "sqlx", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tracing", ] [[package]] name = "objc2-core-foundation" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.9.1", ] [[package]] name = "objc2-io-kit" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" 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" @@ -8712,9 +8627,9 @@ dependencies = [ [[package]] name = "once_cell_polyfill" -version = "1.70.2" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "oorandom" @@ -8746,106 +8661,78 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "opentelemetry" -version = "0.19.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4b8347cc26099d3aeee044065ecc3ae11469796b4d65d065a23a584ed92a6f" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" dependencies = [ - "opentelemetry_api", - "opentelemetry_sdk", + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.12", + "tracing", ] [[package]] name = "opentelemetry-http" -version = "0.8.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a819b71d6530c4297b49b3cae2939ab3a8cc1b9f382826a1bc29dd0ca3864906" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" dependencies = [ "async-trait", "bytes", - "http 0.2.12", - "isahc", - "opentelemetry_api", + "http 1.3.1", + "opentelemetry", + "reqwest 0.12.22", ] [[package]] -name = "opentelemetry-jaeger" -version = "0.18.0" +name = "opentelemetry-otlp" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08e028dc9f4f304e9320ce38c80e7cf74067415b1ad5a8750a38bae54a4d450d" +checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" dependencies = [ - "async-trait", - "futures", - "futures-executor", - "http 0.2.12", - "isahc", - "once_cell", + "http 1.3.1", "opentelemetry", "opentelemetry-http", - "opentelemetry-semantic-conventions", - "thiserror 1.0.69", - "thrift", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost 0.14.3", + "reqwest 0.12.22", + "thiserror 2.0.12", "tokio", + "tonic 0.14.4", + "tracing", ] [[package]] -name = "opentelemetry-semantic-conventions" -version = "0.11.0" +name = "opentelemetry-proto" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e33428e6bf08c6f7fcea4ddb8e358fab0fe48ab877a87c70c6ebe20f673ce5" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" dependencies = [ "opentelemetry", -] - -[[package]] -name = "opentelemetry_api" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed41783a5bf567688eb38372f2b7a8530f5a607a4b49d38dd7573236c23ca7e2" -dependencies = [ - "fnv", - "futures-channel", - "futures-util", - "indexmap 1.9.3", - "once_cell", - "pin-project-lite", - "thiserror 1.0.69", - "urlencoding", + "opentelemetry_sdk", + "prost 0.14.3", + "tonic 0.14.4", + "tonic-prost", ] [[package]] name = "opentelemetry_sdk" -version = "0.19.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" dependencies = [ - "async-trait", - "crossbeam-channel", - "dashmap", - "fnv", "futures-channel", "futures-executor", "futures-util", - "once_cell", - "opentelemetry_api", + "opentelemetry", "percent-encoding", - "rand 0.8.5", - "thiserror 1.0.69", - "tokio", - "tokio-stream", + "rand 0.9.2", + "thiserror 2.0.12", ] [[package]] @@ -8854,21 +8741,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "p256" version = "0.13.2" @@ -8910,9 +8782,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.5" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -8920,15 +8792,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.12" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-targets 0.52.6", ] [[package]] @@ -9009,25 +8881,26 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.8.5" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", + "thiserror 2.0.12", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.5" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -9035,22 +8908,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.5" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "pest_meta" -version = "2.8.5" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ "pest", "sha2 0.10.9", @@ -9063,7 +8936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.13.0", + "indexmap 2.10.0", ] [[package]] @@ -9073,17 +8946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "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", + "phf_shared", ] [[package]] @@ -9093,7 +8956,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", - "phf_shared 0.11.3", + "phf_shared", ] [[package]] @@ -9102,7 +8965,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared 0.11.3", + "phf_shared", "rand 0.8.5", ] @@ -9113,10 +8976,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", - "phf_shared 0.11.3", + "phf_shared", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -9128,15 +8991,6 @@ 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" @@ -9154,7 +9008,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -9248,7 +9102,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -9272,22 +9126,6 @@ dependencies = [ "pnet_macros_support", ] -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "poly1305" version = "0.8.0" @@ -9313,9 +9151,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -9328,9 +9166,9 @@ dependencies = [ [[package]] name = "postgres-protocol" -version = "0.6.9" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" +checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" dependencies = [ "base64 0.22.1", "byteorder", @@ -9346,9 +9184,9 @@ dependencies = [ [[package]] name = "postgres-types" -version = "0.2.11" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095" +checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" dependencies = [ "bytes", "fallible-iterator", @@ -9357,9 +9195,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" dependencies = [ "zerovec", ] @@ -9395,6 +9233,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.106", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -9406,11 +9254,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit", ] [[package]] @@ -9432,14 +9280,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -9465,7 +9313,7 @@ dependencies = [ "memchr", "parking_lot", "protobuf", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -9475,7 +9323,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.13.5", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive 0.14.3", ] [[package]] @@ -9488,7 +9346,20 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -9497,7 +9368,7 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" dependencies = [ - "prost", + "prost 0.13.5", ] [[package]] @@ -9522,9 +9393,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.176" +version = "2.1.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f06bb516febf92008f7e4ebdc1129a75e07905eadc4601af27093cb83928c78" +checksum = "45f621acfbd2ca5670eee9a95270747dfa9a29e63e1937d7e6a1ac6994331966" dependencies = [ "psl-types", ] @@ -9553,9 +9424,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", "cfg_aliases", @@ -9563,9 +9434,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.36", - "socket2 0.6.1", - "thiserror 2.0.17", + "rustls 0.23.29", + "socket2 0.5.10", + "thiserror 2.0.12", "tokio", "tracing", "web-time", @@ -9573,21 +9444,21 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "aws-lc-rs", "bytes", - "getrandom 0.3.4", + "getrandom 0.3.3", "lru-slab", "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.36", + "rustls 0.23.29", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.12", "tinyvec", "tracing", "web-time", @@ -9595,23 +9466,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.5.10", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.43" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -9684,7 +9555,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.3.3", ] [[package]] @@ -9699,9 +9570,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -9709,9 +9580,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.13.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -9719,20 +9590,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.18" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" dependencies = [ - "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", + "bitflags 2.9.1", ] [[package]] @@ -9743,34 +9605,34 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] name = "ref-cast" -version = "1.0.25" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.25" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -9780,9 +9642,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -9791,9 +9653,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -9836,6 +9698,40 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tower 0.5.2", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "reqwest" version = "0.13.1" @@ -9846,10 +9742,10 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.6.0", "hyper-rustls 0.27.7", "hyper-util", "js-sys", @@ -9857,7 +9753,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.36", + "rustls 0.23.29", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -9865,7 +9761,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls 0.26.2", "tokio-util", "tower 0.5.2", "tower-http", @@ -9883,14 +9779,14 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] name = "resolv-conf" -version = "0.7.6" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" +checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" [[package]] name = "rfc6979" @@ -9927,19 +9823,22 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.15" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" dependencies = [ + "byteorder", "num-traits", + "paste", ] [[package]] name = "rmp-serde" -version = "1.3.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" dependencies = [ + "byteorder", "rmp", "serde", ] @@ -9955,9 +9854,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.10" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest 0.10.7", @@ -9976,9 +9875,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.9.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca" +checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -9987,22 +9886,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.9.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" +checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.114", + "syn 2.0.106", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.9.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" +checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" dependencies = [ "sha2 0.10.9", "walkdir", @@ -10024,6 +9923,12 @@ 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" @@ -10045,20 +9950,33 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.27", + "semver 1.0.26", ] [[package]] name = "rustix" -version = "1.1.3" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys", - "windows-sys 0.61.2", + "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", ] [[package]] @@ -10089,16 +10007,16 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.4", "subtle 2.6.1", "zeroize", ] @@ -10137,7 +10055,7 @@ dependencies = [ "openssl-probe 0.2.1", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework 3.6.0", ] [[package]] @@ -10160,9 +10078,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", "zeroize", @@ -10179,14 +10097,14 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.36", + "rustls 0.23.29", "rustls-native-certs 0.8.3", "rustls-platform-verifier-android", - "rustls-webpki 0.103.8", - "security-framework 3.5.1", + "rustls-webpki 0.103.4", + "security-framework 3.6.0", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -10218,9 +10136,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "aws-lc-rs", "ring", @@ -10230,15 +10148,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -10251,11 +10169,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -10286,9 +10204,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", "ref-cast", @@ -10305,9 +10223,15 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.114", + "syn 2.0.106", ] +[[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" @@ -10331,7 +10255,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -10352,7 +10276,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -10394,7 +10318,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -10403,11 +10327,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.9.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -10416,9 +10340,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", @@ -10435,12 +10359,11 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", - "serde_core", ] [[package]] @@ -10492,12 +10415,11 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.19" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" dependencies = [ "serde", - "serde_core", ] [[package]] @@ -10517,7 +10439,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -10528,7 +10450,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -10539,20 +10461,19 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", + "ryu", "serde", - "serde_core", - "zmij", ] [[package]] @@ -10568,7 +10489,7 @@ dependencies = [ "serde_json", "serde_json_path_core", "serde_json_path_macros", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -10580,7 +10501,7 @@ dependencies = [ "inventory", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", ] [[package]] @@ -10602,18 +10523,17 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "serde_path_to_error" -version = "0.1.20" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ "itoa", "serde", - "serde_core", ] [[package]] @@ -10633,7 +10553,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -10659,18 +10579,19 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.10.0", "schemars 0.9.0", - "schemars 1.2.0", - "serde_core", + "schemars 1.0.4", + "serde", + "serde_derive", "serde_json", "serde_with_macros", "time", @@ -10678,14 +10599,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ - "darling 0.21.3", + "darling", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -10694,7 +10615,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.10.0", "itoa", "ryu", "serde", @@ -10799,9 +10720,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio 0.8.11", @@ -10810,11 +10731,10 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.8" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ - "errno", "libc", ] @@ -10830,9 +10750,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "siphasher" @@ -10852,17 +10772,6 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - [[package]] name = "smallvec" version = "1.15.1" @@ -10928,12 +10837,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -10942,7 +10851,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c26f0c20d909fdda1c5d0ece3973127ca421984d55b000215df365e93722fc6e" dependencies = [ - "aes 0.8.4", + "aes", "arrayref", "blake2 0.8.1", "bs58", @@ -11006,24 +10915,24 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener 5.4.1", + "event-listener", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.5", + "hashbrown 0.15.4", "hashlink", - "indexmap 2.13.0", + "indexmap 2.10.0", "log", "memchr", "once_cell", "percent-encoding", - "rustls 0.23.36", + "rustls 0.23.29", "serde", "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -11042,7 +10951,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -11065,7 +10974,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.114", + "syn 2.0.106", "tokio", "url", ] @@ -11078,7 +10987,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.9.1", "byteorder", "bytes", "chrono", @@ -11108,7 +11017,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tracing", "whoami", @@ -11122,7 +11031,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.9.1", "byteorder", "chrono", "crc", @@ -11147,7 +11056,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tracing", "whoami", @@ -11173,7 +11082,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tracing", "url", @@ -11191,9 +11100,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" @@ -11209,7 +11118,7 @@ checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared 0.11.3", + "phf_shared", "precomputed-hash", "serde", ] @@ -11221,7 +11130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ "phf_generator", - "phf_shared 0.11.3", + "phf_shared", "proc-macro2", "quote", ] @@ -11261,7 +11170,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -11293,12 +11202,10 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "superboring" -version = "0.1.6" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d8c985e81c88f5694d5dfc232691b2aa34f3e1f66e860db9cd1ddf2bb6dc64" +checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f" dependencies = [ - "aes-gcm", - "aes-keywrap", "getrandom 0.2.16", "hmac-sha256", "hmac-sha512", @@ -11319,9 +11226,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -11351,14 +11258,14 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "sysinfo" -version = "0.37.2" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753" dependencies = [ "libc", "memchr", @@ -11414,15 +11321,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.24.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "fastrand 2.3.0", - "getrandom 0.3.4", + "fastrand", + "getrandom 0.3.3", "once_cell", - "rustix", - "windows-sys 0.61.2", + "rustix 1.0.8", + "windows-sys 0.59.0", ] [[package]] @@ -11440,7 +11347,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost", + "prost 0.13.5", "ripemd", "serde", "serde_bytes", @@ -11477,7 +11384,7 @@ checksum = "d2c40e13d39ca19082d8a7ed22de7595979350319833698f8b1080f29620a094" dependencies = [ "bytes", "flex-error", - "prost", + "prost 0.13.5", "serde", "serde_bytes", "subtle-encoding", @@ -11500,7 +11407,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "reqwest 0.11.27", - "semver 1.0.27", + "semver 1.0.26", "serde", "serde_bytes", "serde_json", @@ -11540,15 +11447,15 @@ dependencies = [ [[package]] name = "test-with" -version = "0.15.6" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ec38bac967f54b11756d3b8b42069f5b30c3ff449972c516683b98340a4e8e" +checksum = "e0f370b9efbfbbc5f057cbce9888373eaeb146a3095bb8cc869b199c94d15559" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", "regex", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -11584,7 +11491,7 @@ dependencies = [ "serde_json", "sqlx", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.12", "time", "tokio", "toml 0.8.23", @@ -11613,11 +11520,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.12", ] [[package]] @@ -11628,18 +11535,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -11651,33 +11558,11 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "thrift" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" -dependencies = [ - "byteorder", - "integer-encoding", - "log", - "ordered-float", - "threadpool", -] - [[package]] name = "time" -version = "0.3.47" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -11686,22 +11571,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde_core", + "serde", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -11709,9 +11594,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -11729,9 +11614,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -11760,43 +11645,46 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "tokio" -version = "1.49.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ + "backtrace", "bytes", + "io-uring", "libc", - "mio 1.1.1", + "mio 1.0.4", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "slab", + "socket2 0.6.0", "tokio-macros", "tracing", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "tokio-postgres" -version = "0.7.15" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b40d66d9b2cfe04b628173409368e58247e8eddbbd3b0e6c6ba1d09f20f6c9e" +checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" dependencies = [ "async-trait", "byteorder", @@ -11807,12 +11695,12 @@ dependencies = [ "log", "parking_lot", "percent-encoding", - "phf 0.13.1", + "phf", "pin-project-lite", "postgres-protocol", "postgres-types", "rand 0.9.2", - "socket2 0.6.1", + "socket2 0.5.10", "tokio", "tokio-util", "whoami", @@ -11841,19 +11729,19 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.36", + "rustls 0.23.29", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -11863,10 +11751,12 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" dependencies = [ + "async-stream", + "bytes", "futures-core", "tokio", "tokio-stream", @@ -11901,14 +11791,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", "futures-sink", "futures-util", + "hashbrown 0.15.4", "pin-project-lite", "slab", "tokio", @@ -11935,7 +11826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e04c1865c281139e5ccf633cb9f76ffdaabeebfe53b703984cf82878e2aabb" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -11955,8 +11846,8 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", + "toml_datetime", + "toml_edit", ] [[package]] @@ -11968,50 +11859,20 @@ 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.13.0", + "indexmap 2.10.0", "serde", "serde_spanned", - "toml_datetime 0.6.11", + "toml_datetime", "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" @@ -12026,19 +11887,19 @@ checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", - "axum", + "axum 0.7.9", "base64 0.22.1", "bytes", - "h2 0.4.13", - "http 1.4.0", + "h2 0.4.11", + "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.6.0", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", - "prost", + "prost 0.13.5", "socket2 0.5.10", "tokio", "tokio-stream", @@ -12057,16 +11918,16 @@ dependencies = [ "async-trait", "base64 0.22.1", "bytes", - "h2 0.4.13", - "http 1.4.0", + "h2 0.4.11", + "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.6.0", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", - "prost", + "prost 0.13.5", "socket2 0.5.10", "tokio", "tokio-stream", @@ -12076,6 +11937,48 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0" +dependencies = [ + "async-trait", + "axum 0.8.8", + "base64 0.22.1", + "bytes", + "h2 0.4.11", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "rustls-native-certs 0.8.3", + "socket2 0.6.0", + "sync_wrapper 1.0.2", + "tokio", + "tokio-rustls 0.26.2", + "tokio-stream", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f" +dependencies = [ + "bytes", + "prost 0.14.3", + "tonic 0.14.4", +] + [[package]] name = "tower" version = "0.4.13" @@ -12104,7 +12007,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.13.0", + "indexmap 2.10.0", "pin-project-lite", "slab", "sync_wrapper 1.0.2", @@ -12122,11 +12025,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", - "bitflags 2.10.0", + "bitflags 2.9.1", "bytes", "futures-core", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "http-range-header", @@ -12176,7 +12079,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -12189,21 +12092,11 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-indicatif" -version = "0.3.14" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ef6990e0438749f0080573248e96631171a0b5ddfddde119aa5ba8c3a9c47e" +checksum = "8c714cc8fc46db04fcfddbd274c6ef59bebb1b435155984e7c6e89c3ce66f200" dependencies = [ "indicatif", "tracing", @@ -12211,17 +12104,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -12235,16 +12117,18 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.19.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00a39dcf9bfc1742fa4d6215253b33a6e474be78275884c216fc2a06267b3600" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" dependencies = [ - "once_cell", + "js-sys", "opentelemetry", + "smallvec", "tracing", "tracing-core", - "tracing-log 0.1.4", + "tracing-log", "tracing-subscriber", + "web-time", ] [[package]] @@ -12254,7 +12138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", - "nu-ansi-term 0.50.3", + "nu-ansi-term", "once_cell", "regex-automata", "sharded-slab", @@ -12262,7 +12146,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", ] [[package]] @@ -12283,19 +12167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.114", -] - -[[package]] -name = "tracing-tree" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ec6adcab41b1391b08a308cc6302b79f8095d1673f6947c2dc65ffb028b0b2d" -dependencies = [ - "nu-ansi-term 0.46.0", - "tracing-core", - "tracing-log 0.1.4", - "tracing-subscriber", + "syn 2.0.106", ] [[package]] @@ -12340,7 +12212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e640d9b0964e9d39df633548591090ab92f7a4567bc31d3891af23471a3365c6" dependencies = [ "lazy_static", - "thiserror 2.0.17", + "thiserror 2.0.12", "ts-rs-macros", ] @@ -12367,7 +12239,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", "termcolor", ] @@ -12394,7 +12266,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -12427,7 +12299,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.4.0", + "http 1.3.1", "httparse", "log", "rand 0.8.5", @@ -12441,29 +12313,29 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.23.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" +checksum = "0d0dd654273fc253fde1df4172c31fb6615cf8b041d3a4008a028ef8b1119e66" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.23.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" +checksum = "016c26257f448222014296978b2c8456e2cad4de308c35bdb1e383acd569ef5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" @@ -12473,9 +12345,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" -version = "2.9.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" @@ -12485,24 +12357,24 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" -version = "0.1.25" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-segmentation" @@ -12518,9 +12390,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" @@ -12530,117 +12402,117 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uniffi" -version = "0.29.5" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f" +checksum = "b334fd69b3cf198b63616c096aabf9820ab21ed9b2aa1367ddd4b411068bf520" dependencies = [ "anyhow", "camino", "cargo_metadata 0.19.2", "clap", - "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", + "uniffi_bindgen 0.29.3", + "uniffi_build 0.29.3", + "uniffi_core 0.29.3", + "uniffi_macros 0.29.3", + "uniffi_pipeline 0.29.3", ] [[package]] name = "uniffi" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c866f627c3f04c3df068b68bb2d725492caaa539dd313e2a9d26bb85b1a32f4e" +checksum = "b8c6dec3fc6645f71a16a3fa9ff57991028153bd194ca97f4b55e610c73ce66a" 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", + "uniffi_bindgen 0.31.0", + "uniffi_build 0.31.0", + "uniffi_core 0.31.0", + "uniffi_macros 0.31.0", + "uniffi_pipeline 0.31.0", ] [[package]] name = "uniffi_bindgen" -version = "0.29.5" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04b99fa7796eaaa7b87976a0dbdd1178dc1ee702ea00aca2642003aef9b669e" +checksum = "2ff0132b533483cf19abb30bba5c72c24d9f3e4d9a2ff71cb3e22e73899fd46e" dependencies = [ "anyhow", - "askama", + "askama 0.13.1", "camino", "cargo_metadata 0.19.2", "fs-err", "glob", "goblin", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.10.0", "once_cell", "serde", "tempfile", "textwrap", "toml 0.5.11", - "uniffi_internal_macros 0.29.5", - "uniffi_meta 0.29.5", - "uniffi_pipeline 0.29.5", - "uniffi_udl 0.29.5", + "uniffi_internal_macros 0.29.3", + "uniffi_meta 0.29.3", + "uniffi_pipeline 0.29.3", + "uniffi_udl 0.29.3", ] [[package]] name = "uniffi_bindgen" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c8ca600167641ebe7c8ba9254af40492dda3397c528cc3b2f511bd23e8541a5" +checksum = "4ed0150801958d4825da56a41c71f000a457ac3a4613fa9647df78ac4b6b6881" dependencies = [ "anyhow", - "askama", + "askama 0.14.0", "camino", "cargo_metadata 0.19.2", "fs-err", "glob", "goblin", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.10.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", + "uniffi_internal_macros 0.31.0", + "uniffi_meta 0.31.0", + "uniffi_pipeline 0.31.0", + "uniffi_udl 0.31.0", ] [[package]] name = "uniffi_build" -version = "0.29.5" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025a05cba02ee22b6624ac3d257e59c7395319ea8fe1aae33a7cdb4e2a3016cc" +checksum = "0d84d607076008df3c32dd2100ee4e727269f11d3faa35691af70d144598f666" dependencies = [ "anyhow", "camino", - "uniffi_bindgen 0.29.5", + "uniffi_bindgen 0.29.3", ] [[package]] name = "uniffi_build" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e55c05228f4858bb258f651d21d743fcc1fe5a2ec20d3c0f9daefddb105ee4d" +checksum = "b78fd9271a4c2e85bd2c266c5a9ede1fac676eb39fd77f636c27eaf67426fd5f" dependencies = [ "anyhow", "camino", - "uniffi_bindgen 0.30.0", + "uniffi_bindgen 0.31.0", ] [[package]] name = "uniffi_core" -version = "0.29.5" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38a9a27529ccff732f8efddb831b65b1e07f7dea3fd4cacd4a35a8c4b253b98" +checksum = "53e3b997192dc15ef1778c842001811ec7f241a093a693ac864e1fc938e64fa9" dependencies = [ "anyhow", "bytes", @@ -12650,9 +12522,9 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7a5a038ebffe8f4cf91416b154ef3c2468b18e828b7009e01b1b99938089f9" +checksum = "b0ef62e69762fbb9386dcb6c87cd3dd05d525fa8a3a579a290892e60ddbda47e" dependencies = [ "anyhow", "bytes", @@ -12662,35 +12534,35 @@ dependencies = [ [[package]] name = "uniffi_internal_macros" -version = "0.29.5" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09acd2ce09c777dd65ee97c251d33c8a972afc04873f1e3b21eb3492ade16933" +checksum = "f64bec2f3a33f2f08df8150e67fa45ba59a2ca740bf20c1beb010d4d791f9a1b" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.10.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "uniffi_internal_macros" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c2a6f93e7b73726e2015696ece25ca0ac5a5f1cf8d6a7ab5214dd0a01d2edf" +checksum = "98f51ebca0d9a4b2aa6c644d5ede45c56f73906b96403c08a1985e75ccb64a01" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.10.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "uniffi_macros" -version = "0.29.5" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5596f178c4f7aafa1a501c4e0b96236a96bc2ef92bdb453d83e609dad0040152" +checksum = "5d8708716d2582e4f3d7e9f320290b5966eb951ca421d7630571183615453efc" dependencies = [ "camino", "fs-err", @@ -12698,16 +12570,16 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.114", + "syn 2.0.106", "toml 0.5.11", - "uniffi_meta 0.29.5", + "uniffi_meta 0.29.3", ] [[package]] name = "uniffi_macros" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c6309fc36c7992afc03bc0c5b059c656bccbef3f2a4bc362980017f8936141" +checksum = "db9d12529f1223d014fd501e5f29ca0884d15d6ed5ddddd9f506e55350327dc3" dependencies = [ "camino", "fs-err", @@ -12715,90 +12587,90 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.114", + "syn 2.0.106", "toml 0.8.23", - "uniffi_meta 0.30.0", + "uniffi_meta 0.31.0", ] [[package]] name = "uniffi_meta" -version = "0.29.5" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beadc1f460eb2e209263c49c4f5b19e9a02e00a3b2b393f78ad10d766346ecff" +checksum = "3d226fc167754ce548c5ece9828c8a06f03bf1eea525d2659ba6bd648bd8e2f3" dependencies = [ "anyhow", "siphasher 0.3.11", - "uniffi_internal_macros 0.29.5", - "uniffi_pipeline 0.29.5", + "uniffi_internal_macros 0.29.3", + "uniffi_pipeline 0.29.3", ] [[package]] name = "uniffi_meta" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a138823392dba19b0aa494872689f97d0ee157de5852e2bec157ce6de9cdc22" +checksum = "9df6d413db2827c68588f8149d30d49b71d540d46539e435b23a7f7dbd4d4f86" dependencies = [ "anyhow", - "siphasher 0.3.11", - "uniffi_internal_macros 0.30.0", - "uniffi_pipeline 0.30.0", + "siphasher 1.0.1", + "uniffi_internal_macros 0.31.0", + "uniffi_pipeline 0.31.0", ] [[package]] name = "uniffi_pipeline" -version = "0.29.5" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd76b3ac8a2d964ca9fce7df21c755afb4c77b054a85ad7a029ad179cc5abb8a" +checksum = "b925b6421df15cf4bedee27714022cd9626fb4d7eee0923522a608b274ba4371" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.10.0", "tempfile", - "uniffi_internal_macros 0.29.5", + "uniffi_internal_macros 0.29.3", ] [[package]] name = "uniffi_pipeline" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c27c4b515d25f8e53cc918e238c39a79c3144a40eaf2e51c4a7958973422c29" +checksum = "a806dddc8208f22efd7e95a5cdf88ed43d0f3271e8f63b47e757a8bbdb43b63a" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.10.0", "tempfile", - "uniffi_internal_macros 0.30.0", + "uniffi_internal_macros 0.31.0", ] [[package]] name = "uniffi_udl" -version = "0.29.5" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319cf905911d70d5b97ce0f46f101619a22e9a189c8c46d797a9955e9233716" +checksum = "9c42649b721df759d9d4692a376b82b62ce3028ec9fc466f4780fb8cdf728996" dependencies = [ "anyhow", "textwrap", - "uniffi_meta 0.29.5", + "uniffi_meta 0.29.3", "weedle2", ] [[package]] name = "uniffi_udl" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0adacdd848aeed7af4f5af7d2f621d5e82531325d405e29463482becfdeafca" +checksum = "0d1a7339539bf6f6fa3e9b534dece13f778bda2d54b1a6d4e40b4d6090ac26e7" dependencies = [ "anyhow", "textwrap", - "uniffi_meta 0.30.0", + "uniffi_meta 0.31.0", "weedle2", ] [[package]] name = "unit-prefix" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" [[package]] name = "universal-hash" @@ -12806,7 +12678,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common 0.1.7", + "crypto-common", "subtle 2.6.1", ] @@ -12824,23 +12696,16 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.8" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", - "serde_derive", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf-8" version = "0.7.6" @@ -12865,7 +12730,7 @@ version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.10.0", "serde", "serde_json", "utoipa-gen", @@ -12880,7 +12745,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.114", + "syn 2.0.106", "uuid", ] @@ -12890,7 +12755,7 @@ version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db4b5ac679cc6dfc5ea3f2823b0291c777750ffd5e13b21137e0f7ac0e8f9617" dependencies = [ - "axum", + "axum 0.7.9", "base64 0.22.1", "mime_guess", "regex", @@ -12919,7 +12784,7 @@ checksum = "268d76aaebb80eba79240b805972e52d7d410d4bcc52321b951318b0f440cd60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -12930,17 +12795,17 @@ checksum = "382673bda1d05c85b4550d32fd4192ccd4cffe9a908543a0795d1e7682b36246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", "utoipauto-core", ] [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -13041,30 +12906,36 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vt100" -version = "0.16.2" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ff75fb8fa83e609e685106df4faeffdf3a735d3c74ebce97ec557d5d36fd9" +checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de" dependencies = [ "itoa", - "unicode-width 0.2.2", + "log", + "unicode-width 0.1.14", "vte", ] [[package]] name = "vte" -version = "0.15.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" dependencies = [ "arrayvec", - "memchr", + "utf8parse", + "vte_generate_state_changes", ] [[package]] -name = "waker-fn" -version = "1.2.0" +name = "vte_generate_state_changes" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" +checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" +dependencies = [ + "proc-macro2", + "quote", +] [[package]] name = "walkdir" @@ -13092,10 +12963,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" +name = "wasi" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] @@ -13112,27 +13001,40 @@ version = "0.12.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" dependencies = [ - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 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.56" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -13143,9 +13045,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -13153,42 +13055,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ - "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", + "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.56" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" 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", @@ -13196,13 +13090,35 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.56" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.10.0", + "wasm-encoder", + "wasmparser", ] [[package]] @@ -13219,10 +13135,22 @@ dependencies = [ ] [[package]] -name = "wasmtimer" -version = "0.4.3" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.9.1", + "hashbrown 0.15.4", + "indexmap 2.10.0", + "semver 1.0.26", +] + +[[package]] +name = "wasmtimer" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" dependencies = [ "futures", "js-sys", @@ -13234,9 +13162,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -13258,7 +13186,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ - "phf 0.11.3", + "phf", "phf_codegen", "string_cache", "string_cache_codegen", @@ -13266,9 +13194,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ "rustls-pki-types", ] @@ -13294,14 +13222,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.5", + "webpki-roots 1.0.2", ] [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -13317,11 +13245,11 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ - "libredox", + "redox_syscall", "wasite", "web-sys", ] @@ -13334,9 +13262,9 @@ checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" [[package]] name = "widestring" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -13356,11 +13284,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.11" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -13468,7 +13396,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -13479,7 +13407,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -13592,16 +13520,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "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", + "windows-targets 0.53.2", ] [[package]] @@ -13652,19 +13571,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.5" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ - "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", + "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", ] [[package]] @@ -13705,9 +13623,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" @@ -13729,9 +13647,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" @@ -13753,9 +13671,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" @@ -13765,9 +13683,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" @@ -13789,9 +13707,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" @@ -13813,9 +13731,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" @@ -13837,9 +13755,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" @@ -13861,15 +13779,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -13890,7 +13808,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22b4dbcc6c93786cf22e420ef96e8976bfb92a455070282302b74de5848191f4" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.9.1", "getrandom 0.2.16", "ipnet", "libloading", @@ -13902,15 +13820,106 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.10.0", + "prettyplease", + "syn 2.0.106", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.106", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.9.1", + "indexmap 2.10.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.10.0", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wyz" @@ -13935,12 +13944,12 @@ dependencies = [ [[package]] name = "xattr" -version = "1.6.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", - "rustix", + "rustix 1.0.8", ] [[package]] @@ -13951,10 +13960,11 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ + "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -13962,34 +13972,34 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -14009,35 +14019,35 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", @@ -14046,9 +14056,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -14057,13 +14067,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.106", ] [[package]] @@ -14077,9 +14087,9 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.13.0", + "indexmap 2.10.0", "memchr", - "thiserror 2.0.17", + "thiserror 2.0.12", "zopfli", ] @@ -14087,22 +14097,14 @@ dependencies = [ name = "zknym-lib" version = "1.20.4" dependencies = [ - "anyhow", "async-trait", - "bs58", - "getrandom 0.2.16", "js-sys", "nym-bin-common", "nym-compact-ecash", - "nym-credentials", - "nym-crypto", "nym-http-api-client", "nym-wasm-utils", - "rand 0.8.5", - "reqwest 0.13.1", "serde", - "thiserror 2.0.17", - "tokio", + "thiserror 2.0.12", "tsify", "uuid", "wasm-bindgen", @@ -14110,17 +14112,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "zmij" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" - [[package]] name = "zopfli" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" dependencies = [ "bumpalo", "crc32fast", @@ -14148,9 +14144,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", @@ -14166,7 +14162,7 @@ dependencies = [ "reqwest 0.13.1", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.12", "tokio", "tracing", "url", diff --git a/Cargo.toml b/Cargo.toml index 5d4ba68b35..4f7ebfae70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,6 @@ members = [ "common/nym-id", "common/nym-kcp", "common/nym-lp", - "common/nym-lp-common", "common/nym-kkt", "common/nym-metrics", "common/nym_offline_compact_ecash", @@ -129,7 +128,6 @@ members = [ "nym-browser-extension/storage", "nym-credential-proxy/nym-credential-proxy", "nym-credential-proxy/nym-credential-proxy-requests", - "nym-credential-proxy/vpn-api-lib-wasm", "nym-data-observatory", "nym-ip-packet-client", "nym-network-monitor", @@ -173,8 +171,9 @@ members = [ "wasm/mix-fetch", "wasm/node-tester", "wasm/zknym-lib", - "nym-gateway-probe", - "integration-tests", "common/nym-lp-transport", "common/nym-kkt-ciphersuite", + # "nym-gateway-probe", + "integration-tests", + "common/nym-kkt-ciphersuite", "common/nym-kkt-context", ] default-members = [ @@ -274,6 +273,7 @@ futures = "0.3.31" futures-util = "0.3" generic-array = "0.14.7" getrandom = "0.2.10" +getrandom03 = { package = "getrandom", version = "=0.3.3" } glob = "0.3" handlebars = "3.5.5" hex = "0.4.3" @@ -309,8 +309,10 @@ nix = "0.30.1" notify = "5.1.0" num_enum = "0.7.5" once_cell = "1.21.3" -opentelemetry = "0.19.0" -opentelemetry-jaeger = "0.18.0" +opentelemetry = "0.31.0" +opentelemetry_sdk = "0.31.0" +opentelemetry-otlp = "0.31.0" +tonic = "0.14.4" parking_lot = "0.12.3" pem = "0.8" petgraph = "0.6.5" @@ -322,6 +324,7 @@ quote = "1" rand = "0.8.5" rand09 = { package = "rand", version = "=0.9.2" } rand_chacha = "0.3" +rand_chacha09 = { package = "rand_chacha", version = "=0.9.0" } rand_core = "0.6.3" rand_distr = "0.4" rayon = "1.5.1" @@ -368,9 +371,8 @@ tower = "0.5.2" tower-http = "0.6.6" tracing = "0.1.41" tracing-log = "0.2" -tracing-opentelemetry = "0.19.0" +tracing-opentelemetry = "0.32.1" tracing-subscriber = "0.3.20" -tracing-tree = "0.2.2" tracing-indicatif = "0.3.9" tracing-test = "0.2.5" ts-rs = "10.1.0" @@ -391,6 +393,17 @@ zeroize = "1.7.0" prometheus = { version = "0.14.0" } + +# libcrux +libcrux-kem = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" } +libcrux-ecdh = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" } +libcrux-curve25519 = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" } +libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" } +libcrux-psq = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" } +libcrux-ml-kem = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" } +libcrux-sha3 = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" } +libcrux-traits = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" } + # 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.4", path = "nym-api/nym-api-requests" } nym-authenticator-requests = { version = "1.20.4", path = "common/authenticator-requests" } @@ -435,7 +448,8 @@ nym-http-api-common = { version = "1.20.4", path = "common/http-api-common", def nym-id = { version = "1.20.4", path = "common/nym-id" } nym-ip-packet-client = { version = "1.20.4", path = "nym-ip-packet-client" } nym-ip-packet-requests = { version = "1.20.4", path = "common/ip-packet-requests" } -nym-kkt-ciphersuite = { path = "common/nym-kkt-ciphersuite" } +nym-kkt = { version = "0.1.0", path = "common/nym-kkt" } +nym-kkt-ciphersuite = { version = "1.20.4", path = "common/nym-kkt-ciphersuite" } nym-metrics = { version = "1.20.4", path = "common/nym-metrics" } nym-mixnet-client = { version = "1.20.4", path = "common/client-libs/mixnet-client" } nym-mixnet-contract-common = { version = "1.20.4", path = "common/cosmwasm-smart-contracts/mixnet-contract" } diff --git a/Makefile b/Makefile index eb58f2ab5c..b88a99d79f 100644 --- a/Makefile +++ b/Makefile @@ -104,11 +104,11 @@ $(eval $(call add_cargo_workspace,wallet,nym-wallet)) sdk-wasm: sdk-wasm-build sdk-wasm-test sdk-wasm-lint sdk-wasm-build: - $(MAKE) -C nym-browser-extension/storage wasm-pack +# $(MAKE) -C nym-browser-extension/storage wasm-pack $(MAKE) -C wasm/client $(MAKE) -C wasm/node-tester $(MAKE) -C wasm/mix-fetch - $(MAKE) -C wasm/zknym-lib +# $(MAKE) -C wasm/zknym-lib # $(MAKE) -C wasm/full-nym-wasm # run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo @@ -119,13 +119,14 @@ sdk-typescript-build: yarn --cwd sdk/typescript/codegen/contract-clients build # NOTE: These targets are part of the main workspace (but not as wasm32-unknown-unknown) -WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib +# WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib +WASM_CRATES = nym-client-wasm nym-node-tester-wasm sdk-wasm-test: #cargo test $(addprefix -p , $(WASM_CRATES)) --target wasm32-unknown-unknown -- -Dwarnings sdk-wasm-lint: - cargo clippy $(addprefix -p , $(WASM_CRATES)) --target wasm32-unknown-unknown -- -Dwarnings + RUSTFLAGS='--cfg getrandom_backend="wasm_js"' cargo clippy $(addprefix -p , $(WASM_CRATES)) --target wasm32-unknown-unknown -- -Dwarnings $(MAKE) -C wasm/mix-fetch check-fmt # Add to top-level targets diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index e0ac34bec8..912ad2f1a3 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.71" +version = "1.1.72" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/native/examples/js-examples/websocket/package-lock.json b/clients/native/examples/js-examples/websocket/package-lock.json index e30503e936..b434755265 100644 --- a/clients/native/examples/js-examples/websocket/package-lock.json +++ b/clients/native/examples/js-examples/websocket/package-lock.json @@ -513,9 +513,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3067,9 +3067,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dev": true, "dependencies": { "side-channel": "^1.1.0" @@ -4989,9 +4989,9 @@ } }, "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6870,9 +6870,9 @@ } }, "qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dev": true, "requires": { "side-channel": "^1.1.0" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index e1bded672c..ab8edde745 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.71" +version = "1.1.72" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/common/bin-common/Cargo.toml b/common/bin-common/Cargo.toml index 8220494794..b10a29b84c 100644 --- a/common/bin-common/Cargo.toml +++ b/common/bin-common/Cargo.toml @@ -19,12 +19,15 @@ serde_json = { workspace = true, optional = true } ## tracing tracing-subscriber = { workspace = true, features = ["env-filter"], optional = true } -tracing-tree = { workspace = true, optional = true } tracing = { workspace = true, optional = true } -opentelemetry-jaeger = { workspace = true, features = ["rt-tokio", "collector_client", "isahc_collector_client"], optional = true } tracing-opentelemetry = { workspace = true, optional = true } utoipa = { workspace = true, optional = true } -opentelemetry = { workspace = true, features = ["rt-tokio"], optional = true } +opentelemetry = { workspace = true, features = ["trace"], optional = true } + +## otel-otlp (modern OTLP export to SigNoz/any OTLP collector) +opentelemetry_sdk = { workspace = true, features = ["trace"], optional = true } +opentelemetry-otlp = { workspace = true, features = ["grpc-tonic", "trace", "tls-roots"], optional = true } +tonic = { workspace = true, optional = true } [build-dependencies] @@ -35,13 +38,14 @@ default = [] openapi = ["utoipa"] output_format = ["serde_json", "dep:clap"] bin_info_schema = ["schemars"] -basic_tracing = ["dep:tracing", "tracing-subscriber"] -tracing = [ +basic_tracing = ["dep:tracing", "dep:tracing-subscriber"] +otel-otlp = [ "basic_tracing", - "tracing-tree", - "opentelemetry-jaeger", - "tracing-opentelemetry", - "opentelemetry", + "dep:opentelemetry", + "dep:opentelemetry_sdk", + "dep:opentelemetry-otlp", + "dep:tracing-opentelemetry", + "dep:tonic", ] clap = ["dep:clap", "dep:clap_complete", "dep:clap_complete_fig"] models = [] diff --git a/common/bin-common/src/logging/mod.rs b/common/bin-common/src/logging/mod.rs index 20d2e9e274..70b2f76a54 100644 --- a/common/bin-common/src/logging/mod.rs +++ b/common/bin-common/src/logging/mod.rs @@ -4,16 +4,9 @@ use serde::{Deserialize, Serialize}; use std::io::IsTerminal; -#[cfg(feature = "tracing")] -pub use opentelemetry; -#[cfg(feature = "tracing")] -pub use opentelemetry_jaeger; -#[cfg(feature = "tracing")] -pub use tracing_opentelemetry; +// Re-export tracing_subscriber for consumers that need to compose layers #[cfg(feature = "basic_tracing")] pub use tracing_subscriber; -#[cfg(feature = "tracing")] -pub use tracing_tree; #[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -69,40 +62,106 @@ pub fn setup_tracing_logger() { build_tracing_logger().init() } -// TODO: This has to be a macro, running it as a function does not work for the file_appender for some reason -#[cfg(feature = "tracing")] -#[macro_export] -macro_rules! setup_tracing { - ($service_name: expr) => { - use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt; - use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt; +/// Initialize an OpenTelemetry tracing layer that exports spans via OTLP/gRPC. +/// +/// This produces a layer compatible with `tracing_subscriber::registry()` that +/// sends traces to any OTLP-compatible collector (SigNoz, Grafana Tempo, etc). +/// +/// Returns both the tracing layer and the [`SdkTracerProvider`] so the caller +/// can invoke [`SdkTracerProvider::shutdown`] for graceful flush on exit. +/// +/// # Arguments +/// * `service_name` - The service name reported to the collector (e.g. "nym-node") +/// * `endpoint` - The OTLP/gRPC collector endpoint (e.g. "http://localhost:4317" +/// or "https://ingest.eu.signoz.cloud:443" for SigNoz Cloud) +/// * `ingestion_key` - Optional SigNoz Cloud ingestion key. When provided, it is +/// sent as the `signoz-ingestion-key` gRPC metadata header on every export. +/// * `environment` - Deployment environment label (e.g. "sandbox", "mainnet", "canary"). +/// Attached as the `deployment.environment` OTel resource attribute. +/// * `sample_ratio` - Trace sampling ratio in 0.0..=1.0 (e.g. 0.1 = 10% of traces). +/// Used to limit cost when exporting from many nodes; clamped to [0.0, 1.0]. +/// * `export_timeout_secs` - Timeout in seconds for each OTLP export batch. Prevents +/// unbounded blocking if the collector is slow or unreachable. +#[cfg(feature = "otel-otlp")] +pub fn init_otel_layer( + service_name: &str, + endpoint: &str, + ingestion_key: Option<&str>, + environment: &str, + sample_ratio: f64, + export_timeout_secs: u64, +) -> Result< + ( + tracing_opentelemetry::OpenTelemetryLayer, + opentelemetry_sdk::trace::SdkTracerProvider, + ), + Box, +> +where + S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, +{ + use opentelemetry::trace::TracerProvider as _; + use opentelemetry_otlp::WithExportConfig; + use opentelemetry_otlp::WithTonicConfig; + use opentelemetry_sdk::trace::Sampler; + use std::time::Duration; - let registry = nym_bin_common::logging::tracing_subscriber::Registry::default() - .with(nym_bin_common::logging::tracing_subscriber::EnvFilter::from_default_env()) - .with( - nym_bin_common::logging::tracing_tree::HierarchicalLayer::new(4) - .with_targets(true) - .with_bracketed_fields(true), - ); + // Validate endpoint URI early to fail with a clear message + if !endpoint.starts_with("http://") && !endpoint.starts_with("https://") { + return Err(format!( + "invalid OTLP endpoint URI: {endpoint} (must start with http:// or https://)" + ) + .into()); + } - let tracer = nym_bin_common::logging::opentelemetry_jaeger::new_collector_pipeline() - .with_endpoint("http://44.199.230.10:14268/api/traces") - .with_service_name($service_name) - .with_isahc() - .with_trace_config( - nym_bin_common::logging::opentelemetry::sdk::trace::config().with_sampler( - nym_bin_common::logging::opentelemetry::sdk::trace::Sampler::TraceIdRatioBased( - 0.1, - ), - ), - ) - .install_batch(nym_bin_common::logging::opentelemetry::runtime::Tokio) - .expect("Could not init tracer"); + let sample_ratio_clamped = sample_ratio.clamp(0.0, 1.0); - let telemetry = nym_bin_common::logging::tracing_opentelemetry::layer().with_tracer(tracer); + let mut builder = opentelemetry_otlp::SpanExporter::builder() + .with_tonic() + .with_endpoint(endpoint) + .with_timeout(Duration::from_secs(export_timeout_secs)); - registry.with(telemetry).init(); - }; + // Explicitly configure TLS when the endpoint uses HTTPS + if endpoint.starts_with("https://") { + builder = + builder.with_tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots()); + } + + if let Some(key) = ingestion_key { + let mut metadata = tonic::metadata::MetadataMap::new(); + metadata.insert( + "signoz-ingestion-key", + key.parse() + .map_err(|_| "invalid ingestion key format (value redacted)")?, + ); + builder = builder.with_metadata(metadata); + } + + let exporter = builder + .build() + .map_err(|e| format!("failed to build OTLP exporter for endpoint {endpoint}: {e}"))?; + + let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder() + .with_sampler(Sampler::TraceIdRatioBased(sample_ratio_clamped)) + .with_batch_exporter(exporter) + .with_resource( + opentelemetry_sdk::Resource::builder() + .with_service_name(service_name.to_owned()) + .with_attribute(opentelemetry::KeyValue::new( + "deployment.environment", + environment.to_owned(), + )) + .build(), + ) + .build(); + + opentelemetry::global::set_tracer_provider(tracer_provider.clone()); + let tracer = tracer_provider.tracer(service_name.to_owned()); + + Ok(( + tracing_opentelemetry::layer().with_tracer(tracer), + tracer_provider, + )) } pub fn banner(crate_name: &str, crate_version: &str) -> String { diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index 77cdf19c98..c6275532e6 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -121,6 +121,10 @@ features = ["wasm-bindgen"] workspace = true features = ["full"] +[target."cfg(target_arch = \"wasm32\")".dependencies.getrandom03] +workspace = true +features = ["wasm_js"] + [dev-dependencies] tempfile = { workspace = true } diff --git a/common/client-core/src/client/helpers/wasm.rs b/common/client-core/src/client/helpers/wasm.rs index 59f1ec9e75..ef04028396 100644 --- a/common/client-core/src/client/helpers/wasm.rs +++ b/common/client-core/src/client/helpers/wasm.rs @@ -15,3 +15,13 @@ pub(crate) fn get_time_now() -> Instant { pub(crate) fn new_interval_stream(polling_rate: Duration) -> IntervalStream { gloo_timers::future::IntervalStream::new(polling_rate.as_millis() as u32) } + +#[unsafe(no_mangle)] +unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), getrandom03::Error> { + let _ = dest; + let _ = len; + Err(getrandom03::Error::UNSUPPORTED) +} diff --git a/common/client-libs/mixnet-client/src/client.rs b/common/client-libs/mixnet-client/src/client.rs index 5f001810bd..ffd4d025e1 100644 --- a/common/client-libs/mixnet-client/src/client.rs +++ b/common/client-libs/mixnet-client/src/client.rs @@ -128,54 +128,95 @@ impl ManagedConnection { async fn run(self) { let address = self.address; + let reconnection_attempt = self.current_reconnection.load(Ordering::Acquire); + let connect_start = tokio::time::Instant::now(); let connection_fut = TcpStream::connect(address); let conn = match tokio::time::timeout(self.connection_timeout, connection_fut).await { Ok(stream_res) => match stream_res { Ok(stream) => { - debug!("Managed to establish connection to {}", self.address); + let connect_ms = connect_start.elapsed().as_millis() as u64; + debug!( + peer = %address, + connect_ms, + "Managed to establish connection to {}", self.address + ); + let noise_start = tokio::time::Instant::now(); let noise_stream = match upgrade_noise_initiator(stream, &self.noise_config).await { Ok(noise_stream) => noise_stream, Err(err) => { - error!("Failed to perform Noise handshake with {address} - {err}"); - // we failed to finish the noise handshake - increase reconnection attempt + let noise_handshake_ms = noise_start.elapsed().as_millis() as u64; + warn!( + event = "connection.failed.noise", + peer = %address, + error = %err, + connect_ms, + noise_handshake_ms, + reconnection_attempt, + exit_reason = "noise_error", + "Failed to perform Noise initiator handshake with {address}" + ); self.current_reconnection.fetch_add(1, Ordering::SeqCst); return; } }; - // if we managed to connect AND do the noise handshake, reset the reconnection count (whatever it might have been) + let noise_handshake_ms = noise_start.elapsed().as_millis() as u64; self.current_reconnection.store(0, Ordering::Release); - debug!("Noise initiator handshake completed for {:?}", address); + debug!( + peer = %address, + connect_ms, + noise_handshake_ms, + "Noise initiator handshake completed for {:?}", address + ); Framed::new(noise_stream, NymCodec) } Err(err) => { - debug!("failed to establish connection to {address} (err: {err})",); + let connect_ms = connect_start.elapsed().as_millis() as u64; + warn!( + event = "connection.failed.connect", + peer = %address, + error = %err, + connect_ms, + reconnection_attempt, + exit_reason = "connect_error", + "failed to establish connection to {address}" + ); return; } }, Err(_) => { - debug!( + let connect_ms = connect_start.elapsed().as_millis() as u64; + warn!( + event = "connection.failed.timeout", + peer = %address, + timeout_ms = self.connection_timeout.as_millis() as u64, + connect_ms, + reconnection_attempt, + exit_reason = "timeout", "failed to connect to {address} within {:?}", self.connection_timeout ); - - // we failed to connect - increase reconnection attempt self.current_reconnection.fetch_add(1, Ordering::SeqCst); return; } }; - // Take whatever the receiver channel produces and put it on the connection. - // We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care - // about neither receiver nor the connection, it doesn't matter which one gets consumed if let Err(err) = self.message_receiver.map(Ok).forward(conn).await { - warn!("Failed to forward packets to {address}: {err}"); + warn!( + event = "connection.forward_error", + peer = %address, + error = %err, + exit_reason = "forward_error", + "Failed to forward packets to {address}: {err}" + ); } debug!( - "connection manager to {address} is finished. Either the connection failed or mixnet client got dropped", + peer = %address, + exit_reason = "sender_dropped", + "connection manager to {address} finished" ); } } @@ -272,16 +313,18 @@ impl SendWithoutResponse for Client { trace!("Sending packet to {address}"); // TODO: optimisation for the future: rather than constantly using legacy encoding, - // once we're addressing by node_id (and thus have full node info here), - // we could simply infer supported encoding based on their version + // use the mix packet type / flags to pick encoding per packet let framed_packet = FramedNymPacket::from_mix_packet(packet, self.config.use_legacy_packet_encoding); let Some(sender) = self.active_connections.get_mut(&address) else { // there was never a connection to begin with - debug!("establishing initial connection to {address}"); - // it's not a 'big' error, but we did not manage to send the packet, but queue the packet - // for sending for as soon as the connection is created + debug!( + event = "mixclient.try_send", + peer = %address, + result = "not_connected", + "establishing initial connection to {address}" + ); self.make_connection(address, framed_packet); return Err(io::Error::new( io::ErrorKind::NotConnected, @@ -289,15 +332,24 @@ impl SendWithoutResponse for Client { )); }; + let channel_capacity = sender.channel.max_capacity(); + let channel_available = sender.channel.capacity(); + let channel_used = channel_capacity - channel_available; + let sending_res = sender.channel.try_send(framed_packet); drop(sender); sending_res.map_err(|err| { match err { TrySendError::Full(_) => { - debug!("Connection to {address} seems to not be able to handle all the traffic - dropping the current packet"); - // it's not a 'big' error, but we did not manage to send the packet - // if the queue is full, we can't really do anything but to drop the packet + warn!( + event = "mixclient.try_send", + peer = %address, + result = "full_dropped", + channel_capacity, + channel_used, + "dropping packet: connection buffer to {address} is full ({channel_used}/{channel_capacity})" + ); io::Error::new( io::ErrorKind::WouldBlock, "connection queue is full", @@ -305,11 +357,13 @@ impl SendWithoutResponse for Client { } TrySendError::Closed(dropped) => { debug!( - "Connection to {address} seems to be dead. attempting to re-establish it...", + event = "mixclient.try_send", + peer = %address, + result = "closed_reconnecting", + channel_capacity, + channel_used, + "connection to {address} dead, attempting re-establishment" ); - - // it's not a 'big' error, but we did not manage to send the packet, but queue - // it up to send it as soon as the connection is re-established self.make_connection(address, dropped); io::Error::new( io::ErrorKind::ConnectionAborted, diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index d461157f52..43ab5808a7 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, NymNodeDescriptionV1, + MixnodeCoreStatusResponse, NymNodeDescriptionV1, NymNodeDescriptionV2, }; use nym_api_requests::nym_nodes::{ NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNode, SkimmedNodesWithMetadata, @@ -273,18 +273,18 @@ impl Client { Ok(history) } - // #[deprecated(note = "use get_all_cached_described_nodes_v2 instead")] + #[deprecated(note = "use get_all_cached_described_nodes_v2 instead")] pub async fn get_all_cached_described_nodes( &self, ) -> Result, ValidatorClientError> { Ok(self.nym_api.get_all_described_nodes().await?) } - // 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_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, @@ -473,7 +473,7 @@ impl NymApiClient { Ok(self.nym_api.health().await?) } - // #[deprecated(note = "use .get_all_described_nodes_v2 instead")] + #[deprecated(note = "use .get_all_described_nodes_v2 instead")] pub async fn get_all_described_nodes( &self, ) -> Result, ValidatorClientError> { @@ -495,29 +495,29 @@ 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_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, 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 08bcd7e7f3..cf71f706a4 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,7 @@ use nym_api_requests::ecash::VerificationKeyResponse; use nym_api_requests::models::{ AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainBlocksStatusResponse, ChainStatusResponse, KeyRotationInfoResponse, NodePerformanceResponse, NodeRefreshBody, - NymNodeDescriptionV1, PerformanceHistoryResponse, RewardedSetResponse, + NymNodeDescriptionV1, NymNodeDescriptionV2, PerformanceHistoryResponse, RewardedSetResponse, SignerInformationResponse, }; use nym_api_requests::nym_nodes::{ @@ -117,7 +117,7 @@ pub trait NymApiClientExt: ApiClient { } #[tracing::instrument(level = "debug", skip_all)] - // #[deprecated(note = "use .get_nodes_described_v2 instead")] + #[deprecated(note = "use .get_nodes_described_v2 instead")] async fn get_nodes_described( &self, page: Option, @@ -144,32 +144,32 @@ 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 - // } + #[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 @@ -302,8 +302,8 @@ pub trait NymApiClientExt: ApiClient { Ok(SkimmedNodesWithMetadata::new(nodes, metadata)) } - // #[deprecated(note = "use .get_all_described_nodes_v2 instead")] - // #[allow(deprecated)] + #[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; @@ -323,24 +323,24 @@ 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) - // } + async fn get_all_described_nodes_v2(&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( diff --git a/common/commands/src/validator/mixnet/query/query_all_gateways.rs b/common/commands/src/validator/mixnet/query/query_all_gateways.rs index f03fb61d28..516a533315 100644 --- a/common/commands/src/validator/mixnet/query/query_all_gateways.rs +++ b/common/commands/src/validator/mixnet/query/query_all_gateways.rs @@ -14,7 +14,7 @@ pub struct Args { } pub async fn query(args: Args, client: &QueryClientWithNyxd) { - match client.get_all_cached_described_nodes().await { + match client.get_all_cached_described_nodes_v2().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs index ef5b4a9548..b1a81780b9 100644 --- a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs +++ b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs @@ -14,7 +14,7 @@ pub struct Args { } pub async fn query(args: Args, client: &QueryClientWithNyxd) { - match client.get_all_cached_described_nodes().await { + match client.get_all_cached_described_nodes_v2().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index 1b5d62f85e..7d037f1a97 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -21,10 +21,13 @@ generic-array = { workspace = true, optional = true } hkdf = { workspace = true, optional = true } hmac = { workspace = true, optional = true } jwt-simple = { workspace = true, optional = true } +libcrux-psq = { workspace = true, optional = true } +libcrux-curve25519 = { workspace = true, optional = true } cipher = { workspace = true, optional = true } x25519-dalek = { workspace = true, features = ["static_secrets"], optional = true } ed25519-dalek = { workspace = true, features = ["rand_core"], optional = true } rand = { workspace = true, optional = true } +rand09 = { workspace = true, optional = true } serde_bytes = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } sha2 = { workspace = true, optional = true } @@ -39,17 +42,18 @@ nym-pemstore = { workspace = true } [dev-dependencies] anyhow = { workspace = true } rand_chacha = { workspace = true } -serde_json = { workspace = true } nym-test-utils = { workspace = true } +serde_json = { workspace = true } [features] default = [] aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"] naive_jwt = ["asymmetric", "jwt-simple"] +libcrux_x25519 = ["libcrux-psq", "libcrux-curve25519"] 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", "zeroize"] +hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2", "zeroize", "rand09"] stream_cipher = ["aes", "ctr", "cipher", "generic-array"] sphinx = ["nym-sphinx-types", "nym-sphinx-types/sphinx"] diff --git a/common/crypto/src/asymmetric/x25519/mod.rs b/common/crypto/src/asymmetric/x25519/mod.rs index e717df3d92..8b88f6325a 100644 --- a/common/crypto/src/asymmetric/x25519/mod.rs +++ b/common/crypto/src/asymmetric/x25519/mod.rs @@ -17,6 +17,9 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "serde")] pub mod serde_helpers; +#[cfg(feature = "libcrux_x25519")] +pub use libcrux_psq::handshake::types::{DHKeyPair, DHPrivateKey, DHPublicKey}; + /// Size of a X25519 private key pub const PRIVATE_KEY_SIZE: usize = 32; @@ -45,6 +48,9 @@ pub enum KeyRecoveryError { #[source] source: bs58::decode::Error, }, + + #[error("the x25519 private key could not be converted to its PSQ representation")] + IncompatiblePSQPrivateKey, } #[derive(Zeroize, ZeroizeOnDrop)] @@ -413,6 +419,88 @@ impl AsRef<[u8]> for PrivateKey { } } +// libcrux-psq conversion +#[cfg(feature = "libcrux_x25519")] +impl TryFrom for libcrux_psq::handshake::types::DHPrivateKey { + type Error = KeyRecoveryError; + + fn try_from( + key: PrivateKey, + ) -> Result { + Self::try_from(&key) + } +} + +#[cfg(feature = "libcrux_x25519")] +impl From for PrivateKey { + fn from(key: libcrux_psq::handshake::types::DHPrivateKey) -> PrivateKey { + // SAFETY: the DHPrivateKey is guaranteed to be 32 bytes in length + #[allow(clippy::unwrap_used)] + PrivateKey::from_bytes(key.as_ref()).unwrap() + } +} + +#[cfg(feature = "libcrux_x25519")] +impl TryFrom<&PrivateKey> for libcrux_psq::handshake::types::DHPrivateKey { + type Error = KeyRecoveryError; + + fn try_from( + key: &PrivateKey, + ) -> Result { + let mut private_key_bytes = zeroize::Zeroizing::new(key.to_bytes()); + libcrux_curve25519::clamp(&mut private_key_bytes); + match libcrux_psq::handshake::types::DHPrivateKey::from_bytes(&private_key_bytes) { + Ok(key) => Ok(key), + Err(_) => Err(KeyRecoveryError::IncompatiblePSQPrivateKey), + } + } +} + +#[cfg(feature = "libcrux_x25519")] +impl From<&libcrux_psq::handshake::types::DHPrivateKey> for PrivateKey { + fn from(key: &libcrux_psq::handshake::types::DHPrivateKey) -> PrivateKey { + // SAFETY: the DHPrivateKey is guaranteed to be 32 bytes in length + #[allow(clippy::unwrap_used)] + PrivateKey::from_bytes(key.as_ref()).unwrap() + } +} + +#[cfg(feature = "libcrux_x25519")] +impl From for libcrux_psq::handshake::types::DHPublicKey { + fn from(key: PublicKey) -> libcrux_psq::handshake::types::DHPublicKey { + libcrux_psq::handshake::types::DHPublicKey::from_bytes(key.as_bytes()) + } +} + +#[cfg(feature = "libcrux_x25519")] +impl From for PublicKey { + fn from(key: libcrux_psq::handshake::types::DHPublicKey) -> PublicKey { + // SAFETY: the DHPublicKey is guaranteed to be 32 bytes in length + #[allow(clippy::unwrap_used)] + PublicKey::from_bytes(key.as_ref()).unwrap() + } +} + +#[cfg(feature = "libcrux_x25519")] +impl TryFrom for libcrux_psq::handshake::types::DHKeyPair { + type Error = KeyRecoveryError; + + fn try_from( + key: KeyPair, + ) -> Result { + Ok(libcrux_psq::handshake::types::DHKeyPair::from( + libcrux_psq::handshake::types::DHPrivateKey::try_from(&key.private_key)?, + )) + } +} + +#[cfg(feature = "libcrux_x25519")] +impl From for KeyPair { + fn from(key: libcrux_psq::handshake::types::DHKeyPair) -> KeyPair { + KeyPair::from(PrivateKey::from(key.sk())) + } +} + #[cfg(test)] mod tests { use super::*; @@ -421,6 +509,21 @@ mod tests { fn assert_zeroize() {} + #[test] + fn test_key_conversion() { + let dalek_kp = super::KeyPair::new(&mut rand::thread_rng()); + + let mut dalek_private_key_bytes = dalek_kp.private_key().as_bytes().to_owned(); + + libcrux_curve25519::clamp(&mut dalek_private_key_bytes); + let libcrux_private_key = + libcrux_psq::handshake::types::DHPrivateKey::from_bytes(&dalek_private_key_bytes) + .unwrap(); + let libcrux_public_key = libcrux_private_key.to_public(); + + assert_eq!(libcrux_public_key.as_ref(), dalek_kp.public_key.as_bytes()); + } + #[test] fn private_key_is_zeroized() { assert_zeroize::(); diff --git a/common/crypto/src/asymmetric/x25519/serde_helpers.rs b/common/crypto/src/asymmetric/x25519/serde_helpers.rs index 02a5282cdd..14fe273f10 100644 --- a/common/crypto/src/asymmetric/x25519/serde_helpers.rs +++ b/common/crypto/src/asymmetric/x25519/serde_helpers.rs @@ -44,3 +44,25 @@ pub mod option_bs58_x25519_pubkey { } } } + +#[cfg(feature = "libcrux_x25519")] +pub mod bs58_dh_public_key { + use crate::asymmetric::x25519; + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize( + key: &libcrux_psq::handshake::types::DHPublicKey, + serializer: S, + ) -> Result { + let x25519: x25519::PublicKey = (*key).into(); + serializer.serialize_str(&x25519.to_base58_string()) + } + + pub fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let x25519 = x25519::PublicKey::from_base58_string(s).map_err(serde::de::Error::custom)?; + Ok(x25519.into()) + } +} diff --git a/common/crypto/src/hkdf.rs b/common/crypto/src/hkdf.rs index c343002430..0cd4650083 100644 --- a/common/crypto/src/hkdf.rs +++ b/common/crypto/src/hkdf.rs @@ -109,3 +109,152 @@ impl DerivationMaterial { } } } + +pub mod blake3 { + + //! Key Derivation Functions using Blake3. + + use blake3::Hasher; + + use rand09::{RngCore, rng}; + use zeroize::Zeroize; + + pub fn derive_key_blake3_multi_input( + info: &str, + input_key_material: &[&[u8]], + salt: &[u8], + ) -> [u8; 32] { + let mut hasher = Hasher::new_derive_key(info); + + for input_key in input_key_material { + hasher.update(input_key); + } + + hasher.update(salt); + + hasher.finalize().as_bytes().to_owned() + } + + /// Derives a 32-byte key using Blake3's key derivation mode. + /// + /// Uses Blake3's built-in `derive_key` function with domain separation via context string. + /// + /// # Arguments + /// * `info` - Context string for domain separation (e.g., "nym-lp-psk-v1") + /// * `input_key_material` - Input key material (shared secret from ECDH, etc.) + /// * `salt` - Additional salt for freshness (nonce) + /// + /// # Returns + /// 32-byte derived key suitable for use as PSK + /// + /// # Example + /// ```ignore + /// let psk = derive_key_blake3("nym-lp-psk-v1", shared_secret.as_bytes(), &salt); + /// ``` + pub fn derive_key_blake3(info: &str, input_key_material: &[u8], salt: &[u8]) -> [u8; 32] { + derive_key_blake3_multi_input(info, &[input_key_material], salt) + } + + pub fn derive_fresh_key_blake3_multi_input( + info: &str, + input_key_material: &[&[u8]], + ) -> [u8; 32] { + let mut salt = [0u8; 32]; + rng().fill_bytes(&mut salt); + + let derived_key = derive_key_blake3_multi_input(info, input_key_material, &salt); + + // Zeroize salt + salt.zeroize(); + + derived_key + } + + /// Derives a fresh 32-byte key using Blake3's key derivation mode. + /// The function calls a random number generator to generate a fresh salt. + /// Uses Blake3's built-in `derive_key` function with domain separation via context string. + /// + /// # Arguments + /// * `info` - Context string for domain separation (e.g., "nym-lp-psk-v1") + /// * `input_key_material` - Input key material (shared secret from ECDH, etc.) + /// + /// # Returns + /// 32-byte derived key suitable for use as PSK + /// + /// # Example + /// ```ignore + /// let psk = derive_fresh_key_blake3("nym-lp-psk-v1", shared_secret.as_bytes()); + /// ``` + pub fn derive_fresh_key_blake3(info: &str, input_key_material: &[u8]) -> [u8; 32] { + derive_fresh_key_blake3_multi_input(info, &[input_key_material]) + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_deterministic_derivation() { + let context = "test-context"; + let key_material = b"shared_secret_12345"; + let salt = b"salt_67890"; + + let key1 = derive_key_blake3(context, key_material, salt); + let key2 = derive_key_blake3(context, key_material, salt); + + assert_eq!(key1, key2, "Same inputs should produce same output"); + } + + #[test] + fn test_different_contexts_produce_different_keys() { + let key_material = b"shared_secret"; + let salt = b"salt"; + + let key1 = derive_key_blake3("context1", key_material, salt); + let key2 = derive_key_blake3("context2", key_material, salt); + + assert_ne!( + key1, key2, + "Different contexts should produce different keys" + ); + } + + #[test] + fn test_different_salts_produce_different_keys() { + let context = "test-context"; + let key_material = b"shared_secret"; + + let key1 = derive_key_blake3(context, key_material, b"salt1"); + let key2 = derive_key_blake3(context, key_material, b"salt2"); + + assert_ne!(key1, key2, "Different salts should produce different keys"); + } + + #[test] + fn test_different_key_material_produces_different_keys() { + let context = "test-context"; + let salt = b"salt"; + + let key1 = derive_key_blake3(context, b"secret1", salt); + let key2 = derive_key_blake3(context, b"secret2", salt); + + assert_ne!( + key1, key2, + "Different key material should produce different keys" + ); + } + + #[test] + fn test_output_length() { + let key = derive_key_blake3("test", b"key", b"salt"); + assert_eq!(key.len(), 32, "Output should be exactly 32 bytes"); + } + + #[test] + fn test_empty_inputs() { + // Should not panic with empty inputs + let key = derive_key_blake3("test", b"", b""); + assert_eq!(key.len(), 32); + } + } +} diff --git a/common/crypto/src/kdf.rs b/common/crypto/src/kdf.rs deleted file mode 100644 index 3edb257299..0000000000 --- a/common/crypto/src/kdf.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -//! Key Derivation Functions using Blake3. - -/// Derives a 32-byte key using Blake3's key derivation mode. -/// -/// Uses Blake3's built-in `derive_key` function with domain separation via context string. -/// -/// # Arguments -/// * `context` - Context string for domain separation (e.g., "nym-lp-psk-v1") -/// * `key_material` - Input key material (shared secret from ECDH, etc.) -/// * `salt` - Additional salt for freshness (timestamp + nonce) -/// -/// # Returns -/// 32-byte derived key suitable for use as PSK -/// -/// # Example -/// ```ignore -/// let psk = derive_key_blake3("nym-lp-psk-v1", shared_secret.as_bytes(), &salt); -/// ``` -pub fn derive_key_blake3(context: &str, key_material: &[u8], salt: &[u8]) -> [u8; 32] { - // Concatenate key_material and salt as input - let input = [key_material, salt].concat(); - - // Use Blake3's derive_key with context for domain separation - // blake3::derive_key returns [u8; 32] directly - blake3::derive_key(context, &input) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_deterministic_derivation() { - let context = "test-context"; - let key_material = b"shared_secret_12345"; - let salt = b"salt_67890"; - - let key1 = derive_key_blake3(context, key_material, salt); - let key2 = derive_key_blake3(context, key_material, salt); - - assert_eq!(key1, key2, "Same inputs should produce same output"); - } - - #[test] - fn test_different_contexts_produce_different_keys() { - let key_material = b"shared_secret"; - let salt = b"salt"; - - let key1 = derive_key_blake3("context1", key_material, salt); - let key2 = derive_key_blake3("context2", key_material, salt); - - assert_ne!( - key1, key2, - "Different contexts should produce different keys" - ); - } - - #[test] - fn test_different_salts_produce_different_keys() { - let context = "test-context"; - let key_material = b"shared_secret"; - - let key1 = derive_key_blake3(context, key_material, b"salt1"); - let key2 = derive_key_blake3(context, key_material, b"salt2"); - - assert_ne!(key1, key2, "Different salts should produce different keys"); - } - - #[test] - fn test_different_key_material_produces_different_keys() { - let context = "test-context"; - let salt = b"salt"; - - let key1 = derive_key_blake3(context, b"secret1", salt); - let key2 = derive_key_blake3(context, b"secret2", salt); - - assert_ne!( - key1, key2, - "Different key material should produce different keys" - ); - } - - #[test] - fn test_output_length() { - let key = derive_key_blake3("test", b"key", b"salt"); - assert_eq!(key.len(), 32, "Output should be exactly 32 bytes"); - } - - #[test] - fn test_empty_inputs() { - // Should not panic with empty inputs - let key = derive_key_blake3("test", b"", b""); - assert_eq!(key.len(), 32); - } -} diff --git a/common/crypto/src/lib.rs b/common/crypto/src/lib.rs index 3875fa7f81..1dff7b82be 100644 --- a/common/crypto/src/lib.rs +++ b/common/crypto/src/lib.rs @@ -10,8 +10,6 @@ pub mod crypto_hash; pub mod hkdf; #[cfg(feature = "hashing")] pub mod hmac; -#[cfg(feature = "hashing")] -pub mod kdf; #[cfg(all(feature = "asymmetric", feature = "hashing", feature = "stream_cipher"))] pub mod shared_key; pub mod symmetric; diff --git a/common/nym-kkt-ciphersuite/Cargo.toml b/common/nym-kkt-ciphersuite/Cargo.toml index 079325f625..ef1b8e7738 100644 --- a/common/nym-kkt-ciphersuite/Cargo.toml +++ b/common/nym-kkt-ciphersuite/Cargo.toml @@ -9,15 +9,17 @@ license.workspace = true rust-version.workspace = true readme.workspace = true version.workspace = true +publish = false [dependencies] thiserror = { workspace = true } num_enum = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } +semver = { workspace = true } blake3 = { workspace = true, optional = true } -libcrux-sha3 = { git = "https://github.com/cryspen/libcrux", optional = true } +libcrux-sha3 = { workspace = true, optional = true } [features] digests = ["blake3", "libcrux-sha3"] diff --git a/common/nym-kkt-ciphersuite/src/lib.rs b/common/nym-kkt-ciphersuite/src/lib.rs index e72d873a87..9e5c382dc6 100644 --- a/common/nym-kkt-ciphersuite/src/lib.rs +++ b/common/nym-kkt-ciphersuite/src/lib.rs @@ -3,10 +3,12 @@ use crate::error::KKTCiphersuiteError; use num_enum::{IntoPrimitive, TryFromPrimitive}; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::fmt::Display; use strum_macros::{Display, EnumIter, EnumString}; +pub use strum::IntoEnumIterator; + pub mod error; pub const DEFAULT_HASH_LEN: usize = 32; @@ -45,10 +47,13 @@ pub mod xwing { 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 KEMKeyDigests = BTreeMap>; -pub type KeyDigests = HashMap>; +pub mod node_compatibility { + /// Indicates the initial version where kkt has been introduced + /// 1.27.0 Raclette release + pub const INTRODUCTION: semver::Version = semver::Version::new(1, 27, 0); +} #[derive( Clone, @@ -62,6 +67,8 @@ pub type KeyDigests = HashMap>; EnumIter, EnumString, Display, + Ord, + PartialOrd, )] #[strum(ascii_case_insensitive)] #[strum(serialize_all = "lowercase")] @@ -204,23 +211,26 @@ impl SignatureScheme { EnumIter, EnumString, Display, + Default, + Ord, + PartialOrd, )] #[strum(ascii_case_insensitive)] #[strum(serialize_all = "lowercase")] #[repr(u8)] pub enum KEM { - XWing = 0, + // unsupported + // XWing = 0, + #[default] MlKem768 = 1, McEliece = 2, - X25519 = 255, } impl KEM { - pub fn encapsulation_key_length(&self) -> usize { + pub const 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::XWing => xwing::PUBLIC_KEY_LENGTH, KEM::McEliece => mceliece::PUBLIC_KEY_LENGTH, } } @@ -238,6 +248,17 @@ pub struct Ciphersuite { signature_length: usize, } +impl Default for Ciphersuite { + fn default() -> Self { + Ciphersuite::new( + KEM::MlKem768, + HashFunction::Blake3, + SignatureScheme::Ed25519, + HashLength::Default, + ) + } +} + impl Ciphersuite { pub fn new( kem: KEM, @@ -257,6 +278,51 @@ impl Ciphersuite { } } + /// Determine optimal `Ciphersuite` based on remote's node's version + pub fn from_node_version(semver: semver::Version) -> Option { + if semver < node_compatibility::INTRODUCTION { + // node can't possibly support any Ciphersuite + return None; + } + // currently there are no other branches known to the client + // once changes to defaults are introduced, follow pattern similar to the one implemented in + // `common/authenticator-requests/src/version.rs` + Some(Ciphersuite::new( + KEM::MlKem768, + HashFunction::Blake3, + SignatureScheme::Ed25519, + HashLength::Default, + )) + } + + #[must_use] + pub fn with_kem(mut self, kem: KEM) -> Self { + self.kem = kem; + self.encapsulation_key_length = kem.encapsulation_key_length(); + self + } + + #[must_use] + pub fn with_signature_scheme(mut self, signature_scheme: SignatureScheme) -> Self { + self.signature_scheme = signature_scheme; + self.signing_key_length = signature_scheme.signing_key_length(); + self.verification_key_length = signature_scheme.verification_key_length(); + self.signature_length = signature_scheme.signature_length(); + self + } + + #[must_use] + pub fn with_hash_function(mut self, hash_function: HashFunction) -> Self { + self.hash_function = hash_function; + self + } + + #[must_use] + pub fn with_hash_length(mut self, hash_length: HashLength) -> Self { + self.hash_length = hash_length; + self + } + pub fn kem_key_len(&self) -> usize { self.encapsulation_key_length } diff --git a/common/nym-lp-transport/Cargo.toml b/common/nym-kkt-context/Cargo.toml similarity index 54% rename from common/nym-lp-transport/Cargo.toml rename to common/nym-kkt-context/Cargo.toml index d4448e49c8..9ab1c8e123 100644 --- a/common/nym-lp-transport/Cargo.toml +++ b/common/nym-kkt-context/Cargo.toml @@ -1,6 +1,5 @@ [package] -name = "nym-lp-transport" -version = "0.1.0" +name = "nym-kkt-context" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,15 +8,14 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +version.workspace = true publish = false [dependencies] -tokio = { workspace = true, features = ["net", "io-util"] } -nym-test-utils = { path = "../test-utils", optional = true } -tracing = { workspace = true } +num_enum = { workspace = true } +thiserror = { workspace = true } -[features] -io-mocks = ["nym-test-utils"] +nym-kkt-ciphersuite = { path = "../nym-kkt-ciphersuite" } [lints] workspace = true diff --git a/common/nym-kkt/src/context.rs b/common/nym-kkt-context/src/lib.rs similarity index 62% rename from common/nym-kkt/src/context.rs rename to common/nym-kkt-context/src/lib.rs index 8034c52af7..7e7e1f7b6b 100644 --- a/common/nym-kkt/src/context.rs +++ b/common/nym-kkt-context/src/lib.rs @@ -1,13 +1,38 @@ -// Copyright 2025 - Nym Technologies SA +// Copyright 2026 - 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 nym_kkt_ciphersuite::{CIPHERSUITE_ENCODING_LEN, Ciphersuite}; use std::fmt::Display; +use thiserror::Error; + +// This must be less than 4 bits +pub const KKT_VERSION: u8 = 1; +const _: () = assert!(KKT_VERSION < 1 << 4); pub const KKT_CONTEXT_LEN: usize = 3 + CIPHERSUITE_ENCODING_LEN; +#[derive(Debug, Error)] +pub enum KKTContextEncodingError { + #[error("KKT Message Count Limit Reached")] + MessageCountLimitReached, + + #[error("{version} is not a valid KKT version")] + InvalidVersion { version: u8 }, + + #[error("{raw} is not a valid KKTStatus")] + InvalidStatus { raw: u8 }, + + #[error("{raw} is not a valid KKTRole")] + InvalidRole { raw: u8 }, + + #[error("{raw} is not a valid KKTMode")] + InvalidMode { raw: u8 }, + + #[error(transparent)] + InvalidCiphersuite(#[from] nym_kkt_ciphersuite::error::KKTCiphersuiteError), +} + // bitmask used: 0b1110_0000 #[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)] #[repr(u8)] @@ -15,11 +40,11 @@ pub enum KKTStatus { Ok = 0b0000_0000, InvalidRequestFormat = 0b0010_0000, InvalidResponseFormat = 0b0100_0000, - InvalidSignature = 0b0110_0000, - UnsupportedCiphersuite = 0b1000_0000, - UnsupportedKKTVersion = 0b1010_0000, - InvalidKey = 0b1100_0000, - Timeout = 0b1110_0000, + UnsupportedCiphersuite = 0b0110_0000, + UnsupportedKKTVersion = 0b1000_0000, + InvalidKey = 0b1010_0000, + Timeout = 0b1100_0000, + UnverifiedKEMKey = 0b1110_0000, } impl Display for KKTStatus { @@ -28,10 +53,10 @@ impl Display for KKTStatus { KKTStatus::Ok => "Ok", KKTStatus::InvalidRequestFormat => "Invalid Request Format", KKTStatus::InvalidResponseFormat => "Invalid Response Format", - KKTStatus::InvalidSignature => "Invalid Signature", KKTStatus::UnsupportedCiphersuite => "Unsupported Ciphersuite", KKTStatus::UnsupportedKKTVersion => "Unsupported KKT Version", KKTStatus::InvalidKey => "Invalid Key", + KKTStatus::UnverifiedKEMKey => "Could not verify received encapsulation key", KKTStatus::Timeout => "Timeout", }) } @@ -43,7 +68,16 @@ impl Display for KKTStatus { pub enum KKTRole { Initiator = 0b0000_0000, Responder = 0b0000_0001, - AnonymousInitiator = 0b0000_0010, +} + +impl KKTRole { + pub const fn is_initiator(&self) -> bool { + matches!(self, KKTRole::Initiator) + } + + pub const fn is_responder(&self) -> bool { + matches!(self, KKTRole::Responder) + } } // bitmask used: 0b0001_1100 @@ -54,6 +88,16 @@ pub enum KKTMode { Mutual = 0b0000_0100, } +impl KKTMode { + pub const fn is_one_way(&self) -> bool { + matches!(self, KKTMode::OneWay) + } + + pub const fn is_mutual(&self) -> bool { + matches!(self, KKTMode::Mutual) + } +} + #[derive(Copy, Clone, Debug, PartialEq)] pub struct KKTContext { version: u8, @@ -63,24 +107,20 @@ pub struct KKTContext { role: KKTRole, ciphersuite: Ciphersuite, } + impl KKTContext { - pub fn new(role: KKTRole, mode: KKTMode, ciphersuite: Ciphersuite) -> Result { - if role == KKTRole::AnonymousInitiator && mode != KKTMode::OneWay { - return Err(KKTError::IncompatibilityError { - info: "Anonymous Initiator can only use OneWay mode", - }); - } - Ok(Self { + pub fn new(role: KKTRole, mode: KKTMode, ciphersuite: Ciphersuite) -> Self { + Self { version: KKT_VERSION, message_sequence: 0, status: KKTStatus::Ok, mode, role, ciphersuite, - }) + } } - pub fn derive_responder_header(&self) -> Result { + pub fn derive_responder_header(&self) -> Result { let mut responder_header = *self; responder_header.increment_message_sequence_count()?; @@ -89,12 +129,12 @@ impl KKTContext { Ok(responder_header) } - pub fn increment_message_sequence_count(&mut self) -> Result<(), KKTError> { + pub fn increment_message_sequence_count(&mut self) -> Result<(), KKTContextEncodingError> { if self.message_sequence + 1 < (1 << 4) { self.message_sequence += 1; Ok(()) } else { - Err(KKTError::MessageCountLimitReached) + Err(KKTContextEncodingError::MessageCountLimitReached) } } @@ -118,9 +158,10 @@ impl KKTContext { } pub fn body_len(&self) -> usize { - if self.status != KKTStatus::Ok - || (self.mode == KKTMode::OneWay - && (self.role == KKTRole::Initiator || self.role == KKTRole::AnonymousInitiator)) + if (self.status != KKTStatus::Ok && self.status != KKTStatus::UnverifiedKEMKey) + || + // no payload + (self.mode == KKTMode::OneWay && self.role == KKTRole::Initiator) { 0 } else { @@ -128,37 +169,18 @@ impl KKTContext { } } - pub fn signature_len(&self) -> usize { - match self.role { - KKTRole::Initiator | KKTRole::Responder => self.ciphersuite.signature_len(), - KKTRole::AnonymousInitiator => 0, - } - } - pub const fn header_len(&self) -> usize { KKT_CONTEXT_LEN } - 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 - // KKTRole::AnonymousInitiator => 0, - // } - KKT_SESSION_ID_LEN - } - pub fn full_message_len(&self) -> usize { - self.body_len() + self.signature_len() + self.header_len() + self.session_id_len() + self.body_len() + self.header_len() } - pub fn encode(&self) -> Result<[u8; KKT_CONTEXT_LEN], KKTError> { + pub fn encode(&self) -> Result<[u8; KKT_CONTEXT_LEN], KKTContextEncodingError> { let mut header_bytes = [0u8; KKT_CONTEXT_LEN]; if self.message_sequence >= 1 << 4 { - return Err(KKTError::MessageCountLimitReached); + return Err(KKTContextEncodingError::MessageCountLimitReached); } let ciphersuite_bytes = self.ciphersuite.encode(); @@ -175,15 +197,17 @@ impl KKTContext { Ok(header_bytes) } - pub fn try_decode(header_bytes: [u8; KKT_CONTEXT_LEN]) -> Result { + 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; // We only check if stuff is valid here, not necessarily if it's compatible if kkt_version > KKT_VERSION { - return Err(KKTError::FrameDecodingError { - info: format!("Header - Invalid KKT Version: {kkt_version}"), + return Err(KKTContextEncodingError::InvalidVersion { + version: kkt_version, }); } @@ -191,16 +215,15 @@ impl KKTContext { 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}"), + let status = KKTStatus::try_from(raw_kkt_status).map_err(|_| { + KKTContextEncodingError::InvalidStatus { + raw: raw_kkt_status, + } })?; + let role = KKTRole::try_from(raw_kkt_role) + .map_err(|_| KKTContextEncodingError::InvalidRole { raw: raw_kkt_role })?; + let mode = KKTMode::try_from(raw_kkt_mode) + .map_err(|_| KKTContextEncodingError::InvalidMode { raw: raw_kkt_mode })?; // SAFETY: we're taking exactly `CIPHERSUITE_ENCODING_LEN` bytes #[allow(clippy::unwrap_used)] @@ -228,9 +251,8 @@ mod tests { let valid_context = KKTContext::new( KKTRole::Initiator, KKTMode::Mutual, - Ciphersuite::decode([255, 1, 0, 0]).unwrap(), - ) - .unwrap(); + Ciphersuite::decode([1, 1, 0, 0]).unwrap(), + ); let encoded = valid_context.encode().unwrap(); let decoded = KKTContext::try_decode(encoded).unwrap(); diff --git a/common/nym-kkt/Cargo.toml b/common/nym-kkt/Cargo.toml index 705395f712..207b1f00ed 100644 --- a/common/nym-kkt/Cargo.toml +++ b/common/nym-kkt/Cargo.toml @@ -7,35 +7,30 @@ 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-crypto = { path = "../crypto", features = ["hashing"] } nym-kkt-ciphersuite = { workspace = true, features = ["digests"] } +nym-kkt-context = { path = "../nym-kkt-context" } +nym-pemstore = { workspace = true } -libcrux-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" } +libcrux-kem = { workspace = true } +libcrux-ecdh = { workspace = true, features = ["codec"] } +libcrux-chacha20poly1305 = { workspace = true } -# rand 0.9 for libcrux integration (libcrux uses rand 0.9) rand09 = { workspace = true } zeroize = { workspace = true, features = ["zeroize_derive"] } -classic-mceliece-rust = { git = "https://github.com/georgio/classic-mceliece-rust", features = ["mceliece460896f", "zeroize"] } +libcrux-psq = { workspace = true, features = ["classic-mceliece"] } +libcrux-ml-kem = { workspace = true } [dev-dependencies] rand_chacha = "0.9.0" anyhow = { workspace = true } -criterion = { workspace = true } -[[bench]] -name = "benches" -harness = false - [lints] workspace = true diff --git a/common/nym-kkt/benches/benches.rs b/common/nym-kkt/benches/benches.rs deleted file mode 100644 index 4d4f0f7e13..0000000000 --- a/common/nym-kkt/benches/benches.rs +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -// fine in benchmarking code -#![allow(clippy::expect_used)] -#![allow(clippy::unwrap_used)] - -use criterion::{Criterion, criterion_group, criterion_main}; - -use nym_crypto::asymmetric::ed25519; -use nym_kkt::{ - ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM, SignatureScheme}, - context::KKTMode, - frame::KKTFrame, - key_utils::{generate_keypair_libcrux, generate_keypair_mceliece, hash_encapsulation_key}, - session::{ - anonymous_initiator_process, initiator_ingest_response, initiator_process, - responder_ingest_message, responder_process, - }, -}; -use rand09::prelude::*; - -pub fn gen_ed25519_keypair(c: &mut Criterion) { - c.bench_function("Generate Ed25519 Keypair", |b| { - b.iter(|| { - let mut s: [u8; 32] = [0u8; 32]; - rand09::rng().fill_bytes(&mut s); - ed25519::KeyPair::from_secret(s, 0) - }); - }); -} - -pub fn gen_mlkem768_keypair(c: &mut Criterion) { - c.bench_function("Generate MlKem768 Keypair", |b| { - b.iter(|| { - libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, &mut rand09::rng()).unwrap() - }); - }); -} - -pub fn kkt_benchmark(c: &mut Criterion) { - let mut rng = rand09::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 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, - ] { - let ciphersuite = Ciphersuite::resolve_ciphersuite( - kem, - hash_function, - 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 - { - c.bench_function( - &format!("{kem}, {hash_function} | Anonymous Initiator: Generate Request",), - |b| { - b.iter(|| anonymous_initiator_process(&mut rng, ciphersuite).unwrap()); - }, - ); - - let (i_context, i_frame) = - anonymous_initiator_process(&mut rng, ciphersuite).unwrap(); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: Encode Frame - Request", - ), - |b| b.iter(|| i_frame.to_bytes()), - ); - - let i_frame_bytes = i_frame.to_bytes(); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: 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!( - "{kem}, {hash_function} | Anonymous Initiator: Responder Ingest Frame", - ), - |b| { - b.iter(|| { - responder_ingest_message(&r_context, None, None, &i_frame_r).unwrap() - }); - }, - ); - - let (r_context, _) = - responder_ingest_message(&r_context, None, None, &i_frame_r).unwrap(); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: Responder Generate Response", - ), - |b| { - b.iter(|| { - responder_process( - &r_context, - i_frame_r.session_id(), - responder_ed25519_keypair.private_key(), - &responder_kem_public_key, - ) - .unwrap() - }); - }, - ); - let r_frame = responder_process( - &r_context, - i_frame_r.session_id(), - responder_ed25519_keypair.private_key(), - &responder_kem_public_key, - ) - .unwrap(); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: Responder Encode Frame", - ), - |b| b.iter(|| r_frame.to_bytes()), - ); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: Initiator Ingest Response", - ), - |b| { - b.iter(|| { - initiator_ingest_response( - &i_context, - &r_frame, - &r_frame.context().unwrap(), - responder_ed25519_keypair.public_key(), - &r_dir_hash, - ) - .unwrap() - }); - }, - ); - - let obtained_key = initiator_ingest_response( - &i_context, - &r_frame, - &r_frame.context().unwrap(), - responder_ed25519_keypair.public_key(), - &r_dir_hash, - ) - .unwrap(); - - assert_eq!(obtained_key.encode(), r_kem_key_bytes) - } - // Initiator, OneWay - { - let (i_context, i_frame) = initiator_process( - &mut rng, - KKTMode::OneWay, - ciphersuite, - initiator_ed25519_keypair.private_key(), - None, - ) - .unwrap(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator OneWay: Generate Request",), - |b| { - b.iter(|| { - initiator_process( - &mut rng, - KKTMode::OneWay, - ciphersuite, - initiator_ed25519_keypair.private_key(), - None, - ) - .unwrap() - }); - }, - ); - - c.bench_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!("{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!("{kem}, {hash_function} | Initiator OneWay: Responder Ingest Frame",), - |b| { - b.iter(|| { - responder_ingest_message( - &r_context, - Some(initiator_ed25519_keypair.public_key()), - None, - &i_frame_r, - ) - .unwrap() - }); - }, - ); - - let (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()); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Initiator OneWay: Responder Generate Response", - ), - |b| { - b.iter(|| { - responder_process( - &r_context, - i_frame_r.session_id(), - responder_ed25519_keypair.private_key(), - &responder_kem_public_key, - ) - .unwrap() - }); - }, - ); - - let r_frame = responder_process( - &r_context, - i_frame_r.session_id(), - responder_ed25519_keypair.private_key(), - &responder_kem_public_key, - ) - .unwrap(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator OneWay: Responder Encode Frame",), - |b| { - b.iter(|| r_frame.to_bytes()); - }, - ); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Initiator OneWay: Initiator Ingest Response", - ), - |b| { - b.iter(|| { - initiator_ingest_response( - &i_context, - &r_frame, - &r_frame.context().unwrap(), - responder_ed25519_keypair.public_key(), - &r_dir_hash, - ) - .unwrap() - }); - }, - ); - - let i_obtained_key = initiator_ingest_response( - &i_context, - &r_frame, - &r_frame.context().unwrap(), - responder_ed25519_keypair.public_key(), - &r_dir_hash, - ) - .unwrap(); - - assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) - } - - // Initiator, Mutual - { - c.bench_function( - &format!("{kem}, {hash_function} | Initiator Mutual: Generate Request",), - |b| { - b.iter(|| { - initiator_process( - &mut rng, - KKTMode::Mutual, - ciphersuite, - initiator_ed25519_keypair.private_key(), - Some(&initiator_kem_public_key), - ) - .unwrap() - }); - }, - ); - - let (i_context, i_frame) = initiator_process( - &mut rng, - KKTMode::Mutual, - ciphersuite, - initiator_ed25519_keypair.private_key(), - Some(&initiator_kem_public_key), - ) - .unwrap(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator Mutual: Encode Frame - Request",), - |b| { - b.iter(|| i_frame.to_bytes()); - }, - ); - - let i_frame_bytes = i_frame.to_bytes(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator Mutual: 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!("{kem}, {hash_function} | Initiator Mutual: Responder Ingest Frame",), - |b| { - b.iter(|| { - responder_ingest_message( - &r_context, - Some(initiator_ed25519_keypair.public_key()), - Some(&i_dir_hash), - &i_frame_r, - ) - .unwrap() - }); - }, - ); - - let (r_context, r_obtained_key) = responder_ingest_message( - &r_context, - 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); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Initiator Mutual: Responder Generate Response", - ), - |b| { - b.iter(|| { - responder_process( - &r_context, - i_frame_r.session_id(), - responder_ed25519_keypair.private_key(), - &responder_kem_public_key, - ) - .unwrap() - }); - }, - ); - - let r_frame = responder_process( - &r_context, - i_frame_r.session_id(), - responder_ed25519_keypair.private_key(), - &responder_kem_public_key, - ) - .unwrap(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator Mutual: Responder Encode Frame",), - |b| { - b.iter(|| { - r_frame.to_bytes(); - }); - }, - ); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Initiator Mutual: Initiator Ingest Response", - ), - |b| { - b.iter(|| { - initiator_ingest_response( - &i_context, - &r_frame, - &r_frame.context().unwrap(), - responder_ed25519_keypair.public_key(), - &r_dir_hash, - ) - .unwrap() - }); - }, - ); - - let obtained_key = initiator_ingest_response( - &i_context, - &r_frame, - &r_frame.context().unwrap(), - responder_ed25519_keypair.public_key(), - &r_dir_hash, - ) - .unwrap(); - - assert_eq!(obtained_key.encode(), r_kem_key_bytes) - } - } - } -} - -criterion_group!( - benches, - gen_ed25519_keypair, - gen_mlkem768_keypair, - kkt_benchmark -); -criterion_main!(benches); diff --git a/common/nym-kkt/src/carrier.rs b/common/nym-kkt/src/carrier.rs new file mode 100644 index 0000000000..001c10b609 --- /dev/null +++ b/common/nym-kkt/src/carrier.rs @@ -0,0 +1,188 @@ +use libcrux_chacha20poly1305::TAG_LEN; +use libcrux_psq::handshake::types::{DHKeyPair, DHPublicKey}; +use nym_crypto::hkdf::blake3::derive_key_blake3; +use rand09::{CryptoRng, RngCore}; +use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; + +use crate::error::KKTError; + +// This is arbitrary +pub const MAX_PAYLOAD_LEN: usize = 1_000_000; +const CARRIER_KDF_INFO_TX: &str = "CARRIER_V1_KDF_TX"; +const CARRIER_KDF_INFO_RX: &str = "CARRIER_V1_KDF_RX"; + +#[derive(Zeroize, ZeroizeOnDrop)] +pub struct Carrier { + tx_key: [u8; 32], + rx_key: [u8; 32], + tx_counter: u64, + rx_counter: u64, +} + +pub enum CarrierRole { + Initiator, + Responder, +} + +fn increment_nonce(nonce: &mut u64) -> Result<(), KKTError> { + match nonce.checked_add(1) { + Some(incremented_nonce) => { + *nonce = incremented_nonce; + Ok(()) + } + None => Err(KKTError::AEADError { + info: "Nonce maxed out.", + }), + } +} + +fn as_nonce_bytes(nonce: u64) -> [u8; 12] { + let mut bytes = [0u8; 12]; + let nonce_bytes = nonce.to_le_bytes(); + bytes[4..].clone_from_slice(&nonce_bytes); + bytes +} + +impl Carrier { + fn init(tx_key: [u8; 32], rx_key: [u8; 32]) -> Self { + Self { + tx_key, + rx_key, + tx_counter: 1, + rx_counter: 1, + } + } + + pub fn new( + rng: &mut R, + remote_public_key: &DHPublicKey, + context: &[u8], + is_initiator: bool, + ) -> Result<(Self, DHPublicKey), KKTError> + where + R: RngCore + CryptoRng, + { + let ephemeral_keypair = DHKeyPair::new(rng); + let shared_secret = ephemeral_keypair + .sk() + .diffie_hellman(remote_public_key) + .map_err(|_| KKTError::X25519Error { + info: "Key Derivation Error", + })?; + + Ok(( + Self::from_secret_slice(shared_secret.as_ref(), context, is_initiator), + ephemeral_keypair.pk, + )) + } + + pub(crate) fn from_secret_slice(secret: &[u8], context: &[u8], is_initiator: bool) -> Self { + let (tx_key, rx_key) = if is_initiator { + ( + derive_key_blake3(CARRIER_KDF_INFO_TX, secret, context), + derive_key_blake3(CARRIER_KDF_INFO_RX, secret, context), + ) + } else { + ( + derive_key_blake3(CARRIER_KDF_INFO_RX, secret, context), + derive_key_blake3(CARRIER_KDF_INFO_TX, secret, context), + ) + }; + + Self::init(tx_key, rx_key) + } + + pub fn from_secret(secret: [u8; 32], context: &[u8], is_initiator: bool) -> Self { + Self::from_secret_slice(Zeroizing::new(secret).as_slice(), context, is_initiator) + } + + pub fn encrypt(&mut self, plaintext: &[u8]) -> Result, KKTError> { + if plaintext.len() > MAX_PAYLOAD_LEN { + return Err(KKTError::AEADError { + info: "Plaintext too large", + }); + } + let mut output_buffer = vec![0; plaintext.len() + TAG_LEN]; + libcrux_chacha20poly1305::encrypt( + &self.tx_key, + plaintext, + &mut output_buffer, + b"kkt-carrier-v1", + &as_nonce_bytes(self.tx_counter), + )?; + + increment_nonce(&mut self.tx_counter)?; + + Ok(output_buffer) + } + pub fn decrypt(&mut self, ciphertext: &[u8]) -> Result, KKTError> { + if ciphertext.len() > MAX_PAYLOAD_LEN + TAG_LEN { + return Err(KKTError::AEADError { + info: "Ciphertext too large", + }); + } + let mut output_buffer = vec![0; ciphertext.len() - TAG_LEN]; + libcrux_chacha20poly1305::decrypt( + &self.rx_key, + &mut output_buffer, + ciphertext, + b"kkt-carrier-v1", + &as_nonce_bytes(self.rx_counter), + )?; + + increment_nonce(&mut self.rx_counter)?; + + Ok(output_buffer) + } +} + +#[cfg(test)] +mod tests { + use crate::{carrier::Carrier, key_utils::generate_lp_keypair_x25519}; + use rand09::RngCore; + + #[test] + fn test_e2e() { + let mut rng = rand09::rng(); + + // generate responder x25519 keys + let r_x25519 = generate_lp_keypair_x25519(&mut rng); + + let mut context: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut context); + + let ephemeral_keypair = generate_lp_keypair_x25519(&mut rng); + + let i_shared_secret = ephemeral_keypair.sk().diffie_hellman(&r_x25519.pk).unwrap(); + + let r_shared_secret = r_x25519.sk().diffie_hellman(&ephemeral_keypair.pk).unwrap(); + + let mut i_carrier = Carrier::from_secret_slice(i_shared_secret.as_ref(), &context, true); + let mut r_carrier = Carrier::from_secret_slice(r_shared_secret.as_ref(), &context, false); + + let test1 = b"test1: i>r #1"; + let ct1 = i_carrier.encrypt(test1).unwrap(); + let pt1 = r_carrier.decrypt(&ct1).unwrap(); + assert_eq!(pt1, test1); + + let test2 = b"test2: r>i #1"; + let ct2 = i_carrier.encrypt(test2).unwrap(); + let pt2 = r_carrier.decrypt(&ct2).unwrap(); + assert_eq!(pt2, test2); + let test3 = b"test3: i>r #2"; + + let ct3 = i_carrier.encrypt(test3).unwrap(); + let pt3 = r_carrier.decrypt(&ct3).unwrap(); + assert_eq!(pt3, test3); + + let test4 = b"test4: i>r #3"; + let ct4 = i_carrier.encrypt(test4).unwrap(); + let pt4 = r_carrier.decrypt(&ct4).unwrap(); + assert_eq!(pt4, test4); + + let test5 = b"test5: r>i #2"; + let ct5 = i_carrier.encrypt(test5).unwrap(); + let pt5 = r_carrier.decrypt(&ct5).unwrap(); + assert_eq!(pt5, test5); + } +} diff --git a/common/nym-kkt/src/ciphersuite.rs b/common/nym-kkt/src/ciphersuite.rs deleted file mode 100644 index c87915881c..0000000000 --- a/common/nym-kkt/src/ciphersuite.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::error::KKTError; -use libcrux_kem::Algorithm; - -pub use nym_kkt_ciphersuite::*; - -pub enum EncapsulationKey<'a> { - MlKem768(libcrux_kem::PublicKey), - XWing(libcrux_kem::PublicKey), - X25519(libcrux_kem::PublicKey), - McEliece(classic_mceliece_rust::PublicKey<'a>), -} - -pub enum DecapsulationKey<'a> { - MlKem768(libcrux_kem::PrivateKey), - XWing(libcrux_kem::PrivateKey), - X25519(libcrux_kem::PrivateKey), - McEliece(classic_mceliece_rust::SecretKey<'a>), -} -impl<'a> EncapsulationKey<'a> { - pub(crate) fn decode(kem: KEM, bytes: &[u8]) -> Result { - match kem { - KEM::McEliece => { - if bytes.len() != classic_mceliece_rust::CRYPTO_PUBLICKEYBYTES { - Err(KKTError::KEMError { - info: "Received McEliece Encapsulation Key with Invalid Length", - }) - } else { - let mut public_key_bytes = - Box::new([0u8; classic_mceliece_rust::CRYPTO_PUBLICKEYBYTES]); - // Size must be correct due to KKTFrame::from_bytes(message_bytes)? - public_key_bytes.clone_from_slice(bytes); - Ok(EncapsulationKey::McEliece( - classic_mceliece_rust::PublicKey::from(public_key_bytes), - )) - } - } - KEM::X25519 => Ok(EncapsulationKey::X25519(libcrux_kem::PublicKey::decode( - map_kem_to_libcrux_kem(kem)?, - bytes, - )?)), - KEM::MlKem768 => Ok(EncapsulationKey::MlKem768(libcrux_kem::PublicKey::decode( - map_kem_to_libcrux_kem(kem)?, - bytes, - )?)), - KEM::XWing => Ok(EncapsulationKey::XWing(libcrux_kem::PublicKey::decode( - map_kem_to_libcrux_kem(kem)?, - bytes, - )?)), - } - } - - pub fn encode(&self) -> Vec { - match self { - EncapsulationKey::XWing(public_key) - | EncapsulationKey::MlKem768(public_key) - | EncapsulationKey::X25519(public_key) => public_key.encode(), - EncapsulationKey::McEliece(public_key) => Vec::from(public_key.as_array()), - } - } -} - -pub const fn map_kem_to_libcrux_kem(kem: KEM) -> Result { - match kem { - KEM::MlKem768 => Ok(Algorithm::MlKem768), - KEM::XWing => Ok(Algorithm::XWingKemDraft06), - KEM::X25519 => Ok(Algorithm::X25519), - KEM::McEliece => Err(KKTError::KEMMapping { - info: "attempted to map McEliece KEM to libcrux_kem", - }), - } -} diff --git a/common/nym-kkt/src/encryption.rs b/common/nym-kkt/src/encryption.rs deleted file mode 100644 index c4189f9552..0000000000 --- a/common/nym-kkt/src/encryption.rs +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2025-2026 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -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 rand09::{CryptoRng, RngCore}; -use zeroize::Zeroize; - -#[derive(Clone, Copy, Zeroize)] -pub struct KKTSessionSecret([u8; 32]); - -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); - - let ephemeral_private_key = x25519::PrivateKey::from_secret(private_key_bytes); - let ephemeral_public_key = x25519::PublicKey::from(&ephemeral_private_key); - - ( - Self::derive(&ephemeral_private_key, remote_public_key), - ephemeral_public_key, - ) - } - pub fn from_bytes(secret: [u8; 32]) -> Self { - Self(secret) - } - - 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]); - - // Todo: check validity of pk... - let pk = x25519::PublicKey::from(pub_key); - Ok(Self::derive(private_key, &pk)) - } - - pub fn derive(private_key: &x25519::PrivateKey, public_key: &x25519::PublicKey) -> Self { - let mut shared_secret = private_key.diffie_hellman(public_key); - - let mut hasher = Hasher::new(); - - hasher.update(&shared_secret); - shared_secret.zeroize(); - - Self(hasher.finalize().as_bytes().to_owned()) - } - pub fn as_bytes(&self) -> &[u8; 32] { - &self.0 - } -} - -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 mut encrypted_frame = - encrypt_kkt_frame(rng, &session_secret_key, kkt_frame, KKT_INITIAL_FRAME_AAD)?; - - 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); - - // [ 32 | 12 | ciphertext | 16]; - // [eph_pub_key | nonce | ciphertext | tag]; - Ok((session_secret_key, output_buffer)) -} - -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 (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 rand09::{RngCore, SeedableRng, rng}; - - #[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 = rand_chacha::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 1defc1b1e0..5751b096ba 100644 --- a/common/nym-kkt/src/error.rs +++ b/common/nym-kkt/src/error.rs @@ -3,18 +3,18 @@ use crate::context::KKTStatus; use nym_kkt_ciphersuite::error::KKTCiphersuiteError; +use nym_kkt_context::KKTContextEncodingError; use std::fmt::Debug; use thiserror::Error; #[derive(Error, Debug)] pub enum KKTError { - #[error("Signature constructor error")] - SigConstructorError, - #[error("Signature verification error")] - SigVerifError, #[error(transparent)] CiphersuiteDecodingError(#[from] KKTCiphersuiteError), + #[error(transparent)] + MaskedByteError(#[from] MaskedByteError), + #[error("KEM mapping failure: {}", info)] KEMMapping { info: &'static str }, @@ -33,9 +33,6 @@ pub enum KKTError { #[error("KKT Responder Flagged Error: {}", status)] ResponderFlaggedError { status: KKTStatus }, - #[error("KKT Message Count Limit Reached")] - MessageCountLimitReached, - #[error("PSQ KEM Error: {}", info)] KEMError { info: &'static str }, @@ -48,8 +45,40 @@ pub enum KKTError { #[error("{}", info)] AEADError { info: &'static str }, + #[error("{}", info)] + DecodingError { info: &'static str }, + + #[error("{}", info)] + UnsupportedAlgorithm { info: &'static str }, + #[error("Generic libcrux error")] LibcruxError, + + #[error("failed to derive shared secret: {inner:?}")] + SharedSecretDerivationFailure { + inner: libcrux_psq::handshake::HandshakeError, + }, + + #[error("the received encapsulation key hash does not match the expected value")] + MismatchedKEMHash, + + #[error(transparent)] + MalformedContext(#[from] KKTContextEncodingError), +} + +impl KKTError { + pub fn shared_secret_derivation_failure(inner: libcrux_psq::handshake::HandshakeError) -> Self { + KKTError::SharedSecretDerivationFailure { inner } + } +} + +#[derive(Error, Debug)] +pub enum MaskedByteError { + #[error("invalid Masked Byte Length: Expected({expected}), Actual({actual})")] + InvalidLength { expected: usize, actual: usize }, + + #[error("failed to Unmask Byte")] + Failure, } impl From for KKTError { diff --git a/common/nym-kkt/src/frame.rs b/common/nym-kkt/src/frame.rs index 6502af882c..a6780336c4 100644 --- a/common/nym-kkt/src/frame.rs +++ b/common/nym-kkt/src/frame.rs @@ -7,90 +7,158 @@ // [2..=5] => Ciphersuite // [6] => Reserved +use crate::context::{KKTMode, KKTRole}; +use crate::message::{ + DecryptedRequestFrame, KKTRequest, KKTRequestEncryptionResult, KKTRequestPlaintext, +}; use crate::{ context::{KKT_CONTEXT_LEN, KKTContext}, error::KKTError, }; +use libcrux_psq::handshake::types::{DHKeyPair, DHPublicKey}; +use nym_kkt_ciphersuite::KEM; +use rand09::{CryptoRng, RngCore}; -pub const KKT_SESSION_ID_LEN: usize = 16; - -pub type KKTSessionId = [u8; KKT_SESSION_ID_LEN]; +pub(crate) const KKT_CARRIER_CONTEXT: &[u8] = b"CARRIER_V1_KKT_V1_KDF"; #[derive(Debug, PartialEq, Clone)] pub struct KKTFrame { - context: [u8; KKT_CONTEXT_LEN], - session_id: KKTSessionId, + context: KKTContext, body: Vec, - signature: Vec, + payload: Vec, } -// if oneway and message coming from initiator => body is empty, signature contains signature of context + session id (64 bytes). -// if message coming from anonymous initiator => body is empty, there is no signature. -// if mutual and message coming from initiator => body has the initiator's kem public key and the signature is over the context + body + session_id. -// if coming from responder => body has the responder's kem public key and the signature is over the context + body + session_id. +// if oneway and message coming from initiator => body is empty. +// if mutual and message coming from initiator => body has the initiator's kem public key. +// if coming from responder => body has the responder's kem public key. impl KKTFrame { - pub fn new( - context: [u8; KKT_CONTEXT_LEN], - body: &[u8], - session_id: [u8; KKT_SESSION_ID_LEN], - signature: &[u8], - ) -> Self { + pub fn new(context: KKTContext, body: &[u8], payload: Vec) -> Self { Self { context, body: Vec::from(body), - session_id, - signature: Vec::from(signature), + payload, } } - pub fn context_ref(&self) -> &[u8] { + + pub const fn size_excluding_payload(role: KKTRole, mode: KKTMode, kem: KEM) -> usize { + match role { + KKTRole::Initiator => { + match mode { + KKTMode::OneWay => { + // if oneway and message coming from initiator => body is empty. + KKT_CONTEXT_LEN + } + KKTMode::Mutual => { + // if mutual and message coming from initiator => body has the initiator's kem public key. + KKT_CONTEXT_LEN + kem.encapsulation_key_length() + } + } + } + KKTRole::Responder => { + // if coming from responder => body has the responder's kem public key. + KKT_CONTEXT_LEN + kem.encapsulation_key_length() + } + } + } + + pub fn size(&self) -> usize { + self.payload.len() + + Self::size_excluding_payload( + self.context.role(), + self.context.mode(), + self.context.ciphersuite().kem(), + ) + } + + pub fn context(&self) -> &KKTContext { &self.context } - pub fn context(&self) -> Result { - KKTContext::try_decode(self.context) + pub fn payload(&self) -> &[u8] { + self.payload.as_ref() } - pub fn signature_ref(&self) -> &[u8] { - &self.signature + pub fn encrypt_initiator_frame( + self, + rng: &mut R, + responder_public_key: &DHPublicKey, + version_byte: u8, + ) -> Result + where + R: CryptoRng + RngCore, + { + let ephemeral_keypair = DHKeyPair::new(rng); + + let plaintext = + KKTRequestPlaintext::new(ephemeral_keypair.pk, responder_public_key, version_byte); + + let mut carrier = + plaintext.derive_initiator_carrier(ephemeral_keypair.sk(), responder_public_key)?; + let full_kkt_message = plaintext.into_request(&mut carrier, self)?; + + Ok(KKTRequestEncryptionResult { + carrier, + request: full_kkt_message, + }) + } + + pub fn decrypt_initiator_frame( + responder_keypair: &DHKeyPair, + message: KKTRequest, + supported_versions: &[u8], + request_payload_len: usize, + ) -> Result { + let mask = message.plaintext.version_mask(&responder_keypair.pk); + + // check mask + // this could be used later when we have multiple versions + // if this call fails, it does before the server has to run a DH + let outer_protocol_version = message + .plaintext + .masked_version_bytes + .unmask_check_version(&mask, supported_versions)?; + + // after verifying the version, we can perform the DH and continue processing the request + let mut carrier = message + .plaintext + .derive_responder_carrier(responder_keypair)?; + + let decrypted_message = carrier.decrypt(&message.encrypted_frame)?; + let frame = KKTFrame::from_bytes(&decrypted_message, request_payload_len)?; + + Ok(DecryptedRequestFrame { + carrier, + remote_frame: frame, + outer_protocol_version, + }) } pub fn body_ref(&self) -> &[u8] { &self.body } - 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 body(self) -> Vec { + self.body } - pub fn signature_mut(&mut self) -> &mut [u8] { - &mut self.signature - } pub fn body_mut(&mut self) -> &mut [u8] { &mut self.body } - pub fn session_id_mut(&mut self) -> &mut [u8] { - &mut self.session_id - } - pub fn frame_length(&self) -> usize { - self.context.len() + self.session_id.len() + self.body.len() + self.signature.len() + KKT_CONTEXT_LEN + self.body.len() } - pub fn to_bytes(&self) -> Vec { + pub fn try_to_bytes(&self) -> Result, KKTError> { let mut bytes = Vec::with_capacity(self.frame_length()); - bytes.extend_from_slice(&self.context); + bytes.extend_from_slice(&self.context.encode()?); bytes.extend_from_slice(&self.body); - bytes.extend_from_slice(&self.session_id); - bytes.extend_from_slice(&self.signature); - bytes + bytes.extend_from_slice(&self.payload); + Ok(bytes) } - pub fn from_bytes(bytes: &[u8]) -> Result<(Self, KKTContext), KKTError> { + pub fn from_bytes(bytes: &[u8], payload_len: usize) -> Result { let len = bytes.len(); if bytes.len() < KKT_CONTEXT_LEN { return Err(KKTError::FrameDecodingError { @@ -105,7 +173,7 @@ impl KKTFrame { 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() { + if bytes.len() != context.full_message_len() + payload_len { return Err(KKTError::FrameDecodingError { info: format!( "Frame is shorter than expected: actual {len} != expected {}", @@ -115,7 +183,6 @@ impl KKTFrame { } let mut body = Vec::new(); - let mut signature = Vec::new(); // decode body if context.body_len() > 0 { @@ -123,33 +190,9 @@ impl KKTFrame { 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(); + // decode payload. this could be empty. + let payload: Vec = Vec::from(&bytes[KKT_CONTEXT_LEN + context.body_len()..]); - // // 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, - )) + Ok(KKTFrame::new(context, &body, payload)) } } diff --git a/common/nym-kkt/src/initiator.rs b/common/nym-kkt/src/initiator.rs new file mode 100644 index 0000000000..e764a12375 --- /dev/null +++ b/common/nym-kkt/src/initiator.rs @@ -0,0 +1,188 @@ +// Copyright 2025-2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use libcrux_psq::handshake::types::DHPublicKey; +use nym_kkt_ciphersuite::Ciphersuite; +use rand09::{CryptoRng, RngCore}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +use crate::keys::EncapsulationKey; +use crate::message::{KKTRequest, KKTResponse, ProcessedKKTResponse}; +use crate::{ + carrier::Carrier, + context::{KKTContext, KKTMode, KKTRole, KKTStatus}, + error::KKTError, + frame::KKTFrame, + key_utils::validate_encapsulation_key, +}; + +#[derive(Zeroize, ZeroizeOnDrop)] +pub struct KKTInitiator<'a> { + carrier: Carrier, + + #[zeroize(skip)] + context: KKTContext, + + #[zeroize(skip)] + expected_hash: &'a [u8], +} + +impl<'a> KKTInitiator<'a> { + // to be used by clients + pub fn generate_one_way_request( + rng: &mut R, + ciphersuite: Ciphersuite, + responder_dh_public_key: &DHPublicKey, + expected_hash: &'a [u8], + outer_protocol_version: u8, + payload: Option>, + ) -> Result<(Self, KKTRequest), KKTError> + where + R: CryptoRng + RngCore, + { + Self::generate_encrypted_request( + rng, + KKTMode::OneWay, + ciphersuite, + None, + responder_dh_public_key, + expected_hash, + outer_protocol_version, + payload, + ) + } + + // to be used by nodes + pub fn generate_mutual_request<'b, R>( + rng: &mut R, + ciphersuite: Ciphersuite, + local_encapsulation_key: &'b [u8], + responder_dh_public_key: &DHPublicKey, + expected_hash: &'a [u8], + outer_protocol_version: u8, + payload: Option>, + ) -> Result<(Self, KKTRequest), KKTError> + where + R: CryptoRng + RngCore, + { + Self::generate_encrypted_request( + rng, + KKTMode::Mutual, + ciphersuite, + Some(local_encapsulation_key), + responder_dh_public_key, + expected_hash, + outer_protocol_version, + payload, + ) + } + + #[allow(clippy::too_many_arguments)] + fn generate_encrypted_request<'b, R>( + rng: &mut R, + mode: KKTMode, + ciphersuite: Ciphersuite, + local_encapsulation_key: Option<&'b [u8]>, + responder_dh_public_key: &DHPublicKey, + expected_hash: &'a [u8], + outer_protocol_version: u8, + payload: Option>, + ) -> Result<(Self, KKTRequest), KKTError> + where + R: CryptoRng + RngCore, + { + let frame = initiator_process(mode, ciphersuite, local_encapsulation_key, payload)?; + let context = *frame.context(); + + let request = + frame.encrypt_initiator_frame(rng, responder_dh_public_key, outer_protocol_version)?; + + Ok(( + Self { + carrier: request.carrier, + context, + expected_hash, + }, + request.request, + )) + } + + pub fn process_response( + &mut self, + response: KKTResponse, + response_payload_len: usize, + ) -> Result { + let decrypted_response_bytes = self.carrier.decrypt(&response.encrypted_frame)?; + let response_frame = KKTFrame::from_bytes(&decrypted_response_bytes, response_payload_len)?; + initiator_ingest_response(&self.context, &response_frame, self.expected_hash) + } +} + +pub fn initiator_process( + mode: KKTMode, + ciphersuite: Ciphersuite, + own_encapsulation_key: Option<&[u8]>, + payload: Option>, +) -> Result { + let context = KKTContext::new(KKTRole::Initiator, mode, ciphersuite); + + let body: &[u8] = match mode { + KKTMode::OneWay => &[], + KKTMode::Mutual => match own_encapsulation_key { + Some(encaps_key) => encaps_key, + + // Missing key + None => { + return Err(KKTError::FunctionInputError { + info: "KEM Key Not Provided", + }); + } + }, + }; + + Ok(KKTFrame::new( + context, + body, + match payload { + Some(payload_vec) => payload_vec, + None => Vec::with_capacity(0), + }, + )) +} + +pub fn initiator_ingest_response( + own_context: &KKTContext, + remote_frame: &KKTFrame, + expected_hash: &[u8], +) -> Result { + let remote_context = remote_frame.context(); + let verified_initiator_kem_key = match remote_context.status() { + KKTStatus::Ok | KKTStatus::UnverifiedKEMKey => { + match validate_encapsulation_key( + own_context.ciphersuite().hash_function(), + own_context.ciphersuite().hash_len(), + remote_frame.body_ref(), + expected_hash, + ) { + true => remote_context.status() != KKTStatus::UnverifiedKEMKey, + + // The key does not match the hash obtained from the directory + false => return Err(KKTError::MismatchedKEMHash), + } + } + _ => { + return Err(KKTError::ResponderFlaggedError { + status: remote_context.status(), + }); + } + }; + + let kem = own_context.ciphersuite().kem(); + let kem_bytes = remote_frame.body_ref(); + let encapsulation_key = EncapsulationKey::try_from_bytes(kem_bytes.to_vec(), kem)?; + Ok(ProcessedKKTResponse { + encapsulation_key, + verified_initiator_kem_key, + response_payload: remote_frame.payload().to_vec(), + }) +} diff --git a/common/nym-kkt/src/key_utils.rs b/common/nym-kkt/src/key_utils.rs index 2ea88d231b..222f228088 100644 --- a/common/nym-kkt/src/key_utils.rs +++ b/common/nym-kkt/src/key_utils.rs @@ -1,74 +1,35 @@ -use crate::ciphersuite::HashFunction; -use std::collections::HashMap; +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 -use classic_mceliece_rust::keypair_boxed; - -use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, KeyDigests}; +use libcrux_ml_kem::mlkem768::MlKem768KeyPair; +use libcrux_psq::handshake::types::DHKeyPair; +use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, HashFunction, KEMKeyDigests}; use rand09::{CryptoRng, RngCore}; +use std::collections::BTreeMap; -pub fn generate_keypair_ed25519( - rng: &mut R, - index: Option, -) -> nym_crypto::asymmetric::ed25519::KeyPair +pub fn generate_lp_keypair_x25519(rng: &mut R) -> DHKeyPair 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)) + DHKeyPair::new(rng) } -pub fn generate_keypair_x25519(rng: &mut R) -> nym_crypto::asymmetric::x25519::KeyPair +pub fn generate_keypair_mlkem(rng: &mut R) -> MlKem768KeyPair 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() + libcrux_ml_kem::mlkem768::rand::generate_key_pair(rng) } -// (decapsulation_key, encapsulation_key) -pub fn generate_keypair_libcrux( - rng: &mut R, - kem: crate::ciphersuite::KEM, -) -> Result<(libcrux_kem::PrivateKey, libcrux_kem::PublicKey), crate::error::KKTError> +pub fn generate_keypair_mceliece(rng: &mut R) -> libcrux_psq::classic_mceliece::KeyPair where R: RngCore + CryptoRng, { - match kem { - 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", - }), - } -} -// (decapsulation_key, encapsulation_key) -pub fn generate_keypair_mceliece<'a, R>( - rng: &mut R, -) -> ( - classic_mceliece_rust::SecretKey<'a>, - classic_mceliece_rust::PublicKey<'a>, -) -where - R: RngCore + CryptoRng, -{ - let (encapsulation_key, decapsulation_key) = keypair_boxed(rng); - (decapsulation_key, encapsulation_key) + libcrux_psq::classic_mceliece::KeyPair::generate_key_pair(rng) } pub fn hash_key_bytes( - hash_function: &HashFunction, + hash_function: HashFunction, hash_length: usize, key_bytes: &[u8], ) -> Vec { @@ -77,9 +38,9 @@ pub fn hash_key_bytes( /// 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 { +pub fn produce_key_digests(key_bytes: &[u8]) -> KEMKeyDigests { use strum::IntoEnumIterator; - let mut digests = HashMap::new(); + let mut digests = BTreeMap::new(); for hash in HashFunction::iter() { digests.insert(hash, hash.digest(key_bytes, DEFAULT_HASH_LEN)); } @@ -93,7 +54,7 @@ fn compare_hashes(a: &[u8], b: &[u8]) -> bool { } pub fn validate_encapsulation_key( - hash_function: &HashFunction, + hash_function: HashFunction, hash_length: usize, encapsulation_key: &[u8], expected_hash_bytes: &[u8], @@ -104,20 +65,8 @@ pub fn validate_encapsulation_key( ) } -pub fn validate_key_bytes( - hash_function: &HashFunction, - hash_length: usize, - key_bytes: &[u8], - expected_hash_bytes: &[u8], -) -> bool { - compare_hashes( - &hash_key_bytes(hash_function, hash_length, key_bytes), - expected_hash_bytes, - ) -} - pub fn hash_encapsulation_key( - hash_function: &HashFunction, + hash_function: HashFunction, hash_length: usize, encapsulation_key: &[u8], ) -> Vec { diff --git a/common/nym-kkt/src/keys.rs b/common/nym-kkt/src/keys.rs new file mode 100644 index 0000000000..18206de7b8 --- /dev/null +++ b/common/nym-kkt/src/keys.rs @@ -0,0 +1,440 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::KKTError; +use libcrux_psq::handshake::types::PQEncapsulationKey; +use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; +use std::collections::BTreeMap; +use std::fmt::{Debug, Formatter}; +use std::sync::Arc; + +use crate::key_utils::produce_key_digests; +pub use libcrux_ml_kem::mlkem768::{MlKem768KeyPair, MlKem768PrivateKey, MlKem768PublicKey}; +pub use libcrux_psq::classic_mceliece as mceliece; +pub use libcrux_psq::handshake::types::{DHKeyPair, DHPrivateKey, DHPublicKey}; + +/// Wrapper around keys used for the KEM exchange +/// with cheap clones thanks to Arc wrappers +#[derive(Clone)] +pub struct KEMKeys { + mc_eliece_pk: Arc, + mc_eliece_sk: Arc, + ml_kem768_pk: Arc, + ml_kem768_sk: Arc, +} + +impl Debug for KEMKeys { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KEMKeys") + .field("mc_eliece", &"") + .field("ml_kem768", &"") + .finish() + } +} + +impl KEMKeys { + pub fn new(mc_eliece: mceliece::KeyPair, ml_kem768: MlKem768KeyPair) -> Self { + let (ml_kem768_sk, ml_kem768_pk) = ml_kem768.into_parts(); + Self { + mc_eliece_pk: Arc::new(mc_eliece.pk), + mc_eliece_sk: Arc::new(mc_eliece.sk), + ml_kem768_pk: Arc::new(ml_kem768_pk), + ml_kem768_sk: Arc::new(ml_kem768_sk), + } + } + + pub fn encapsulation_keys_digests(&self) -> BTreeMap { + let mut digests = BTreeMap::new(); + + let mlkem_digests = produce_key_digests(self.ml_kem768_pk.as_slice()); + let mceliece_digests = produce_key_digests(self.mc_eliece_pk.as_ref().as_ref()); + + digests.insert(KEM::MlKem768, mlkem_digests); + digests.insert(KEM::McEliece, mceliece_digests); + + digests + } + + pub fn encoded_encapsulation_key(&self, kem: KEM) -> Option<&[u8]> { + match kem { + KEM::McEliece => Some(self.mc_eliece_pk.as_ref().as_ref()), + KEM::MlKem768 => Some(self.ml_kem768_pk.as_slice()), + // _ => None, + } + } + + pub fn encapsulation_key(&self, kem: KEM) -> Option { + match kem { + KEM::McEliece => Some(EncapsulationKey::McEliece(self.mc_eliece_pk.clone())), + KEM::MlKem768 => Some(EncapsulationKey::MlKem768(self.ml_kem768_pk.clone())), + // _ => None, + } + } + + pub fn mc_eliece_encapsulation_key(&self) -> &mceliece::PublicKey { + &self.mc_eliece_pk + } + + pub fn ml_kem768_encapsulation_key(&self) -> &MlKem768PublicKey { + self.ml_kem768_pk.as_ref() + } + + pub fn mc_eliece_decapsulation_key(&self) -> &mceliece::SecretKey { + &self.mc_eliece_sk + } + + pub fn ml_kem768_decapsulation_key(&self) -> &MlKem768PrivateKey { + &self.ml_kem768_sk + } +} + +#[derive(Clone)] +pub enum EncapsulationKey { + McEliece(Arc), + MlKem768(Arc), +} + +impl Debug for EncapsulationKey { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + EncapsulationKey::McEliece(_) => write!(f, "EncapsulationKey::McEliece"), + EncapsulationKey::MlKem768(_) => write!(f, "EncapsulationKey::MlKem768"), + } + } +} + +impl EncapsulationKey { + pub fn kem(&self) -> KEM { + match self { + EncapsulationKey::McEliece(_) => KEM::McEliece, + EncapsulationKey::MlKem768(_) => KEM::MlKem768, + } + } + + pub fn as_pq_encapsulation_key(&self) -> PQEncapsulationKey<'_> { + match self { + EncapsulationKey::McEliece(pk) => PQEncapsulationKey::CMC(pk), + EncapsulationKey::MlKem768(pk) => PQEncapsulationKey::MlKem(pk), + } + } + + pub fn try_from_bytes(bytes: Vec, kem: KEM) -> Result { + match kem { + KEM::MlKem768 => Ok(EncapsulationKey::MlKem768(Arc::new( + MlKem768PublicKey::try_from(bytes.as_slice()).map_err(|_| KKTError::KEMError { + info: "mlkem768 key of invalid length", + })?, + ))), + KEM::McEliece => { + let boxed_array: Box<[u8; nym_kkt_ciphersuite::mceliece::PUBLIC_KEY_LENGTH]> = + bytes + .into_boxed_slice() + .try_into() + .map_err(|_| KKTError::KEMError { + info: "mceliece key of invalid length", + })?; + + Ok(EncapsulationKey::McEliece(Arc::new( + mceliece::PublicKey::from(boxed_array), + ))) + } + } + } + + pub fn as_bytes(&self) -> &[u8] { + match self { + EncapsulationKey::McEliece(k) => k.as_ref().as_ref(), + EncapsulationKey::MlKem768(k) => k.as_ref().as_ref(), + } + } +} + +// storage helpers +pub mod storage_wrappers { + use nym_pemstore::traits::PemStorableKey; + use thiserror::Error; + + #[derive(Debug, Error)] + pub enum MalformedStoredKeyError { + #[error("{typ} stored key has an invalid length")] + InvalidKeyLength { typ: &'static str }, + + #[error("{typ} stored key is malformed: {message}")] + MalformedData { typ: &'static str, message: String }, + + #[error("attempted to take ownership of a stored {typ} key representation")] + IllegalStoredConversion { typ: &'static str }, + } + + pub trait StorableKey: Sized { + type StorableRepresentation<'a>: PemStorableKey + + From<&'a Self> + + TryInto + + Sized + where + Self: 'a; + + fn to_storable(&self) -> Self::StorableRepresentation<'_> { + self.into() + } + + fn from_storable( + repr: Self::StorableRepresentation<'_>, + ) -> Result { + repr.try_into() + } + } + + macro_rules! declare_key_wrappers { + ($pub_key_type:ty, $private_key_type:ty) => { + pub enum StorablePublicKey<'a> { + Owned(Box<$pub_key_type>), + Borrowed(&'a $pub_key_type), + } + + impl AsRef<$pub_key_type> for StorablePublicKey<'_> { + fn as_ref(&self) -> &$pub_key_type { + match self { + StorablePublicKey::Owned(k) => k, + StorablePublicKey::Borrowed(k) => k, + } + } + } + + pub enum StorablePrivateKey<'a> { + Owned(Box<$private_key_type>), + Borrowed(&'a $private_key_type), + } + + impl AsRef<$private_key_type> for StorablePrivateKey<'_> { + fn as_ref(&self) -> &$private_key_type { + match self { + StorablePrivateKey::Owned(k) => k, + StorablePrivateKey::Borrowed(k) => k, + } + } + } + + impl<'a> From<&'a $pub_key_type> for StorablePublicKey<'a> { + fn from(value: &'a $pub_key_type) -> Self { + StorablePublicKey::Borrowed(value) + } + } + + impl<'a> TryFrom> for $pub_key_type { + type Error = MalformedStoredKeyError; + + fn try_from(value: StorablePublicKey<'a>) -> Result { + match value { + StorablePublicKey::Owned(value) => Ok(*value), + StorablePublicKey::Borrowed(_) => { + Err(MalformedStoredKeyError::IllegalStoredConversion { + typ: ::pem_type(), + }) + } + } + } + } + + impl<'a> From<&'a $private_key_type> for StorablePrivateKey<'a> { + fn from(value: &'a $private_key_type) -> Self { + StorablePrivateKey::Borrowed(value) + } + } + + impl<'a> TryFrom> for $private_key_type { + type Error = MalformedStoredKeyError; + + fn try_from(value: StorablePrivateKey<'a>) -> Result { + match value { + StorablePrivateKey::Owned(value) => Ok(*value), + StorablePrivateKey::Borrowed(_) => { + Err(MalformedStoredKeyError::IllegalStoredConversion { + typ: ::pem_type(), + }) + } + } + } + } + + impl $crate::keys::storage_wrappers::StorableKey for $pub_key_type { + type StorableRepresentation<'a> = StorablePublicKey<'a>; + } + + impl $crate::keys::storage_wrappers::StorableKey for $private_key_type { + type StorableRepresentation<'a> = StorablePrivateKey<'a>; + } + }; + } + + pub mod mceliece { + use crate::keys::storage_wrappers::MalformedStoredKeyError; + use libcrux_psq::classic_mceliece; + use nym_pemstore::traits::PemStorableKey; + + declare_key_wrappers!(classic_mceliece::PublicKey, classic_mceliece::SecretKey); + + impl<'a> PemStorableKey for StorablePrivateKey<'a> { + type Error = MalformedStoredKeyError; + + fn pem_type() -> &'static str { + "MCELIECE PRIVATE KEY" + } + + fn to_bytes(&self) -> Vec { + self.as_ref().as_ref().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let bytes: Box<[u8; nym_kkt_ciphersuite::mceliece::SECRET_KEY_LENGTH]> = + bytes.to_vec().into_boxed_slice().try_into().map_err(|_| { + MalformedStoredKeyError::InvalidKeyLength { + typ: Self::pem_type(), + } + })?; + + Ok(StorablePrivateKey::Owned(Box::new( + classic_mceliece::SecretKey::from(bytes), + ))) + } + } + + impl<'a> PemStorableKey for StorablePublicKey<'a> { + type Error = MalformedStoredKeyError; + + fn pem_type() -> &'static str { + "MCELIECE PUBLIC KEY" + } + + fn to_bytes(&self) -> Vec { + self.as_ref().as_ref().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let bytes: Box<[u8; nym_kkt_ciphersuite::mceliece::PUBLIC_KEY_LENGTH]> = + bytes.to_vec().into_boxed_slice().try_into().map_err(|_| { + MalformedStoredKeyError::InvalidKeyLength { + typ: Self::pem_type(), + } + })?; + + Ok(StorablePublicKey::Owned(Box::new( + classic_mceliece::PublicKey::from(bytes), + ))) + } + } + } + + pub mod mlkem768 { + use crate::keys::storage_wrappers::MalformedStoredKeyError; + use libcrux_ml_kem::mlkem768::{MlKem768PrivateKey, MlKem768PublicKey}; + use nym_pemstore::traits::PemStorableKey; + + declare_key_wrappers!(MlKem768PublicKey, MlKem768PrivateKey); + + impl<'a> PemStorableKey for StorablePrivateKey<'a> { + type Error = MalformedStoredKeyError; + + fn pem_type() -> &'static str { + "MLKEM768 PRIVATE KEY" + } + + fn to_bytes(&self) -> Vec { + self.as_ref().as_slice().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let inner = MlKem768PrivateKey::try_from(bytes).map_err(|message| { + MalformedStoredKeyError::MalformedData { + typ: Self::pem_type(), + message: message.to_string(), + } + })?; + Ok(StorablePrivateKey::Owned(Box::new(inner))) + } + } + + impl<'a> PemStorableKey for StorablePublicKey<'a> { + type Error = MalformedStoredKeyError; + + fn pem_type() -> &'static str { + "MLKEM768 PUBLIC KEY" + } + + fn to_bytes(&self) -> Vec { + self.as_ref().as_slice().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let inner = MlKem768PublicKey::try_from(bytes).map_err(|message| { + MalformedStoredKeyError::MalformedData { + typ: Self::pem_type(), + message: message.to_string(), + } + })?; + Ok(StorablePublicKey::Owned(Box::new(inner))) + } + } + } + + pub mod x25519 { + use crate::keys::storage_wrappers::MalformedStoredKeyError; + use libcrux_psq::handshake::types::{DHPrivateKey, DHPublicKey}; + use nym_pemstore::traits::PemStorableKey; + + declare_key_wrappers!(DHPublicKey, DHPrivateKey); + + impl<'a> PemStorableKey for StorablePrivateKey<'a> { + type Error = MalformedStoredKeyError; + + fn pem_type() -> &'static str { + "LP X25519 PRIVATE KEY" + } + + fn to_bytes(&self) -> Vec { + self.as_ref().as_ref().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let bytes = + bytes + .try_into() + .map_err(|_| MalformedStoredKeyError::InvalidKeyLength { + typ: Self::pem_type(), + })?; + Ok(StorablePrivateKey::Owned(Box::new( + DHPrivateKey::from_bytes(&bytes).map_err(|err| { + MalformedStoredKeyError::MalformedData { + typ: Self::pem_type(), + message: format!("{err:?}"), + } + })?, + ))) + } + } + + impl<'a> PemStorableKey for StorablePublicKey<'a> { + type Error = MalformedStoredKeyError; + + fn pem_type() -> &'static str { + "LP X25519 PUBLIC KEY" + } + + fn to_bytes(&self) -> Vec { + self.as_ref().as_ref().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let bytes = + bytes + .try_into() + .map_err(|_| MalformedStoredKeyError::InvalidKeyLength { + typ: Self::pem_type(), + })?; + Ok(StorablePublicKey::Owned(Box::new(DHPublicKey::from_bytes( + &bytes, + )))) + } + } + } +} diff --git a/common/nym-kkt/src/kkt.rs b/common/nym-kkt/src/kkt.rs deleted file mode 100644 index c1d21982f3..0000000000 --- a/common/nym-kkt/src/kkt.rs +++ /dev/null @@ -1,449 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -//! Convenience wrappers around KKT protocol functions for easier integration. -//! -//! This module provides simplified APIs for the common use case of exchanging -//! KEM public keys between a client (initiator) and gateway (responder). -//! -//! The underlying KKT protocol is implemented in the `session` module. - -use nym_crypto::asymmetric::{ed25519, x25519}; -use rand09::{CryptoRng, RngCore}; - -use crate::{ - ciphersuite::{Ciphersuite, EncapsulationKey}, - context::{KKTContext, KKTMode}, - encryption::{decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_kkt_frame}, - error::KKTError, -}; - -// Re-export core session functions for advanced use cases -pub use crate::session::{ - anonymous_initiator_process, initiator_ingest_response, initiator_process, - responder_ingest_message, responder_process, -}; - -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. -/// -/// # Arguments -/// * `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 -/// * `Vec` - Contains the client's ephemeral public key and encrypted and signed bytes to send to responder -/// -/// # Example -/// ```ignore -/// let (session_secret, context, request_frame) = request_kem_key( -/// &mut rng, -/// ciphersuite, -/// client_signing_key, -/// responder_dh_public_key, -/// )?; -/// // Send request_frame to gateway -/// ``` -pub fn request_kem_key( - rng: &mut R, - ciphersuite: Ciphersuite, - signing_key: &ed25519::PrivateKey, - 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 - 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)) -} - -/// 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 -/// (typically retrieved from a directory service). -/// -/// # 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 -/// -/// # Returns -/// * `EncapsulationKey` - Authenticated KEM public key of the responder -/// -/// # Example -/// ```ignore -/// let gateway_kem_key = validate_kem_response( -/// &context, -/// &session_secret, -/// &gateway_verification_key, -/// &expected_hash_from_directory, -/// &response_bytes, -/// )?; -/// // Use gateway_kem_key for PSQ -/// ``` -pub fn validate_kem_response<'a>( - context: &mut KKTContext, - session_secret: &KKTSessionSecret, - responder_vk: &ed25519::PublicKey, - expected_key_hash: &[u8], - encrypted_response_bytes: &[u8], -) -> Result, KKTError> { - 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, - ) -} - -/// 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 -/// * `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 -/// * `KKTFrame` - Signed response frame containing the KEM public key -/// -/// # Example -/// ```ignore -/// let response_frame = handle_kem_request( -/// &request_frame, -/// Some(client_verification_key), // or None for anonymous -/// gateway_signing_key, -/// &gateway_kem_public_key, -/// )?; -/// // Send response_frame back to client -/// ``` -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, 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( - &initiator_context, - initiator_vk, - None, // Not checking initiator's KEM key in OneWay mode - &request_frame, - )?; - - // Generate signed response with our KEM public key - let responder_frame = responder_process( - &mut response_context, - 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)] -mod tests { - use super::*; - use crate::{ - ciphersuite::{HashFunction, KEM, SignatureScheme}, - key_utils::{generate_keypair_libcrux, hash_encapsulation_key}, - }; - - fn random_x25519_key() -> x25519::PrivateKey { - let mut bytes = [0u8; 32]; - let mut rng = rand09::rng(); - rng.fill_bytes(&mut bytes); - x25519::PrivateKey::from_secret(bytes) - } - - #[test] - fn test_kkt_wrappers_oneway_authenticated() { - let mut rng = rand09::rng(); - - // Generate Ed25519 keypairs for both parties - let mut initiator_secret = [0u8; 32]; - rng.fill_bytes(&mut initiator_secret); - let ed25519_init = ed25519::KeyPair::from_secret(initiator_secret, 0); - - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - 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(); - let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); - - // Create ciphersuite - let ciphersuite = Ciphersuite::resolve_ciphersuite( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - None, - ) - .unwrap(); - - // Hash the KEM key (simulating directory storage) - let key_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &responder_kem_key.encode(), - ); - - // Client: Request KEM key - let (session_key, context, request_frame_ciphertext) = request_kem_key( - &mut rng, - ciphersuite, - ed25519_init.private_key(), - &x25519_resp_pub, - ) - .unwrap(); - - // Gateway: Handle request - 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(); - - // Client: Validate response - let obtained_key = validate_kem_response( - &context, - &session_key, - ed25519_resp.public_key(), - &key_hash, - &response_frame_ciphertext, - ) - .unwrap(); - - // Verify we got the correct KEM key - assert_eq!(obtained_key.encode(), responder_kem_key.encode()); - } - - #[test] - fn test_kkt_wrappers_anonymous() { - let mut rng = rand09::rng(); - - // Only responder has keys - 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 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, - SignatureScheme::Ed25519, - None, - ) - .unwrap(); - - let key_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &responder_kem_key.encode(), - ); - - // Anonymous initiator - let (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( - &mut rng, - &encrypted_request_bytes, - None, // Anonymous - no verification key - responder_keypair.private_key(), - &x25519_resp_priv, - &responder_kem_key, - ) - .unwrap(); - - // Initiator: Validate response - let obtained_key = validate_kem_response( - &context, - &session_secret, - responder_keypair.public_key(), - &key_hash, - &response_frame, - ) - .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); - - let mut responder_secret = [0u8; 32]; - 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); - let wrong_keypair = ed25519::KeyPair::from_secret(wrong_secret, 2); - - let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); - let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); - - let ciphersuite = Ciphersuite::resolve_ciphersuite( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - None, - ) - .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( - &mut rng, - &request_frame_ciphertext, - Some(wrong_keypair.public_key()), // Wrong key! - responder_keypair.private_key(), - &x25519_resp_priv, - &responder_kem_key, - ); - - // Should fail signature verification - assert!(result.is_err()); - } - - #[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 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); - - let ciphersuite = Ciphersuite::resolve_ciphersuite( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - None, - ) - .unwrap(); - - // Use WRONG hash - let wrong_hash = [0u8; 32]; - - let (session_key, 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(); - - // Client validates with WRONG hash - let result = validate_kem_response( - &context, - &session_key, - responder_keypair.public_key(), - &wrong_hash, // Wrong! - &response_frame, - ); - - // Should fail hash validation - assert!(result.is_err()); - } -} diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index d879cf2286..d6c5dcbe49 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -1,498 +1,230 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub mod ciphersuite; -pub mod context; -pub mod encryption; +pub mod carrier; pub mod error; pub mod frame; +pub mod initiator; pub mod key_utils; -// pub mod kkt; -pub mod session; +pub mod keys; +pub mod masked_byte; +pub mod message; +pub mod rekey; +pub mod responder; -// 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"; +pub use nym_kkt_context as context; #[cfg(test)] mod test { + use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, HashLength, KEM, SignatureScheme}; + use rand09::RngCore; + + use crate::keys::KEMKeys; 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, + initiator::KKTInitiator, 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, + generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519, + hash_encapsulation_key, }, + responder::KKTResponder, }; #[test] - fn test_kkt_psq_e2e_clear() { + fn test_kkt_psq_e2e_encrypted_carrier() { let mut rng = rand09::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)); - - 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 (i_context, i_frame) = - anonymous_initiator_process(&mut rng, ciphersuite).unwrap(); - - let i_frame_bytes = i_frame.to_bytes(); - - let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); - - let (r_context, _) = - responder_ingest_message(&r_context, None, None, &i_frame_r).unwrap(); - - let r_frame = responder_process( - &r_context, - i_frame_r.session_id(), - responder_ed25519_keypair.private_key(), - &responder_kem_public_key, - ) - .unwrap(); - - 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( - &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 (i_context, i_frame) = initiator_process( - &mut rng, - crate::context::KKTMode::OneWay, - ciphersuite, - initiator_ed25519_keypair.private_key(), - None, - ) - .unwrap(); - - let i_frame_bytes = i_frame.to_bytes(); - - let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); - - let (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( - &r_context, - i_frame_r.session_id(), - responder_ed25519_keypair.private_key(), - &responder_kem_public_key, - ) - .unwrap(); - - 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( - &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 (i_context, i_frame) = initiator_process( - &mut rng, - crate::context::KKTMode::Mutual, - ciphersuite, - initiator_ed25519_keypair.private_key(), - Some(&initiator_kem_public_key), - ) - .unwrap(); - - let i_frame_bytes = i_frame.to_bytes(); - - let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); - - let (r_context, r_obtained_key) = responder_ingest_message( - &r_context, - 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( - &r_context, - i_frame_r.session_id(), - responder_ed25519_keypair.private_key(), - &responder_kem_public_key, - ) - .unwrap(); - - 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( - &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) - } - } - } - } - #[test] - fn test_kkt_psq_e2e_encrypted() { - let mut rng = rand09::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)); + let mut payload: Vec = vec![0u8; 900_000]; + rng.fill_bytes(&mut payload); // generate responder x25519 keys - let responder_x25519_keypair = generate_keypair_x25519(&mut rng); + let responder_x25519_keypair = generate_lp_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, - ] { + for hash_function in [ + HashFunction::Blake3, + HashFunction::SHA256, + HashFunction::Shake128, + HashFunction::Shake256, + ] { + // generate kem public keys + + let responder_mlkem_keypair = generate_keypair_mlkem(&mut rng); + let responder_mceliece_keypair = generate_keypair_mceliece(&mut rng); + + let responder_kem = KEMKeys::new(responder_mceliece_keypair, responder_mlkem_keypair); + + let r_dir_hash_mlkem = hash_encapsulation_key( + hash_function, + HashLength::Default.value(), + responder_kem.ml_kem768_encapsulation_key().as_slice(), + ); + + let r_dir_hash_mceliece = hash_encapsulation_key( + hash_function, + HashLength::Default.value(), + responder_kem.mc_eliece_encapsulation_key().as_ref(), + ); + let initiator_mlkem_keypair = generate_keypair_mlkem(&mut rng); + let initiator_mceliece_keypair = generate_keypair_mceliece(&mut rng); + + let _i_dir_hash_mlkem = hash_encapsulation_key( + hash_function, + HashLength::Default.value(), + initiator_mlkem_keypair.public_key().as_slice(), + ); + + let _i_dir_hash_mceliece = hash_encapsulation_key( + hash_function, + HashLength::Default.value(), + initiator_mceliece_keypair.pk.as_ref(), + ); + + let responder = KKTResponder::new( + &responder_x25519_keypair, + &responder_kem, + &[ + HashFunction::Blake3, + HashFunction::SHA256, + HashFunction::Shake128, + HashFunction::Shake256, + ], + &[SignatureScheme::Ed25519], + &[1], + ) + .unwrap(); + + // OneWay - MlKem + { let ciphersuite = Ciphersuite::resolve_ciphersuite( - kem, + KEM::MlKem768, hash_function, - crate::ciphersuite::SignatureScheme::Ed25519, + SignatureScheme::Ed25519, None, ) .unwrap(); + let (mut initiator, request) = KKTInitiator::generate_one_way_request( + &mut rng, + ciphersuite, + &responder_x25519_keypair.pk, + &r_dir_hash_mlkem, + 1u8, + Some(payload.clone()), + ) + .unwrap(); - // generate kem public keys + let processed_request = responder.process_request(request, payload.len()).unwrap(); - 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), - ), - }; + assert_eq!(processed_request.request_payload, payload); - 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 (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, - ) + let result = initiator + .process_response(processed_request.response, 0) .unwrap(); - // decryption - initiator frame + assert_eq!( + result.encapsulation_key.as_bytes(), + responder_kem.ml_kem768_encapsulation_key().as_slice(), + ) + } + // Mutual - MlKem + { + let ciphersuite = Ciphersuite::resolve_ciphersuite( + KEM::MlKem768, + hash_function, + SignatureScheme::Ed25519, + None, + ) + .unwrap(); + let (mut initiator, request) = KKTInitiator::generate_one_way_request( + &mut rng, + ciphersuite, + &responder_x25519_keypair.pk, + &r_dir_hash_mlkem, + 1u8, + Some(payload.clone()), + ) + .unwrap(); - let (r_session_secret, i_frame_r, i_context_r) = - decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes) - .unwrap(); + let processed_request = responder.process_request(request, payload.len()).unwrap(); - let (r_context, _) = - responder_ingest_message(&i_context_r, None, None, &i_frame_r).unwrap(); + assert_eq!(processed_request.request_payload, payload); - let r_frame = responder_process( - &r_context, - i_frame_r.session_id(), - responder_ed25519_keypair.private_key(), - &responder_kem_public_key, - ) + // if we keep unverified keys, this should change + assert!(processed_request.remote_encapsulation_key.is_none()); + + let processed_response = initiator + .process_response(processed_request.response, 0) .unwrap(); - // encryption - responder frame - let r_bytes = - encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, KKT_RESPONSE_AAD) - .unwrap(); + assert_eq!( + processed_response.encapsulation_key.as_bytes(), + responder_kem.ml_kem768_encapsulation_key().as_slice(), + ) + } - // decryption - responder frame + // OneWay - McEliece + { + let ciphersuite = Ciphersuite::resolve_ciphersuite( + KEM::McEliece, + hash_function, + SignatureScheme::Ed25519, + None, + ) + .unwrap(); + let (mut initiator, request) = KKTInitiator::generate_one_way_request( + &mut rng, + ciphersuite, + &responder_x25519_keypair.pk, + &r_dir_hash_mceliece, + 1u8, + Some(payload.clone()), + ) + .unwrap(); - let (i_frame_r, i_context_r) = - decrypt_kkt_frame(&i_session_secret, &r_bytes, KKT_RESPONSE_AAD).unwrap(); + let processed_request = responder.process_request(request, payload.len()).unwrap(); + assert_eq!(processed_request.request_payload, payload); - let i_obtained_key = initiator_ingest_response( - &i_context, - &i_frame_r, - &i_context_r, - responder_ed25519_keypair.public_key(), - &r_dir_hash, - ) + let processed_response = initiator + .process_response(processed_request.response, 0) .unwrap(); - assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) - } - // Initiator, OneWay - { - let (i_context, i_frame) = initiator_process( - &mut rng, - crate::context::KKTMode::OneWay, - ciphersuite, - initiator_ed25519_keypair.private_key(), - None, - ) + assert_eq!( + processed_response.encapsulation_key.as_bytes(), + responder_kem.mc_eliece_encapsulation_key().as_ref() + ) + } + // Mutual - MlKem + { + let ciphersuite = Ciphersuite::resolve_ciphersuite( + KEM::McEliece, + hash_function, + SignatureScheme::Ed25519, + None, + ) + .unwrap(); + let (mut initiator, request) = KKTInitiator::generate_one_way_request( + &mut rng, + ciphersuite, + &responder_x25519_keypair.pk, + &r_dir_hash_mceliece, + 1u8, + Some(payload.clone()), + ) + .unwrap(); + + let processed_request = responder.process_request(request, payload.len()).unwrap(); + + assert_eq!(processed_request.request_payload, payload); + + // if we keep unverified keys, this should change + assert!(processed_request.remote_encapsulation_key.is_none()); + + let processed_response = initiator + .process_response(processed_request.response, 0) .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 (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( - &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( - &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 (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 (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( - &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( - &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) - } + assert_eq!( + processed_response.encapsulation_key.as_bytes(), + responder_kem.mc_eliece_encapsulation_key().as_ref() + ) } } } diff --git a/common/nym-kkt/src/masked_byte.rs b/common/nym-kkt/src/masked_byte.rs new file mode 100644 index 0000000000..2bdd29edae --- /dev/null +++ b/common/nym-kkt/src/masked_byte.rs @@ -0,0 +1,189 @@ +use nym_crypto::{blake3, hmac::hmac::digest::ExtendableOutput}; + +use crate::error::{ + MaskedByteError, + MaskedByteError::{Failure, InvalidLength}, +}; + +pub const MASKED_BYTE_LEN: usize = 16; +pub const MASKED_BYTE_CONTEXT_STR: &[u8] = b"NYM_MASKED_BYTE_V1"; + +const U8_RANGE: [u8; 256] = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, + 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, + 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, + 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, + 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, + 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, + 250, 251, 252, 253, 254, 255, +]; + +#[derive(Clone, Copy)] +pub struct MaskedByte([u8; MASKED_BYTE_LEN]); + +impl MaskedByte { + /// Mask a byte by hashing it with some mask. + /// Outputs Blake3_Hash(MASKED_BYTE_CONTEXT_STR || mask || 0xFF || byte) + pub fn new(byte: u8, mask: &[u8]) -> Self { + let mut output: [u8; MASKED_BYTE_LEN] = [0u8; MASKED_BYTE_LEN]; + let mut hasher = blake3::Hasher::new(); + hasher.update(MASKED_BYTE_CONTEXT_STR); + hasher.update(mask); + // avoid zero update + hasher.update(&[0xFF, byte]); + hasher.finalize_xof_into(&mut output); + + Self(output) + } + /// Unmasks a byte by trial hashing. + /// This function runs Blake3_Hash(MASKED_BYTE_CONTEXT_STR || mask || 0xFF). + /// This Hasher state is then cloned updated with `i: u8` in (0..=u8::max). + /// If we find an `i` which yields back the hash input, then we found the masked byte. + /// Otherwise, the function returns an error. + pub fn unmask(&self, mask: &[u8]) -> Result { + self.unmask_check_version(mask, &U8_RANGE) + } + + // This could be more efficient than unmask, + // because we just could check against a smaller list of supported versions. + pub fn unmask_check_version( + &self, + mask: &[u8], + supported_versions: &[u8], + ) -> Result { + let mut buf: [u8; MASKED_BYTE_LEN] = [0u8; MASKED_BYTE_LEN]; + let mut hasher = blake3::Hasher::new(); + hasher.update(MASKED_BYTE_CONTEXT_STR); + hasher.update(mask); + // avoid zero update + hasher.update(&[0xFF]); + for i in supported_versions { + let mut t_hasher = hasher.clone(); + t_hasher.update(&[*i]); + t_hasher.finalize_xof_into(&mut buf); + if buf == self.0 { + return Ok(*i); + } + } + Err(Failure) + } + + pub fn as_slice(&self) -> &[u8] { + &self.0 + } + + pub fn to_bytes(self) -> [u8; MASKED_BYTE_LEN] { + self.0 + } +} + +impl From<[u8; MASKED_BYTE_LEN]> for MaskedByte { + fn from(value: [u8; MASKED_BYTE_LEN]) -> Self { + MaskedByte(value) + } +} + +impl From<&[u8; MASKED_BYTE_LEN]> for MaskedByte { + fn from(value: &[u8; MASKED_BYTE_LEN]) -> Self { + MaskedByte(*value) + } +} + +impl TryFrom<&[u8]> for MaskedByte { + type Error = MaskedByteError; + + fn try_from(value: &[u8]) -> Result { + let Ok(inner) = value.try_into() else { + return Err(InvalidLength { + expected: MASKED_BYTE_LEN, + actual: value.len(), + }); + }; + Ok(MaskedByte(inner)) + } +} + +#[cfg(test)] +mod test { + + use crate::masked_byte::MASKED_BYTE_LEN; + + use super::MaskedByte; + use rand09::{Rng, RngCore, rng}; + + #[test] + fn test_masking() { + let mut mask: [u8; 256] = [0u8; 256]; + let mut wire_bytes: [u8; MASKED_BYTE_LEN]; + + // why not + for i in 0..=u8::MAX { + // gen mask + rng().fill_bytes(&mut mask); + let masked_byte = MaskedByte::new(i, &mask); + wire_bytes = masked_byte.to_bytes(); + + let decoded_masked_byte = MaskedByte::from(wire_bytes); + let output = decoded_masked_byte.unmask(&mask).unwrap(); + + assert_eq!(i, output); + + // flip bit + let mut with_flipped_bit = decoded_masked_byte.to_bytes(); + + let byte_idx: usize = rng().random_range(0..MASKED_BYTE_LEN); + let bit_idx = rng().random_range(0..8); + with_flipped_bit[byte_idx] ^= 1 << bit_idx; + + let decoded_masked_byte = MaskedByte::from(with_flipped_bit); + assert!(decoded_masked_byte.unmask(&mask).is_err()); + } + } + + #[test] + fn test_decoding() { + let mut mask: [u8; 256] = [0u8; 256]; + + // gen mask + rng().fill_bytes(&mut mask); + let byte = rng().random(); + let masked_byte = MaskedByte::new(byte, &mask); + let wire_bytes: [u8; MASKED_BYTE_LEN] = masked_byte.to_bytes(); + + // should succeed + let decoded_masked_byte = MaskedByte::try_from(wire_bytes.as_slice()).unwrap(); + let output = decoded_masked_byte.unmask(&mask).unwrap(); + + assert_eq!(byte, output); + + let empty_slice: &[u8] = &[]; + // should fail + assert!(MaskedByte::try_from(empty_slice).is_err()); + + let mut wire_bytes_messy = Vec::from(wire_bytes); + + // add more one more byte + wire_bytes_messy.push(0x42); + assert_eq!(wire_bytes_messy.len(), MASKED_BYTE_LEN + 1); + // should fail + assert!(MaskedByte::try_from(wire_bytes_messy.as_slice()).is_err()); + + // pop the added byte + _ = wire_bytes_messy.pop(); + assert_eq!(wire_bytes_messy.len(), MASKED_BYTE_LEN); + // should succeed + assert!(MaskedByte::try_from(wire_bytes_messy.as_slice()).is_ok()); + + // pop one more byte + _ = wire_bytes_messy.pop(); + assert_eq!(wire_bytes_messy.len(), MASKED_BYTE_LEN - 1); + // should fail + assert!(MaskedByte::try_from(wire_bytes_messy.as_slice()).is_err()); + } +} diff --git a/common/nym-kkt/src/message.rs b/common/nym-kkt/src/message.rs new file mode 100644 index 0000000000..2d2e6f3361 --- /dev/null +++ b/common/nym-kkt/src/message.rs @@ -0,0 +1,265 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::carrier::Carrier; +use crate::context::{KKTContext, KKTMode, KKTRole}; +use crate::error::KKTError; +use crate::frame::KKTFrame; +use crate::keys::EncapsulationKey; +use crate::masked_byte::{MASKED_BYTE_LEN, MaskedByte}; +use libcrux_chacha20poly1305::TAG_LEN; +use libcrux_psq::handshake::types::{DHKeyPair, DHPrivateKey, DHPublicKey}; +use nym_kkt_ciphersuite::{KEM, x25519}; + +pub struct KKTRequest { + /// The plaintext part of the request + pub(crate) plaintext: KKTRequestPlaintext, + + /// Ciphertext of an initial request `KKTFrame` + pub(crate) encrypted_frame: Vec, +} + +impl KKTRequest { + // the size of KKTRequest is the plaintext data followed by the frame and the encryption tag + pub const fn size_excluding_payload(mode: KKTMode, kem: KEM) -> usize { + KKTRequestPlaintext::SIZE + + KKTFrame::size_excluding_payload(KKTRole::Initiator, mode, kem) + + TAG_LEN + } + + pub fn size(&self) -> usize { + self.encrypted_frame.len() + KKTRequestPlaintext::SIZE + } + + pub fn into_bytes(mut self) -> Vec { + let mut out = self.plaintext.to_bytes(); + out.append(&mut self.encrypted_frame); + out + } + + pub fn try_from_bytes(b: &[u8]) -> Result { + if b.len() < x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN { + return Err(KKTError::FrameDecodingError { + info: "the KKTRequest frame has invalid length".to_string(), + }); + } + let plaintext = + KKTRequestPlaintext::try_from_bytes(&b[..x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN])?; + + Ok(KKTRequest { + plaintext, + encrypted_frame: b[x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN..].to_vec(), + }) + } +} + +pub(crate) struct KKTRequestPlaintext { + /// Ephemeral Diffie-Hellman public key of the initiator + pub(crate) dh_pubkey: DHPublicKey, + + /// Masked bytes representing the outer protocol version information + pub(crate) masked_version_bytes: MaskedByte, +} + +impl KKTRequestPlaintext { + pub const SIZE: usize = x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN; + + pub(crate) fn new( + initiator_pubkey: DHPublicKey, + responder_pubkey: &DHPublicKey, + outer_protocol_version: u8, + ) -> Self { + let mask = Self::create_version_mask(&initiator_pubkey, responder_pubkey); + let masked_version_bytes = MaskedByte::new(outer_protocol_version, &mask); + KKTRequestPlaintext { + dh_pubkey: initiator_pubkey, + masked_version_bytes, + } + } + + pub(crate) fn into_request( + self, + carrier: &mut Carrier, + frame: KKTFrame, + ) -> Result { + let frame_bytes = frame.try_to_bytes()?; + let frame_ciphertext = carrier.encrypt(&frame_bytes)?; + Ok(KKTRequest { + plaintext: self, + encrypted_frame: frame_ciphertext, + }) + } + + pub(crate) fn create_version_mask( + initiator_pubkey: &DHPublicKey, + responder_pubkey: &DHPublicKey, + ) -> Vec { + let mut mask = Vec::with_capacity(2 * x25519::PUBLIC_KEY_LENGTH); + mask.extend_from_slice(initiator_pubkey.as_ref()); + mask.extend_from_slice(responder_pubkey.as_ref()); + mask + } + + fn create_carrier_ctx( + masked_version: &MaskedByte, + initiator_pubkey: &DHPublicKey, + responder_pubkey: &DHPublicKey, + ) -> Vec { + let mut context = Vec::new(); + context.extend_from_slice(masked_version.as_slice()); + context.extend_from_slice(crate::frame::KKT_CARRIER_CONTEXT); + context.extend_from_slice(initiator_pubkey.as_ref()); + context.extend_from_slice(responder_pubkey.as_ref()); + context + } + + pub(crate) fn to_bytes(&self) -> Vec { + let mut out = Vec::with_capacity(x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN); + out.extend_from_slice(self.dh_pubkey.as_ref()); + out.extend_from_slice(self.masked_version_bytes.as_slice()); + out + } + + pub(crate) fn try_from_bytes(b: &[u8]) -> Result { + if b.len() != x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN { + return Err(KKTError::FrameDecodingError { + info: "the KKTRequest frame has invalid length".to_string(), + }); + } + // SAFETY: we're using exactly 32 byte + #[allow(clippy::unwrap_used)] + let dh_pubkey = + DHPublicKey::from_bytes(&b[..x25519::PUBLIC_KEY_LENGTH].try_into().unwrap()); + let masked_version_bytes = MaskedByte::try_from(&b[x25519::PUBLIC_KEY_LENGTH..])?; + + Ok(KKTRequestPlaintext { + dh_pubkey, + masked_version_bytes, + }) + } + + pub(crate) fn version_mask(&self, responder_pubkey: &DHPublicKey) -> Vec { + Self::create_version_mask(&self.dh_pubkey, responder_pubkey) + } + + pub(crate) fn derive_initiator_carrier( + &self, + initiator_sk: &DHPrivateKey, + responder_pubkey: &DHPublicKey, + ) -> Result { + let ctx = Self::create_carrier_ctx( + &self.masked_version_bytes, + &self.dh_pubkey, + responder_pubkey, + ); + + let shared_secret = initiator_sk + .diffie_hellman(responder_pubkey) + .map_err(KKTError::shared_secret_derivation_failure)?; + + Ok(Carrier::from_secret_slice( + shared_secret.as_ref(), + &ctx, + true, + )) + } + + pub(crate) fn derive_responder_carrier( + &self, + responder_keys: &DHKeyPair, + ) -> Result { + let ctx = Self::create_carrier_ctx( + &self.masked_version_bytes, + &self.dh_pubkey, + &responder_keys.pk, + ); + let shared_secret = responder_keys + .sk() + .diffie_hellman(&self.dh_pubkey) + .map_err(KKTError::shared_secret_derivation_failure)?; + Ok(Carrier::from_secret_slice( + shared_secret.as_ref(), + &ctx, + false, + )) + } +} + +pub struct KKTRequestEncryptionResult { + /// Derived carrier used for decrypting this frame and encrypting the response + pub(crate) carrier: Carrier, + + /// The underlying request that is going to get sent to the remote + pub(crate) request: KKTRequest, +} + +pub struct DecryptedRequestFrame { + /// Derived carrier used for decrypting this frame and encrypting the response + pub(crate) carrier: Carrier, + + /// The remote frame sent in the message + pub(crate) remote_frame: KKTFrame, + + /// The unmasked byte representing the outer protocol version sent by the initiator + pub(crate) outer_protocol_version: u8, +} + +impl DecryptedRequestFrame { + pub(crate) fn remote_context(&self) -> &KKTContext { + self.remote_frame.context() + } +} + +pub struct ProcessedKKTRequest { + pub response: KKTResponse, + + /// The obtained encapsulation key of the remote + pub remote_encapsulation_key: Option, + + /// The KEM key requested in the original request + pub requested_kem: KEM, + + /// The unmasked byte representing the outer protocol version sent by the initiator + pub outer_protocol_version: u8, + + // Request payload data (Could be empty. Contents are unrelated to current KKT execution). + pub request_payload: Vec, +} + +pub struct KKTResponse { + /// Encrypted KKT frame that is going to be sent back to the initiator + pub encrypted_frame: Vec, +} + +impl KKTResponse { + // the size of KKTRequest is the plaintext data followed by the frame and the encryption tag + pub const fn size_excluding_payload(kem: KEM) -> usize { + // `KKTMode` argument makes no difference for the Responder role + KKTFrame::size_excluding_payload(KKTRole::Responder, KKTMode::OneWay, kem) + TAG_LEN + } + + pub fn size(&self) -> usize { + self.encrypted_frame.len() + } + + pub fn from_bytes(bytes: Vec) -> KKTResponse { + KKTResponse { + encrypted_frame: bytes, + } + } + + pub fn into_bytes(self) -> Vec { + self.encrypted_frame + } +} + +pub struct ProcessedKKTResponse { + /// The obtained encapsulation key of the remote + pub encapsulation_key: EncapsulationKey, + + /// Indicates whether responder was able to verify the initiator's kem key, + pub verified_initiator_kem_key: bool, + + /// Optional response payload (Could be empty. Contents are unrelated to current KKT execution). + pub response_payload: Vec, +} diff --git a/common/nym-kkt/src/rekey.rs b/common/nym-kkt/src/rekey.rs new file mode 100644 index 0000000000..dc8fb273df --- /dev/null +++ b/common/nym-kkt/src/rekey.rs @@ -0,0 +1,257 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +//! Post-Quantum Re-Key Protocol + +/// This module implements a stateless post-quantum re-keying protocol in one round-trip. +/// We currently support MlKem768 and XWing. +/// +/// This protocol is safe if it runs under a trusted secure channel. +/// +/// Bandwidth costs: +/// Request (MlKem768): 1216 bytes +/// Response (MlKem768): 1088 bytes +/// Request (XWing): 1248 bytes +/// Response (XWing): 1120 bytes +use libcrux_kem::*; +use nym_crypto::hkdf::blake3::derive_key_blake3; +use nym_kkt_ciphersuite::{KEM, mceliece, ml_kem768, x25519, xwing}; +use rand09::{CryptoRng, RngCore}; +use std::fmt::{Debug, Formatter}; +use zeroize::Zeroize; + +use crate::error::KKTError; + +/// Context string to be used with the Blake3 KDF. +const REKEY_CONTEXT: &str = "NYM_PQ_REKEY_v1"; + +pub struct RekeyInitiator { + algorithm: Algorithm, + decapsulation_key: PrivateKey, + salt: [u8; 32], +} + +impl Debug for RekeyInitiator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let key_typ = match self.decapsulation_key { + PrivateKey::X25519(_) => "x25519", + PrivateKey::P256(_) => "p256", + PrivateKey::MlKem512(_) => "ml512", + PrivateKey::MlKem768(_) => "mlkem768", + PrivateKey::X25519MlKem768Draft00(_) => "x25519-mlkem768", + PrivateKey::XWingKemDraft06(_) => "xwing", + PrivateKey::MlKem1024(_) => "ml1024", + }; + + f.debug_struct("RekeyInitiator") + .field("algorithm", &self.algorithm) + .field("decapsulation_key", &key_typ) + .field("salt", &self.salt) + .finish() + } +} + +impl RekeyInitiator { + /// The Initiator generates an ephemeral KEM keypair and a 32-byte salt. + /// The Initiator keeps the decapsulation key and generates a request message. + /// The request message contains the salt and an encoding of the encapsulation key as follows + /// salt encapsulation_key + /// [0 ........ 32 | 32 .............. ] + /// + /// Inputs: + /// rng: something that implements CryptoRng + RngCore + /// kem: a KEM algorithm (we currently support MlKem768 and XWing) + /// + /// Outputs: + /// RekeyInitiator: A struct which contains the decapsulation key, the salt and the kem algorithm in use. + /// Vec: The request message as explained above. This is to be sent to the responder as-is. + pub fn generate_request(rng: &mut R, kem: KEM) -> Result<(RekeyInitiator, Vec), KKTError> + where + R: CryptoRng + RngCore, + { + let (algorithm, buffer_size) = match kem { + // KEM::XWing => (Algorithm::XWingKemDraft06, 32 + xwing::PUBLIC_KEY_LENGTH), + KEM::MlKem768 => (Algorithm::MlKem768, 32 + ml_kem768::PUBLIC_KEY_LENGTH), + // We don't support McEliece because the keys are massive. + // If this is a deal-breaker, users can start a new session with PSQ which can use McEliece. + KEM::McEliece => { + return Err(KKTError::UnsupportedAlgorithm { + info: "McEliece is not supported for re-keying", + }); + } + }; + + // Generate the Initiator's salt + let mut salt = [0u8; 32]; + rng.fill_bytes(&mut salt); + + // Create the buffer for the request message and copy the salt into it. + let mut request_buffer = Vec::with_capacity(buffer_size); + request_buffer.extend_from_slice(&salt); + + // Generate the ephemeral KEM keypair based on the algorithm from the function's input. + let (decapsulation_key, encapsulation_key) = key_gen(algorithm, rng)?; + + // Append the encoding of the KEM encapsulation key to the initiator's randomness. + request_buffer.extend(encapsulation_key.encode()); + + Ok(( + // The Initiator should store this until they use `RekeyInitiator::finalize`. + RekeyInitiator { + algorithm, + decapsulation_key, + salt, + }, + // This is to be sent to the responder. + request_buffer, + )) + } + + /// The Initiator will attempt to decapsulate the `pre_key` generated by the responder + /// secret. This `pre_key` will be combined with the Initiator's previously generated salt + /// as input to a Blake3 KDF call to generate the new shared secret. + /// + /// This function fails if the ciphertext cannot be decoded or decapsulated. + /// + /// Input: + /// response_message: the responder's message which contains an encapsulation of `pre_key`. + /// Output: + /// [u8; 32]: the new shared secret. + pub fn finalize(mut self, response_message: &[u8]) -> Result<[u8; 32], KKTError> { + // Decode the responder's ciphertext. + let ciphertext = Ct::decode(self.algorithm, response_message)?; + // Decapsulate the `pre_key` using the Initiator's decapsulation key. + let pre_key = ciphertext.decapsulate(&self.decapsulation_key)?; + + // Encode the `pre_key` into bytes + let pre_key_bytes = pre_key.encode(); + + let new_secret: [u8; 32] = derive_key_blake3(REKEY_CONTEXT, &pre_key_bytes, &self.salt); + + // Zeroize the Initiator's salt + self.salt.zeroize(); + + // TODO: zeroize the decapsulation key + + Ok(new_secret) + } +} + +/// The responder parses the request message. +/// The first 32 bytes are the Initiator's salt, +/// and the remainder is the encoding of the public key. +/// Given that XWing and MlKem768 have different key lengths, +/// we could deduce the algorithm from that. +/// +/// If the message is badly formatted, or the encapsulation received is invalid, +/// this function will produce an error. +/// +/// If everything is alright, the responder generates and encapsulates a key `pre_key` to send to the Initiator. +/// Then, the responder calls a Blake3 KDF over `pre_key` and the Initiator's salt to obtain +/// the new shared secret. +/// +/// Inputs: +/// rng: something that implements CryptoRng + RngCore +/// request_message: the Initiator's request message (contains the salt and encapsulation key) +/// +/// Outputs: +/// [u8; 32]: new shared secret +/// Vec: response which contains an encapsulation of a secret value generated by the responder. +/// This is to be sent back to the Initiator as-is. +pub fn responder_process( + rng: &mut R, + mut request_message: Vec, +) -> Result<([u8; 32], Vec), KKTError> +where + R: CryptoRng + RngCore, +{ + // Deduce the KEM algorithm from the message length + let algorithm = match request_message.len().checked_sub(32) { + // + Some(num) => match num { + // If message length is 1216 (32 + 1184) then the algorithm should be MlKem768 + ml_kem768::PUBLIC_KEY_LENGTH => Algorithm::MlKem768, + // If message length is 1248 (32 + 1216) then the algorithm should be MlKem768 + xwing::PUBLIC_KEY_LENGTH => Algorithm::XWingKemDraft06, + // We don't support McEliece because the keys are massive. + // If this is a deal-breaker, users can start a new session with PSQ which can use McEliece. + mceliece::PUBLIC_KEY_LENGTH => { + return Err(KKTError::UnsupportedAlgorithm { + info: "McEliece is not supported for re-keying", + }); + } + // We don't support X25519 because it's not post-quantum secure. + x25519::PUBLIC_KEY_LENGTH => { + return Err(KKTError::UnsupportedAlgorithm { + info: "McEliece is not supported for re-keying", + }); + } + // Reject if the size does not match any of the above. + _ => { + return Err(KKTError::UnsupportedAlgorithm { + info: "Unknown Algorithm", + }); + } + }, + // Reject if message length is less than 32. + None => { + return Err(KKTError::DecodingError { + info: "Invalid rekey request: size is too small", + }); + } + }; + + // Split the message to get the Initiator's salt (first 32 bytes) + // and the encoding of the Initiator's public key. + let (remote_salt, remote_encapsulation_key_bytes) = request_message.split_at_mut(32); + + // Attempt to decode the Initiator's encapsulation key. + let remote_encapsulation_key = PublicKey::decode(algorithm, remote_encapsulation_key_bytes)?; + + // Encapsulate a fresh `pre_key` using the Initiator's encapsulation key into `ciphertext`. + let (pre_key, ciphertext) = remote_encapsulation_key.encapsulate(rng)?; + // Encode the ciphertext into bytes to send back to the initiator. + let message = ciphertext.encode(); + + // Encode the `pre_key` into bytes + let pre_key_bytes = pre_key.encode(); + + let new_secret: [u8; 32] = derive_key_blake3(REKEY_CONTEXT, &pre_key_bytes, remote_salt); + + // Zeroize the Initiator's salt + remote_salt.zeroize(); + + Ok((new_secret, message)) +} + +#[cfg(test)] +mod tests { + use crate::error::KKTError; + use crate::rekey::{RekeyInitiator, responder_process}; + use nym_kkt_ciphersuite::KEM; + + #[test] + fn rekey_test() { + let mut rng = rand09::rng(); + + let (rekey_state, request_message) = + RekeyInitiator::generate_request(&mut rng, KEM::MlKem768).unwrap(); + + let (responder_secret, response_message) = + responder_process(&mut rng, request_message).unwrap(); + + let initiator_secret = rekey_state.finalize(&response_message).unwrap(); + + assert_eq!(initiator_secret, responder_secret); + + // mceliece should fail + let err = RekeyInitiator::generate_request(&mut rng, KEM::McEliece).unwrap_err(); + assert_eq!( + err.to_string(), + KKTError::UnsupportedAlgorithm { + info: "McEliece is not supported for re-keying", + } + .to_string() + ) + } +} diff --git a/common/nym-kkt/src/responder.rs b/common/nym-kkt/src/responder.rs new file mode 100644 index 0000000000..f7aac86269 --- /dev/null +++ b/common/nym-kkt/src/responder.rs @@ -0,0 +1,196 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::key_utils::validate_encapsulation_key; +use crate::keys::{EncapsulationKey, KEMKeys}; +use crate::message::{KKTRequest, KKTResponse, ProcessedKKTRequest}; +use crate::{ + context::{KKTContext, KKTMode, KKTRole, KKTStatus}, + error::KKTError, + frame::KKTFrame, +}; +use libcrux_psq::handshake::types::DHKeyPair; +use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, SignatureScheme}; + +/// Representation of a KKT Responder +pub struct KKTResponder<'a> { + /// Long-term x25519 DH key pair of this Responder + x25519_keypair: &'a DHKeyPair, + + /// KEM keys of this responder + kem_keys: &'a KEMKeys, + + /// List of supported Hash Functions by this Responder + supported_hash_functions: Vec, + + /// List of supported Signature Schemes by this Responder + supported_signature_schemes: Vec, + + /// List of supported outer (LP) protocol version by this Responder + supported_outer_protocol_versions: Vec, +} + +impl<'a> KKTResponder<'a> { + pub fn new( + x25519_keypair: &'a DHKeyPair, + kem_keys: &'a KEMKeys, + supported_hash_functions: &[HashFunction], + supported_signature_schemes: &[SignatureScheme], + supported_outer_protocol_versions: &[u8], + ) -> Result { + if supported_hash_functions.is_empty() { + return Err(KKTError::FunctionInputError { + info: "Did not provide a supported HashFunction when instantiating a KKTResponder", + }); + } + + if supported_signature_schemes.is_empty() { + return Err(KKTError::FunctionInputError { + info: "Did not provide a supported SignatureScheme when instantiating a KKTResponder", + }); + } + + if supported_outer_protocol_versions.is_empty() { + return Err(KKTError::FunctionInputError { + info: "Did not provide a supported outer protocol version when instantiating a KKTResponder", + }); + } + + Ok(Self { + x25519_keypair, + kem_keys, + supported_hash_functions: supported_hash_functions.to_vec(), + supported_signature_schemes: supported_signature_schemes.to_vec(), + supported_outer_protocol_versions: supported_outer_protocol_versions.to_vec(), + }) + } + + fn check_ciphersuite_compatiblity( + &self, + remote_ciphersuite: Ciphersuite, + ) -> Result<(), KKTError> { + let r_hash = remote_ciphersuite.hash_function(); + let r_sig = remote_ciphersuite.signature_scheme(); + + if !self.supported_hash_functions.contains(&r_hash) { + return Err(KKTError::IncompatibilityError { + info: "Unsupported HashFunction", + }); + } + + if !self.supported_signature_schemes.contains(&r_sig) { + return Err(KKTError::IncompatibilityError { + info: "Unsupported SignatureScheme", + }); + } + + Ok(()) + } + + // When this function fails, we do that silently (i.e. we don't generate a response to the initiator). + + pub fn process_request( + &self, + request: KKTRequest, + request_payload_len: usize, + ) -> Result { + let processed_req = KKTFrame::decrypt_initiator_frame( + self.x25519_keypair, + request, + &self.supported_outer_protocol_versions, + request_payload_len, + )?; + + let remote_context = *processed_req.remote_context(); + let remote_frame = processed_req.remote_frame; + let request_payload = remote_frame.payload().to_vec(); + let mut carrier = processed_req.carrier; + + self.check_ciphersuite_compatiblity(remote_context.ciphersuite())?; + + let (local_context, remote_encapsulation_key) = match remote_context.mode() { + KKTMode::OneWay => responder_ingest_message(None, remote_frame)?, + KKTMode::Mutual => { + // So we can either fetch the remote hash here using some async call to the directory, + // which might make registration hang or accept the sent key then verify later. + + // If we choose to not accept, the response's status will be KKTStatus::UnverifiedKEMKey. + // The response would still contain the responder's encapsulation key. + responder_ingest_message(None, remote_frame)? + } + }; + + let kem = local_context.ciphersuite().kem(); + let Some(kem_key) = self.kem_keys.encoded_encapsulation_key(kem) else { + return Err(KKTError::IncompatibilityError { + info: "Unsupported KEM", + }); + }; + + // for now the response payload is empty + let response_payload = Vec::with_capacity(0); + + let frame = KKTFrame::new(local_context, kem_key, response_payload); + + // encryption - responder frame + let encrypted_frame = carrier.encrypt(&frame.try_to_bytes()?)?; + Ok(ProcessedKKTRequest { + response: KKTResponse { encrypted_frame }, + remote_encapsulation_key, + requested_kem: remote_context.ciphersuite().kem(), + outer_protocol_version: processed_req.outer_protocol_version, + request_payload, + }) + } +} + +pub fn responder_ingest_message( + expected_hash: Option<&[u8]>, + remote_frame: KKTFrame, +) -> Result<(KKTContext, Option), KKTError> { + let remote_context = remote_frame.context(); + let mut own_context = remote_context.derive_responder_header()?; + let cs = own_context.ciphersuite(); + + match remote_context.role() { + KKTRole::Initiator => { + // using own_context here because maybe for whatever reason we want to ignore the remote kem key + match own_context.mode() { + KKTMode::OneWay => Ok((own_context, None)), + KKTMode::Mutual => { + let Some(expected_hash) = expected_hash else { + own_context.update_status(KKTStatus::UnverifiedKEMKey); + // we don't store an unverified key + // changing the status notifies the initiator that we didn't + + // we could still keep it here and then verify later... + // let received_encapsulation_key = EncapsulationKey::decode( + // own_context.ciphersuite().kem(), + // remote_frame.body_ref(), + // )?; + // Ok((own_context, Some(received_encapsulation_key))) + // + return Ok((own_context, None)); + }; + + if !validate_encapsulation_key( + cs.hash_function(), + cs.hash_len(), + remote_frame.body_ref(), + expected_hash, + ) { + // The key does not match the hash obtained from the directory + return Err(KKTError::MismatchedKEMHash); + } + let remote_key = + EncapsulationKey::try_from_bytes(remote_frame.body(), cs.kem())?; + Ok((own_context, Some(remote_key))) + } + } + } + + KKTRole::Responder => Err(KKTError::IncompatibilityError { + info: "Responder received a request from another responder.", + }), + } +} diff --git a/common/nym-kkt/src/session.rs b/common/nym-kkt/src/session.rs deleted file mode 100644 index 40db3d275d..0000000000 --- a/common/nym-kkt/src/session.rs +++ /dev/null @@ -1,230 +0,0 @@ -use nym_crypto::asymmetric::ed25519::{self, Signature}; -use rand09::{CryptoRng, RngCore}; - -use crate::frame::KKTSessionId; -use crate::{ - ciphersuite::{Ciphersuite, EncapsulationKey}, - context::{KKTContext, KKTMode, KKTRole, KKTStatus}, - error::KKTError, - frame::{KKT_SESSION_ID_LEN, KKTFrame}, - key_utils::validate_encapsulation_key, -}; - -pub fn initiator_process<'a, R>( - rng: &mut R, - mode: KKTMode, - ciphersuite: Ciphersuite, - signing_key: &ed25519::PrivateKey, - own_encapsulation_key: Option<&EncapsulationKey<'a>>, -) -> Result<(KKTContext, KKTFrame), KKTError> -where - R: CryptoRng + RngCore, -{ - let context = KKTContext::new(KKTRole::Initiator, mode, ciphersuite)?; - - let context_bytes = context.encode()?; - - let mut session_id = [0; KKT_SESSION_ID_LEN]; - // Generate Session ID - rng.fill_bytes(&mut session_id); - - let body: &[u8] = match mode { - KKTMode::OneWay => &[], - KKTMode::Mutual => match own_encapsulation_key { - Some(encaps_key) => &encaps_key.encode(), - - // Missing key - None => { - return Err(KKTError::FunctionInputError { - info: "KEM Key Not Provided", - }); - } - }, - }; - - let mut bytes_to_sign = - Vec::with_capacity(context.full_message_len() - context.signature_len()); - bytes_to_sign.extend_from_slice(&context_bytes); - bytes_to_sign.extend_from_slice(body); - bytes_to_sign.extend_from_slice(&session_id); - - let signature = signing_key.sign(bytes_to_sign).to_bytes(); - - Ok(( - context, - KKTFrame::new(context_bytes, body, session_id, &signature), - )) -} - -pub fn anonymous_initiator_process( - rng: &mut R, - ciphersuite: Ciphersuite, -) -> Result<(KKTContext, KKTFrame), KKTError> -where - R: CryptoRng + RngCore, -{ - let context = KKTContext::new(KKTRole::AnonymousInitiator, KKTMode::OneWay, ciphersuite)?; - let context_bytes = context.encode()?; - - let mut session_id = [0u8; KKT_SESSION_ID_LEN]; - rng.fill_bytes(&mut session_id); - - Ok((context, KKTFrame::new(context_bytes, &[], session_id, &[]))) -} - -pub fn initiator_ingest_response<'a>( - own_context: &KKTContext, - remote_frame: &KKTFrame, - remote_context: &KKTContext, - remote_verification_key: &ed25519::PublicKey, - expected_hash: &[u8], -) -> Result, KKTError> { - 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(remote_frame.body_ref()); - bytes_to_verify.extend_from_slice(remote_frame.session_id_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(), - remote_frame.body_ref(), - )?; - - match validate_encapsulation_key( - &own_context.ciphersuite().hash_function(), - own_context.ciphersuite().hash_len(), - remote_frame.body_ref(), - expected_hash, - ) { - true => Ok(received_encapsulation_key), - - // The key does not match the hash obtained from the directory - false => Err(KKTError::KEMError { - info: "Hash of received encapsulation key does not match the value stored on the directory.", - }), - } - } - Err(_) => Err(KKTError::SigVerifError), - }, - Err(_) => Err(KKTError::SigConstructorError), - } - } - _ => Err(KKTError::ResponderFlaggedError { - status: remote_context.status(), - }), - } -} - -// todo: figure out how to handle errors using status codes - -pub fn responder_ingest_message<'a>( - remote_context: &KKTContext, - remote_verification_key: Option<&ed25519::PublicKey>, - expected_hash: Option<&[u8]>, - remote_frame: &KKTFrame, -) -> Result<(KKTContext, Option>), KKTError> { - let own_context = remote_context.derive_responder_header()?; - - match remote_context.role() { - KKTRole::AnonymousInitiator => Ok((own_context, None)), - - KKTRole::Initiator => { - match remote_verification_key { - Some(remote_verif_key) => { - let mut bytes_to_verify: Vec = Vec::with_capacity( - own_context.full_message_len() - own_context.signature_len(), - ); - bytes_to_verify.extend_from_slice(remote_frame.context_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(remote_frame.signature_ref()) { - Ok(sig) => match remote_verif_key.verify(bytes_to_verify, &sig) { - Ok(()) => { - // using own_context here because maybe for whatever reason we want to ignore the remote kem key - match own_context.mode() { - KKTMode::OneWay => Ok((own_context, None)), - KKTMode::Mutual => { - match expected_hash { - Some(expected_hash) => { - let received_encapsulation_key = - EncapsulationKey::decode( - own_context.ciphersuite().kem(), - remote_frame.body_ref(), - )?; - if validate_encapsulation_key( - &own_context.ciphersuite().hash_function(), - own_context.ciphersuite().hash_len(), - remote_frame.body_ref(), - expected_hash, - ) { - Ok(( - own_context, - Some(received_encapsulation_key), - )) - } - // The key does not match the hash obtained from the directory - else { - Err(KKTError::KEMError { - info: "Hash of received encapsulation key does not match the value stored on the directory.", - }) - } - } - None => Err(KKTError::FunctionInputError { - info: "Expected hash of the remote encapsulation key is not provided.", - }), - } - } - } - } - Err(_) => Err(KKTError::SigVerifError), - }, - Err(_) => Err(KKTError::SigConstructorError), - } - } - None => Err(KKTError::FunctionInputError { - info: "Remote Signature Verification Key Not Provided", - }), - } - } - KKTRole::Responder => Err(KKTError::IncompatibilityError { - info: "Responder received a request from another responder.", - }), - } -} - -pub fn responder_process<'a>( - own_context: &KKTContext, - session_id: KKTSessionId, - signing_key: &ed25519::PrivateKey, - encapsulation_key: &EncapsulationKey<'a>, -) -> Result { - let body = encapsulation_key.encode(); - - let context_bytes = own_context.encode()?; - - let mut bytes_to_sign = - 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); - - let signature = signing_key.sign(bytes_to_sign).to_bytes(); - - Ok(KKTFrame::new(context_bytes, &body, session_id, &signature)) -} - -fn check_compatibility( - _own_context: &KKTContext, - _remote_context: &KKTContext, -) -> Result<(), KKTError> { - // todo: check ciphersuite/context compatibility - Ok(()) -} diff --git a/common/nym-lp-common/Cargo.toml b/common/nym-lp-common/Cargo.toml deleted file mode 100644 index b84b617154..0000000000 --- a/common/nym-lp-common/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "nym-lp-common" -version = "0.1.0" -edition = { workspace = true } -license = { workspace = true } -publish = false - -[dependencies] diff --git a/common/nym-lp-transport/src/traits.rs b/common/nym-lp-transport/src/traits.rs deleted file mode 100644 index 045bf29498..0000000000 --- a/common/nym-lp-transport/src/traits.rs +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2026 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(feature = "io-mocks")] -use nym_test_utils::mocks::async_read_write::MockIOStream; -use std::net::SocketAddr; -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)] -pub trait LpTransport: 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<()>; - - /// 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>; -} - -async fn send_serialised_packet_async_write( - writer: &mut W, - packet_data: &[u8], -) -> std::io::Result<()> -where - W: AsyncWrite + Unpin, -{ - // Send 4-byte length prefix (u32 big-endian) - let len = packet_data.len() as u32; - writer - .write_all(&len.to_be_bytes()) - .await - .inspect_err(|e| debug!("Failed to send packet length: {e}"))?; - - // Send the actual packet data - writer - .write_all(packet_data) - .await - .inspect_err(|e| debug!("Failed to send packet data: {e}"))?; - - // Flush to ensure data is sent immediately - writer - .flush() - .await - .inspect_err(|e| debug!("Failed to flush stream: {e}"))?; - - tracing::trace!( - "Sent LP packet ({} bytes + 4 byte header)", - packet_data.len() - ); - Ok(()) -} - -async fn receive_raw_packet_async_read(reader: &mut R) -> std::io::Result> -where - R: AsyncRead + Unpin, -{ - // Read 4-byte length prefix (u32 big-endian) - let mut len_buf = [0u8; 4]; - reader - .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]; - reader - .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 { - async fn connect(endpoint: SocketAddr) -> std::io::Result { - TcpStream::connect(endpoint).await - } - - fn set_no_delay(&mut self, nodelay: bool) -> std::io::Result<()> { - // Set TCP_NODELAY for low latency - self.set_nodelay(nodelay) - } - - async fn send_serialised_packet(&mut self, packet_data: &[u8]) -> std::io::Result<()> { - send_serialised_packet_async_write(self, packet_data).await - } - - async fn receive_raw_packet(&mut self) -> std::io::Result> { - receive_raw_packet_async_read(self).await - } -} - -#[cfg(feature = "io-mocks")] -impl LpTransport for MockIOStream { - async fn connect(_endpoint: SocketAddr) -> std::io::Result { - Ok(MockIOStream::default()) - } - - fn set_no_delay(&mut self, _nodelay: bool) -> std::io::Result<()> { - Ok(()) - } - - async fn send_serialised_packet(&mut self, packet_data: &[u8]) -> std::io::Result<()> { - send_serialised_packet_async_write(self, packet_data).await - } - - async fn receive_raw_packet(&mut self) -> std::io::Result> { - receive_raw_packet_async_read(self).await - } -} diff --git a/common/nym-lp/Cargo.toml b/common/nym-lp/Cargo.toml index 22f11c279b..cbfaf75bbd 100644 --- a/common/nym-lp/Cargo.toml +++ b/common/nym-lp/Cargo.toml @@ -7,50 +7,36 @@ publish = false [dependencies] thiserror = { workspace = true } -parking_lot = { workspace = true } -snow = { workspace = true } bs58 = { workspace = true } -serde = { workspace = true } bytes = { workspace = true } -dashmap = { workspace = true } -sha2 = { workspace = true } tracing = { workspace = true } -rand = { workspace = true } -# rand 0.9 for KKT integration (nym-kkt uses rand 0.9) rand09 = { workspace = true } +tls_codec = { workspace = true } +tokio = { workspace = true, features = ["net", "io-util"] } -nym-crypto = { path = "../crypto", features = ["hashing", "asymmetric"] } +nym-crypto = { path = "../crypto", features = ["hashing"] } nym-kkt = { path = "../nym-kkt" } -nym-lp-common = { path = "../nym-lp-common" } -nym-lp-transport = { path = "../nym-lp-transport" } +nym-kkt-ciphersuite = { workspace = true } # libcrux dependencies for PSQ (Post-Quantum PSK derivation) -libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = [ - "test-utils", -] } -libcrux-kem = { git = "https://github.com/cryspen/libcrux" } -libcrux-traits = { git = "https://github.com/cryspen/libcrux" } -tls_codec = { workspace = true } +libcrux-psq = { workspace = true, features = ["test-utils"] } num_enum = { workspace = true } -chacha20poly1305 = { workspace = true } zeroize = { workspace = true, features = ["zeroize_derive"] } + # needed for the 'mock 'feature nym-test-utils = { workspace = true, optional = true } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } -#rand_chacha = "0.3" -mock_instant = { workspace = true } -nym-crypto = { path = "../crypto", features = ["rand"] } nym-test-utils = { workspace = true } anyhow = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -nym-lp-transport = { path = "../nym-lp-transport", features = ["io-mocks"] } + [features] -mock = ["nym-test-utils", "nym-crypto/rand"] +mock = ["nym-test-utils"] [[bench]] name = "replay_protection" -harness = false +harness = false \ No newline at end of file diff --git a/common/nym-lp/benches/replay_protection.rs b/common/nym-lp/benches/replay_protection.rs index a21a1092f0..63c1b615d9 100644 --- a/common/nym-lp/benches/replay_protection.rs +++ b/common/nym-lp/benches/replay_protection.rs @@ -1,9 +1,8 @@ use criterion::{BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main}; use nym_lp::replay::ReceivingKeyCounterValidator; -use nym_test_utils::helpers::u64_seeded_rng; -use parking_lot::Mutex; -use rand::Rng; -use std::sync::Arc; +use nym_test_utils::helpers::deterministic_rng_09; +use rand09::Rng; +use std::sync::{Arc, Mutex}; fn bench_sequential_counters(c: &mut Criterion) { let mut group = c.benchmark_group("replay_sequential"); @@ -47,8 +46,8 @@ fn bench_out_of_order_counters(c: &mut Criterion) { let validator = ReceivingKeyCounterValidator::default(); // Create random counters within a valid window - let mut rng = u64_seeded_rng(42); - let counters: Vec = (0..size).map(|_| rng.gen_range(0..1024)).collect(); + let mut rng = deterministic_rng_09(); + let counters: Vec = (0..size).map(|_| rng.random_range(0..1024)).collect(); b.iter(|| { let mut validator = validator.clone(); @@ -75,19 +74,15 @@ fn bench_thread_safety(c: &mut Criterion) { BenchmarkId::new("thread_safe_validator", size), &size, |b, &size| { - let validator = Arc::new(Mutex::new(ReceivingKeyCounterValidator::default())); + let mut validator = ReceivingKeyCounterValidator::default(); let counters: Vec = (0..size).collect(); b.iter(|| { for &counter in &counters { - let result = { - let guard = validator.lock(); - black_box(guard.will_accept_branchless(counter)) - }; + let result = { black_box(validator.will_accept_branchless(counter)) }; if result.is_ok() { - let mut guard = validator.lock(); - let _ = black_box(guard.mark_did_receive_branchless(counter)); + let _ = black_box(validator.mark_did_receive_branchless(counter)); } } }); @@ -202,7 +197,7 @@ fn bench_concurrency_scaling(c: &mut Criterion) { let mut success_count = 0; for i in 0..100 { let counter = t * 1000 + i; - let mut guard = validator_clone.lock(); + let mut guard = validator_clone.lock().unwrap(); if guard.mark_did_receive_branchless(counter as u64).is_ok() { success_count += 1; } diff --git a/common/nym-lp/src/codec.rs b/common/nym-lp/src/codec.rs index 94eedc1199..794f7a2b49 100644 --- a/common/nym-lp/src/codec.rs +++ b/common/nym-lp/src/codec.rs @@ -2,1324 +2,294 @@ // SPDX-License-Identifier: Apache-2.0 use crate::LpError; -use crate::message::{LpMessage, MessageType}; -use crate::packet::{LpHeader, LpPacket, OuterHeader, TRAILER_LEN}; -use bytes::{BufMut, BytesMut}; -use chacha20poly1305::{ - ChaCha20Poly1305, Key, Nonce, Tag, - aead::{AeadInPlace, KeyInit}, -}; -use zeroize::{Zeroize, ZeroizeOnDrop}; +use crate::packet::{EncryptedLpPacket, InnerHeader, LpHeader, LpMessage, LpPacket}; +use bytes::BytesMut; +use libcrux_psq::Channel; -/// Size of outer header (receiver_idx + counter) - always cleartext -pub const OUTER_HEADER_SIZE: usize = OuterHeader::SIZE; // 12 bytes +// needs to be equal or above to the actual overhead +pub(crate) const SANE_ENC_OVERHEAD: usize = 32; -/// Size of inner prefix (proto + reserved) - cleartext or encrypted depending on mode -const INNER_PREFIX_SIZE: usize = 4; // proto(1) + reserved(3) +// needs to be equal or below the actual overhead +pub(crate) const SANE_DEC_OVERHEAD: usize = 24; -/// Outer AEAD key for LP packet encryption. -/// -/// Derived from PSK using Blake3 KDF with domain separation. -/// Used for opportunistic encryption: before PSK packets are cleartext, -/// after PSK packets have encrypted payload and authenticated header. -/// -/// # Security: Nonce Reuse Prevention -/// -/// ChaCha20-Poly1305 requires unique nonces per key. The counter starts at 0 -/// for each session, which is safe because: -/// -/// 1. **PSK is always fresh**: Each handshake uses PSQ -/// with a client-generated random salt. This ensures a unique -/// PSK for every session, even between the same client-gateway pair. -/// -/// 2. **Key derivation**: `outer_key = Blake3_KDF("lp-outer-aead", PSK)`. -/// Different PSK → different outer_key → nonce reuse impossible. -/// -/// 3. **No PSK persistence**: PSK handles are not stored/reused across sessions. -/// Each connection performs fresh KKT+PSQ handshake. -/// -#[derive(Clone, Zeroize, ZeroizeOnDrop)] -pub struct OuterAeadKey { - key: [u8; 32], -} +pub(crate) fn encrypt_data( + plaintext: &[u8], + transport: &mut libcrux_psq::session::Transport, +) -> Result, LpError> { + let mut ciphertext = vec![0u8; plaintext.len() + SANE_ENC_OVERHEAD]; + let n = transport.write_message(plaintext, &mut ciphertext)?; -impl OuterAeadKey { - /// KDF context for outer AEAD key derivation (domain separation) - const KDF_CONTEXT: &'static str = "lp-outer-aead"; - - /// Derive outer AEAD key from PSK. - /// - /// Uses Blake3 KDF with domain separation to avoid key reuse - /// between the outer AEAD layer and the inner Noise layer. - pub fn from_psk(psk: &[u8; 32]) -> Self { - let key = nym_crypto::kdf::derive_key_blake3(Self::KDF_CONTEXT, psk, &[]); - Self { key } + if plaintext.len() + SANE_ENC_OVERHEAD != n { + ciphertext.truncate(n); } - /// Get reference to the raw key bytes. - pub fn as_bytes(&self) -> &[u8; 32] { - &self.key + Ok(ciphertext) +} + +pub(crate) fn decrypt_data( + ciphertext: &[u8], + transport: &mut libcrux_psq::session::Transport, +) -> Result, LpError> { + if ciphertext.len() < SANE_DEC_OVERHEAD { + return Err(LpError::InsufficientBufferSize); } -} + let mut plaintext = vec![0u8; ciphertext.len() - SANE_DEC_OVERHEAD]; -impl std::fmt::Debug for OuterAeadKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("OuterAeadKey") - .field("key", &"[REDACTED]") - .finish() + let (_, n) = transport.read_message(ciphertext, &mut plaintext)?; + if n != ciphertext.len() - SANE_DEC_OVERHEAD { + plaintext.truncate(n); } + Ok(plaintext) } -/// Build 12-byte nonce from 8-byte counter (zero-padded). -/// -/// Format: counter (8 bytes LE) || 0x00000000 (4 bytes) -fn build_nonce(counter: u64) -> [u8; 12] { - let mut nonce = [0u8; 12]; - nonce[..8].copy_from_slice(&counter.to_le_bytes()); - // bytes 8..12 remain zero (zero-padding) - nonce +pub(crate) fn encrypt_lp_packet( + packet: LpPacket, + transport: &mut libcrux_psq::session::Transport, +) -> Result { + let mut plaintext = BytesMut::with_capacity(InnerHeader::SIZE + packet.message().len()); + packet.header().inner.encode(&mut plaintext); + packet.message().encode(&mut plaintext); + + let ciphertext = encrypt_data(plaintext.as_ref(), transport)?; + + Ok(EncryptedLpPacket::new(packet.header().outer, ciphertext)) } -/// Parse message from raw type and content bytes. -/// -/// Used when decrypting outer-encrypted packets where the message type -/// was encrypted along with the content. -fn parse_message_from_type_and_content( - msg_type_raw: u32, - content: &[u8], -) -> Result { - let message_type = MessageType::from_u32(msg_type_raw) - .ok_or_else(|| LpError::invalid_message_type(msg_type_raw))?; - - LpMessage::decode_content(content, message_type) -} - -/// Parse only the outer header from raw packet bytes. -/// -/// Used for routing before session lookup. The outer header (receiver_idx + counter) -/// is always cleartext at bytes 0-12 in the unified packet format. -/// -/// # Arguments -/// * `src` - Raw packet bytes (at least OuterHeader::SIZE bytes) -/// -/// # Errors -/// * `LpError::InsufficientBufferSize` - Packet too small for outer header -pub fn parse_lp_header_only(src: &[u8]) -> Result { - OuterHeader::parse(src) -} - -/// Parses a complete Lewes Protocol packet from a byte slice (e.g., a UDP datagram payload). -/// -/// ## Unified Packet Format -/// -/// Both cleartext and encrypted packets have the same structure: -/// - Outer header (12B): receiver_idx(4) + counter(8) - always cleartext -/// - Inner payload: proto(1) + reserved(3) + msg_type(4) + content - cleartext or encrypted -/// - Trailer (16B): zeros (cleartext) or AEAD tag (encrypted) -/// -/// # Arguments -/// * `src` - Raw packet bytes -/// * `outer_key` - None for cleartext parsing, Some for AEAD decryption -/// -/// # Errors -/// * `LpError::AeadTagMismatch` - Tag verification failed (when outer_key provided) -/// * `LpError::InsufficientBufferSize` - Packet too small -pub fn parse_lp_packet(src: &[u8], outer_key: Option<&OuterAeadKey>) -> Result { - // Minimum size check: OuterHeader + InnerPrefix + MsgType + Trailer (for 0-payload message) - // 12 + 4 + 2 + 16 = 34 bytes - let min_size = OUTER_HEADER_SIZE + INNER_PREFIX_SIZE + 2 + TRAILER_LEN; - if src.len() < min_size { +pub(crate) fn decrypt_lp_packet( + packet: EncryptedLpPacket, + transport: &mut libcrux_psq::session::Transport, +) -> Result { + if packet.ciphertext().len() < InnerHeader::SIZE + SANE_DEC_OVERHEAD { return Err(LpError::InsufficientBufferSize); } - // Parse outer header (always cleartext at bytes 0-12) - let outer_header = OuterHeader::parse(src)?; + let plaintext = decrypt_data(packet.ciphertext(), transport)?; - // Extract trailer (potential AEAD tag) - let trailer_start = src.len() - TRAILER_LEN; - let mut trailer = [0u8; TRAILER_LEN]; - trailer.copy_from_slice(&src[trailer_start..]); + let inner_header = InnerHeader::parse(&plaintext)?; + let payload = &plaintext[InnerHeader::SIZE..]; + let message = LpMessage::decode(payload)?; - // Inner payload is everything between outer header and trailer - let inner_bytes = &src[OUTER_HEADER_SIZE..trailer_start]; - - // Handle decryption if outer key provided - match outer_key { - None => { - // Cleartext mode - parse inner directly - // Inner format: proto(1) + reserved(3) + msg_type(4) + content - if inner_bytes.len() < INNER_PREFIX_SIZE + 4 { - return Err(LpError::InsufficientBufferSize); - } - - let protocol_version = inner_bytes[0]; - // reserved bytes [1..4] are ignored - let msg_type = u32::from_le_bytes([ - inner_bytes[4], - inner_bytes[5], - inner_bytes[6], - inner_bytes[7], - ]); - let message_content = &inner_bytes[8..]; - - let header = LpHeader { - protocol_version, - reserved: [0u8; 3], - receiver_idx: outer_header.receiver_idx, - counter: outer_header.counter, - }; - - let message = parse_message_from_type_and_content(msg_type, message_content)?; - - Ok(LpPacket { - header, - message, - trailer, - }) - } - Some(key) => { - // AEAD decryption mode - // AAD is the outer header (12 bytes) - let nonce = build_nonce(outer_header.counter); - let aad = &src[..OUTER_HEADER_SIZE]; - - // Copy inner payload for in-place decryption - let mut decrypted = inner_bytes.to_vec(); - - // Convert trailer to Tag - let tag = Tag::from_slice(&trailer); - - // Decrypt and verify - let cipher = ChaCha20Poly1305::new(Key::from_slice(key.as_bytes())); - cipher - .decrypt_in_place_detached(Nonce::from_slice(&nonce), aad, &mut decrypted, tag) - .map_err(|_| LpError::AeadTagMismatch)?; - - // Decrypted format: proto(1) + reserved(3) + msg_type(4) + content - if decrypted.len() < INNER_PREFIX_SIZE + 4 { - return Err(LpError::InsufficientBufferSize); - } - - let protocol_version = decrypted[0]; - // reserved bytes [1..4] are ignored - let msg_type = - u32::from_le_bytes([decrypted[4], decrypted[5], decrypted[6], decrypted[7]]); - let message_content = &decrypted[8..]; - - let header = LpHeader { - protocol_version, - reserved: [0u8; 3], - receiver_idx: outer_header.receiver_idx, - counter: outer_header.counter, - }; - - let message = parse_message_from_type_and_content(msg_type, message_content)?; - - Ok(LpPacket { - header, - message, - trailer, - }) - } - } -} - -/// Serializes an LpPacket into the provided BytesMut buffer. -/// -/// ## Unified Packet Format -/// -/// Both cleartext and encrypted packets have the same structure: -/// - Outer header (12B): receiver_idx(4) + counter(8) - always cleartext -/// - Inner payload: proto(1) + reserved(3) + msg_type(4) + content - cleartext or encrypted -/// - Trailer (16B): zeros (cleartext) or AEAD tag (encrypted) -/// -/// # Arguments -/// * `item` - Packet to serialize -/// * `dst` - Output buffer -/// * `outer_key` - None for cleartext, Some for AEAD encryption -pub fn serialize_lp_packet( - item: &LpPacket, - dst: &mut BytesMut, - outer_key: Option<&OuterAeadKey>, -) -> Result<(), LpError> { - // Total size: outer_header(12) + inner_prefix(4) + msg_type(4) + content + trailer(16) - let total_size = OUTER_HEADER_SIZE + INNER_PREFIX_SIZE + 4 + item.message.len() + TRAILER_LEN; - dst.reserve(total_size); - - // 1. Write outer header (always cleartext) - 12 bytes - let outer_header = OuterHeader::new(item.header.receiver_idx, item.header.counter); - outer_header.encode_into(dst); - - match outer_key { - None => { - // Cleartext mode - // 2. Write inner prefix: proto(1) + reserved(3) - dst.put_u8(item.header.protocol_version); - dst.put_slice(&item.header.reserved); // reserved - - // 3. Write message type (4B) + content - dst.put_slice(&(item.message.typ() as u32).to_le_bytes()); - item.message.encode_content(dst); - - // 4. Write zeros trailer - dst.put_slice(&[0u8; TRAILER_LEN]); - - Ok(()) - } - Some(key) => { - // AEAD encryption mode - // AAD is the outer header (first 12 bytes) - let aad = outer_header.encode(); - - // 2. Build plaintext: proto(1) + reserved(3) + msg_type(4) + content - let mut plaintext = BytesMut::new(); - plaintext.put_u8(item.header.protocol_version); - plaintext.put_slice(&item.header.reserved); // reserved - plaintext.put_slice(&(item.message.typ() as u32).to_le_bytes()); - item.message.encode_content(&mut plaintext); - - // 3. Copy plaintext to dst for in-place encryption - let payload_start = dst.len(); - dst.put_slice(&plaintext); - - // 4. Build nonce from counter - let nonce = build_nonce(item.header.counter); - - // 5. Encrypt payload in-place - let cipher = ChaCha20Poly1305::new(Key::from_slice(key.as_bytes())); - let tag = cipher - .encrypt_in_place_detached( - Nonce::from_slice(&nonce), - &aad, - &mut dst[payload_start..], - ) - .map_err(|_| LpError::Internal("AEAD encryption failed".to_string()))?; - - // 6. Append tag as trailer - dst.put_slice(&tag); - - Ok(()) - } - } -} - -// Add a new error variant for invalid message types (Moved from previous impl LpError block) -impl LpError { - pub fn invalid_message_type(message_type: u32) -> Self { - LpError::InvalidMessageType(message_type) - } + Ok(LpPacket::new( + LpHeader { + outer: packet.outer_header(), + inner: inner_header, + }, + message, + )) } #[cfg(test)] mod tests { - use std::time::{SystemTime, UNIX_EPOCH}; - - // Import standalone functions - use super::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; - // Keep necessary imports use crate::LpError; - use crate::message::{EncryptedDataPayload, HandshakeData, LpMessage, MessageType}; - use crate::packet::{LpHeader, LpPacket, TRAILER_LEN}; - use bytes::BytesMut; - use nym_crypto::asymmetric::{ed25519, x25519}; - use rand::thread_rng; + use crate::codec::{decrypt_data, decrypt_lp_packet, encrypt_data, encrypt_lp_packet}; + use crate::packet::{EncryptedLpPacket, LpHeader, LpMessage, LpPacket}; + use crate::peer::mock_peers; + use crate::psq::initiator::{build_psq_ciphersuite, build_psq_principal}; + use crate::psq::{PSQ_MSG2_SIZE, psq_msg1_size, responder}; + use libcrux_psq::{Channel, IntoSession}; + use nym_kkt_ciphersuite::KEM; + use nym_test_utils::helpers::u64_seeded_rng_09; - // With unified format, outer header (receiver_idx + counter) is always first - // and is the only cleartext portion for encrypted packets - const OUTER_HDR: usize = super::OUTER_HEADER_SIZE; // 12 bytes + fn mock_transport() -> ( + libcrux_psq::session::Transport, + libcrux_psq::session::Transport, + ) { + let kem = KEM::MlKem768; + let rng1 = u64_seeded_rng_09(1); + let rng2 = u64_seeded_rng_09(2); + let (init, resp) = mock_peers(); + let remote_resp = resp.as_remote(); + let encapsulation_key = resp + .kem_keypairs + .as_ref() + .unwrap() + .encapsulation_key(kem) + .unwrap(); - // === Cleartext Encode/Decode Tests === + let initiator_ciphersuite = + build_psq_ciphersuite(&init, &remote_resp, &encapsulation_key).unwrap(); + let mut psq_initiator = build_psq_principal(rng1, 1, initiator_ciphersuite).unwrap(); - #[test] - fn test_serialize_parse_busy() { - let mut dst = BytesMut::new(); + let responder_ciphersuite = responder::build_psq_ciphersuite(&resp, kem).unwrap(); + let mut psq_responder = + responder::build_psq_principal(rng2, 1, responder_ciphersuite).unwrap(); - // Create a Busy packet - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, - counter: 123, - }, - message: LpMessage::Busy, - trailer: [0; TRAILER_LEN], - }; + // Send first message + let mut buf = vec![0u8; psq_msg1_size(kem)]; - // Serialize the packet (cleartext) - serialize_lp_packet(&packet, &mut dst, None).unwrap(); + let mut payload_buf_responder = vec![0u8; 4096]; + let mut payload_buf_initiator = vec![0u8; 4096]; - // Parse the packet (cleartext) - let decoded = parse_lp_packet(&dst, None).unwrap(); + let len_i = psq_initiator.write_message(&[], &mut buf).unwrap(); + assert_eq!(len_i, buf.len()); - // Verify the packet fields - assert_eq!(decoded.header.protocol_version, 1); - assert_eq!(decoded.header.receiver_idx, 42); - assert_eq!(decoded.header.counter, 123); - assert!(matches!(decoded.message, LpMessage::Busy)); - assert_eq!(decoded.trailer, [0; TRAILER_LEN]); + // Read first message + let (_, _) = psq_responder + .read_message(&buf, &mut payload_buf_responder) + .unwrap(); + + // Respond + let mut buf = [0u8; PSQ_MSG2_SIZE]; + let len_r = psq_responder.write_message(&[], &mut buf).unwrap(); + assert_eq!(len_r, buf.len()); + + // Finalize on registration initiator + let (len_i_deserialized, _) = psq_initiator + .read_message(&buf, &mut payload_buf_initiator) + .unwrap(); + + // We read the same amount of data. + assert_eq!(len_r, len_i_deserialized); + + // Ready for transport mode + assert!(psq_initiator.is_handshake_finished()); + assert!(psq_responder.is_handshake_finished()); + + let transport_initiator = psq_initiator + .into_session() + .unwrap() + .transport_channel() + .unwrap(); + + let transport_responder = psq_responder + .into_session() + .unwrap() + .transport_channel() + .unwrap(); + + (transport_initiator, transport_responder) } #[test] - fn test_serialize_parse_handshake() { - let mut dst = BytesMut::new(); + fn basic_plain_encryption_test() { + let (mut init_transport, mut resp_transport) = mock_transport(); - // Create a Handshake message packet - let payload = vec![42u8; 80]; // Example payload size - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, - counter: 123, - }, - message: LpMessage::Handshake(HandshakeData(payload.clone())), - trailer: [0; TRAILER_LEN], - }; + for msg_size in [1usize, 10, 100, 1000, 10000, 65535] { + let message1 = vec![42u8; msg_size]; - // Serialize the packet (cleartext) - serialize_lp_packet(&packet, &mut dst, None).unwrap(); + let mut ciphertext = vec![0u8; msg_size + 64]; + let written_init1 = init_transport + .write_message(&message1, &mut ciphertext) + .unwrap(); - // Parse the packet (cleartext) - let decoded = parse_lp_packet(&dst, None).unwrap(); + let init_ciphertext_overhead = written_init1 - msg_size; + let ciphertext_content = &ciphertext[..written_init1]; - // Verify the packet fields - assert_eq!(decoded.header.protocol_version, 1); - assert_eq!(decoded.header.receiver_idx, 42); - assert_eq!(decoded.header.counter, 123); + let mut plaintext = vec![0u8; msg_size + 64]; + let (read_resp1, written_resp1) = resp_transport + .read_message(ciphertext_content, &mut plaintext) + .unwrap(); + let resp_plaintext_overhead = ciphertext_content.len() - written_resp1; - // Verify message type and data - match decoded.message { - LpMessage::Handshake(decoded_payload) => { - assert_eq!(decoded_payload, HandshakeData(payload)); - } - _ => panic!("Expected Handshake message"), - } - assert_eq!(decoded.trailer, [0; TRAILER_LEN]); - } - - #[test] - fn test_serialize_parse_encrypted_data() { - let mut dst = BytesMut::new(); - - // Create an EncryptedData message packet - let payload = vec![43u8; 124]; // Example payload size - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, - counter: 123, - }, - message: LpMessage::EncryptedData(EncryptedDataPayload(payload.clone())), - trailer: [0; TRAILER_LEN], - }; - - // Serialize the packet (cleartext) - serialize_lp_packet(&packet, &mut dst, None).unwrap(); - - // Parse the packet (cleartext) - let decoded = parse_lp_packet(&dst, None).unwrap(); - - // Verify the packet fields - assert_eq!(decoded.header.protocol_version, 1); - assert_eq!(decoded.header.receiver_idx, 42); - assert_eq!(decoded.header.counter, 123); - - // Verify message type and data - match decoded.message { - LpMessage::EncryptedData(decoded_payload) => { - assert_eq!(decoded_payload, EncryptedDataPayload(payload)); - } - _ => panic!("Expected EncryptedData message"), - } - assert_eq!(decoded.trailer, [0; TRAILER_LEN]); - } - - // === Incomplete Data Tests === - - #[test] - fn test_parse_incomplete_header() { - // Create a buffer with incomplete header - let mut buf = BytesMut::new(); - buf.extend_from_slice(&[1, 0, 0, 0]); // Only 4 bytes, not enough for LpHeader::SIZE - - // Attempt to parse - expect error - let result = parse_lp_packet(&buf, None); - assert!(result.is_err()); - assert!(matches!( - result.unwrap_err(), - LpError::InsufficientBufferSize - )); - } - - #[test] - fn test_parse_incomplete_message_type() { - // Create a buffer with complete header but incomplete message type - let mut buf = BytesMut::new(); - buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved - buf.extend_from_slice(&42u32.to_le_bytes()); // Sender index - buf.extend_from_slice(&123u64.to_le_bytes()); // Counter - buf.extend_from_slice(&[0]); // Only 1 byte of message type (need 2) - - // Buffer length = 16 + 1 = 17. Min size = 16 + 2 + 16 = 34. - let result = parse_lp_packet(&buf, None); - assert!(result.is_err()); - assert!(matches!( - result.unwrap_err(), - LpError::InsufficientBufferSize - )); - } - - #[test] - fn test_parse_incomplete_message_data() { - // Create a buffer simulating Handshake but missing trailer and maybe partial payload - let mut buf = BytesMut::new(); - buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved - buf.extend_from_slice(&42u32.to_le_bytes()); // Sender index - buf.extend_from_slice(&123u64.to_le_bytes()); // Counter - buf.extend_from_slice(&MessageType::Handshake.to_u32().to_le_bytes()); // Handshake type - buf.extend_from_slice(&[42; 40]); // 40 bytes of payload data - - // Buffer length = 16 + 2 + 40 = 58. Min size = 16 + 2 + 16 = 34. - // Payload size calculated as 58 - 34 = 24. - // Trailer expected at index 16 + 2 + 24 = 42. - // Trailer read attempts src[42..58]. - // This *should* parse successfully based on the logic, but the trailer is garbage. - // Let's rethink: parse_lp_packet assumes the *entire slice* is the packet. - // If the slice doesn't end exactly where the trailer should, it's an error. - // In this case, total length is 58. OuterHdr(12) + InnerPrefix(4) + Type(2) + Trailer(16) = 34. Payload = 58-34=24. - // Trailer starts at 16+2+24 = 42. Ends at 42+16=58. It fits exactly. - // This test *still* doesn't test incompleteness correctly for the datagram parser. - - // Let's test a buffer that's *too short* even for header+type+trailer+min_payload - // Note: Buffer order doesn't matter for this test since we fail on minimum size check - let mut buf_too_short = BytesMut::new(); - buf_too_short.extend_from_slice(&42u32.to_le_bytes()); // receiver_idx (outer header) - buf_too_short.extend_from_slice(&123u64.to_le_bytes()); // counter (outer header) - buf_too_short.extend_from_slice(&[1, 0, 0, 0]); // version + reserved (inner prefix) - buf_too_short.extend_from_slice(&MessageType::Handshake.to_u32().to_le_bytes()); // msg type - // No payload, no trailer. Length = 12+4+2=18. Min size = 34. - let result_too_short = parse_lp_packet(&buf_too_short, None); - assert!(result_too_short.is_err()); - assert!(matches!( - result_too_short.unwrap_err(), - LpError::InsufficientBufferSize - )); - - // Test a buffer missing PART of the trailer - let mut buf_partial_trailer = BytesMut::new(); - buf_partial_trailer.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved - buf_partial_trailer.extend_from_slice(&42u32.to_le_bytes()); // Sender index - buf_partial_trailer.extend_from_slice(&123u64.to_le_bytes()); // Counter - buf_partial_trailer.extend_from_slice(&MessageType::Handshake.to_u32().to_le_bytes()); // Handshake type - let payload = vec![42u8; 20]; // Assume 20 byte payload - buf_partial_trailer.extend_from_slice(&payload); - buf_partial_trailer.extend_from_slice(&[0; TRAILER_LEN - 1]); // Missing last byte of trailer - - // Total length = 16 + 2 + 20 + 15 = 53. Min size = 34. This passes. - // Payload size = 53 - 34 = 19. <--- THIS IS WRONG. The parser assumes the length dictates payload. - // Let's fix the parser logic slightly. - - // The point is, parse_lp_packet expects a COMPLETE datagram. Providing less bytes - // than LpHeader + Type + Trailer should fail. Providing *more* is also an issue unless - // the length calculation works out perfectly. The most direct test is just < min_size. - // Renaming test to reflect this. - } - - #[test] - fn test_parse_buffer_smaller_than_minimum() { - // Test a buffer that's smaller than the smallest possible packet (LpHeader+Type+Trailer) - let mut buf_too_short = BytesMut::new(); - buf_too_short.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved - buf_too_short.extend_from_slice(&42u32.to_le_bytes()); // Sender index - buf_too_short.extend_from_slice(&123u64.to_le_bytes()); // Counter - buf_too_short.extend_from_slice(&MessageType::Busy.to_u32().to_le_bytes()); // Type - buf_too_short.extend_from_slice(&[0; TRAILER_LEN - 1]); // Missing last byte of trailer - // Length = 16 + 2 + 15 = 33. Min Size = 34. - let result_too_short = parse_lp_packet(&buf_too_short, None); - assert!( - result_too_short.is_err(), - "Expected error for buffer size 33, min 34" - ); - assert!(matches!( - result_too_short.unwrap_err(), - LpError::InsufficientBufferSize - )); - } - - #[test] - fn test_parse_invalid_message_type() { - // Create a buffer with invalid message type - let mut buf = BytesMut::new(); - buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved - buf.extend_from_slice(&42u32.to_le_bytes()); // Sender index - buf.extend_from_slice(&123u64.to_le_bytes()); // Counter - buf.extend_from_slice(&231u16.to_le_bytes()); // Invalid message type - // Need payload and trailer to meet min_size requirement - let payload_size = 10; // Arbitrary - buf.extend_from_slice(&vec![0u8; payload_size]); // Some data - buf.extend_from_slice(&[0; TRAILER_LEN]); // Trailer - - // Attempt to parse - let result = parse_lp_packet(&buf, None); - assert!(result.is_err()); - match result { - Err(LpError::InvalidMessageType(231)) => {} // Expected error - Err(e) => panic!("Expected InvalidMessageType error, got {:?}", e), - Ok(_) => panic!("Expected error, but got Ok"), - } - } - - #[test] - fn test_parse_incorrect_payload_size_for_busy() { - // Create a Busy packet but *with* a payload (which is invalid) - let mut buf = BytesMut::new(); - buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved - buf.extend_from_slice(&42u32.to_le_bytes()); // Sender index - buf.extend_from_slice(&123u64.to_le_bytes()); // Counter - buf.extend_from_slice(&MessageType::Busy.to_u32().to_le_bytes()); // Busy type - buf.extend_from_slice(&[42; 1]); // <<< Invalid 1-byte payload for Busy - buf.extend_from_slice(&[0; TRAILER_LEN]); // Trailer - - // Total size = 16 + 2 + 1 + 16 = 35. Min size = 34. - // Calculated payload size = 35 - 34 = 1. - let result = parse_lp_packet(&buf, None); - assert!(result.is_err()); - assert!(matches!( - result.unwrap_err(), - LpError::InvalidPayloadSize { - expected: 0, - actual: 1 - } - )); - } - - // Test multiple packets simulation isn't relevant for datagram parsing - // #[test] - // fn test_multiple_packets_in_buffer() { ... } - - // === ClientHello Serialization Tests === - - #[test] - fn test_serialize_parse_client_hello() { - use crate::message::ClientHelloData; - - let mut rng = thread_rng(); - let valid_ed25519 = ed25519::KeyPair::new(&mut rng); - let mut dst = BytesMut::new(); - - // Create ClientHelloData - let client_key = x25519::PublicKey::from_byte_array(&[42u8; 32]); - let client_ed25519_key = *valid_ed25519.public_key(); - let salt = [99u8; 32]; - let hello_data = ClientHelloData { - receiver_index: 12345, - client_lp_public_key: client_key, - client_ed25519_public_key: client_ed25519_key, - salt, - }; - - // Create a ClientHello message packet - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, - counter: 123, - }, - message: LpMessage::ClientHello(hello_data), - trailer: [0; TRAILER_LEN], - }; - - // Serialize the packet - serialize_lp_packet(&packet, &mut dst, None).unwrap(); - - // Parse the packet - let decoded = parse_lp_packet(&dst, None).unwrap(); - - // Verify the packet fields - assert_eq!(decoded.header.protocol_version, 1); - assert_eq!(decoded.header.receiver_idx, 42); - assert_eq!(decoded.header.counter, 123); - - // Verify message type and data - match decoded.message { - LpMessage::ClientHello(decoded_data) => { - assert_eq!(decoded_data.client_lp_public_key, client_key); - assert_eq!(decoded_data.salt, salt); - } - _ => panic!("Expected ClientHello message"), - } - assert_eq!(decoded.trailer, [0; TRAILER_LEN]); - } - - #[test] - fn test_serialize_parse_client_hello_with_fresh_salt() { - use crate::message::ClientHelloData; - - let mut dst = BytesMut::new(); - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("System time before UNIX epoch") - .as_secs(); - - // Create ClientHelloData with fresh salt - let mut rng = thread_rng(); - let valid_ed25519 = ed25519::KeyPair::new(&mut rng); - - let client_key = x25519::PublicKey::from_byte_array(&[7u8; 32]); - let client_ed25519_key = *valid_ed25519.public_key(); - let hello_data = - ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); - - // Create a ClientHello message packet - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 100, - counter: 200, - }, - message: LpMessage::ClientHello(hello_data), - trailer: [55; TRAILER_LEN], - }; - - // Serialize the packet - serialize_lp_packet(&packet, &mut dst, None).unwrap(); - - // Parse the packet - let decoded = parse_lp_packet(&dst, None).unwrap(); - - // Verify message type and data - match decoded.message { - LpMessage::ClientHello(decoded_data) => { - assert_eq!(decoded_data.client_lp_public_key, client_key); - assert_eq!(decoded_data.salt, hello_data.salt); - - // Verify timestamp can be extracted - let timestamp = decoded_data.extract_timestamp(); - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs(); - // Timestamp should be within 2 seconds of now - assert!((timestamp as i64 - now as i64).abs() <= 2); - } - _ => panic!("Expected ClientHello message"), - } - } - - #[test] - fn test_parse_client_hello_malformed_data() { - // Create a buffer with ClientHello message type but invalid bincode data - let mut buf = BytesMut::new(); - buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved - buf.extend_from_slice(&42u32.to_le_bytes()); // Sender index - buf.extend_from_slice(&123u64.to_le_bytes()); // Counter - buf.extend_from_slice(&MessageType::ClientHello.to_u32().to_le_bytes()); // ClientHello type - - // Add data that does not equal to ClientHelloData::LEN - buf.extend_from_slice(&[0xFF; 50]); // invalid data - buf.extend_from_slice(&[0; TRAILER_LEN]); // Trailer - - // Attempt to parse - let result = parse_lp_packet(&buf, None); - assert!(result.is_err()); - match result { - Err(LpError::DeserializationError(_)) => {} // Expected error - Err(e) => panic!("Expected DeserializationError, got {:?}", e), - Ok(_) => panic!("Expected error, but got Ok"), - } - } - - #[test] - fn test_parse_client_hello_incomplete_bincode() { - // Create a buffer with ClientHello but truncated bincode data - let mut buf = BytesMut::new(); - buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved - buf.extend_from_slice(&42u32.to_le_bytes()); // Sender index - buf.extend_from_slice(&123u64.to_le_bytes()); // Counter - buf.extend_from_slice(&MessageType::ClientHello.to_u32().to_le_bytes()); // ClientHello type - - // Add incomplete bincode data (only partial ClientHelloData) - buf.extend_from_slice(&[0; 20]); // Too few bytes for full ClientHelloData - buf.extend_from_slice(&[0; TRAILER_LEN]); // Trailer - - // Attempt to parse - let result = parse_lp_packet(&buf, None); - assert!(result.is_err()); - match result { - Err(LpError::DeserializationError(_)) => {} // Expected error - Err(e) => panic!("Expected DeserializationError, got {:?}", e), - Ok(_) => panic!("Expected error, but got Ok"), - } - } - - #[test] - 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".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, "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() + written_init1, read_resp1, + "should work for message {msg_size}" ); - assert_eq!(data.inner_packet_bytes, vec![0xa, 0xb, 0xc, 0xd]); - } else { - panic!("Expected ForwardPacket message"); + let message1_content = &plaintext[..written_resp1]; + assert_eq!( + message1_content, &message1, + "should work for message {msg_size}" + ); + + // reverse the communication + let message2 = vec![43u8; msg_size]; + + let mut ciphertext2 = vec![0u8; msg_size + 64]; + let written_resp2 = resp_transport + .write_message(&message2, &mut ciphertext2) + .unwrap(); + + let resp_ciphertext_overhead = written_resp2 - msg_size; + let ciphertext_content2 = &ciphertext2[..written_resp2]; + + let mut plaintext2 = vec![0u8; msg_size + 64]; + let (read_init2, written_init2) = init_transport + .read_message(ciphertext_content2, &mut plaintext2) + .unwrap(); + let init_plaintext_overhead = ciphertext_content2.len() - written_init2; + + assert_eq!( + written_resp2, read_init2, + "should work for message {msg_size}" + ); + let message2_content = &plaintext2[..written_init2]; + assert_eq!( + message2_content, &message2, + "should work for message {msg_size}" + ); + + // check consistent overheads + // enc/enc + assert_eq!(init_ciphertext_overhead, resp_ciphertext_overhead); + + // dec/dec + assert_eq!(resp_plaintext_overhead, init_plaintext_overhead); + + // enc/dec + assert_eq!(init_ciphertext_overhead, resp_plaintext_overhead); } } - // === Outer AEAD Tests === - #[test] - fn test_aead_roundtrip_with_key() { - // Test that encrypt/decrypt roundtrip works with an AEAD key - let psk = [42u8; 32]; - let outer_key = OuterAeadKey::from_psk(&psk); + fn basic_encryption() { + let (mut init_transport, mut resp_transport) = mock_transport(); - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 12345, - counter: 999, - }, - message: LpMessage::Busy, - trailer: [0; TRAILER_LEN], - }; + // happy path + let msg = b"foomp".to_vec(); + let ciphertext = encrypt_data(&msg, &mut init_transport).unwrap(); + let plaintext = decrypt_data(&ciphertext, &mut resp_transport).unwrap(); + assert_eq!(msg, plaintext); - let mut encrypted = BytesMut::new(); - serialize_lp_packet(&packet, &mut encrypted, Some(&outer_key)).unwrap(); + // incomplete ciphertext + let msg2 = b"foomp".to_vec(); + let ciphertext2 = encrypt_data(&msg2, &mut init_transport).unwrap(); + let len = ciphertext2.len(); + let dec_err = decrypt_data(&ciphertext2[..len - 1], &mut resp_transport).unwrap_err(); + assert!(matches!(dec_err, LpError::PSQSessionFailure { .. })); - // Parse back with the same key - let decoded = parse_lp_packet(&encrypted, Some(&outer_key)).unwrap(); - - assert_eq!(decoded.header.protocol_version, 1); - assert_eq!(decoded.header.receiver_idx, 12345); - assert_eq!(decoded.header.counter, 999); - assert!(matches!(decoded.message, LpMessage::Busy)); + // too small buffer + let msg3 = b"foomp".to_vec(); + let ciphertext3 = encrypt_data(&msg3, &mut resp_transport).unwrap(); + let dec_err = decrypt_data(&ciphertext3[..10], &mut init_transport).unwrap_err(); + assert!(matches!(dec_err, LpError::InsufficientBufferSize)); } #[test] - fn test_aead_ciphertext_differs_from_plaintext() { - // Verify that encrypted payload differs from plaintext - let psk = [42u8; 32]; - let outer_key = OuterAeadKey::from_psk(&psk); + fn basic_packet_encryption() { + let (mut init_transport, mut resp_transport) = mock_transport(); - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 12345, - counter: 999, - }, - message: LpMessage::EncryptedData(crate::message::EncryptedDataPayload(vec![ - 0xAA, 0xBB, 0xCC, 0xDD, - ])), - trailer: [0; TRAILER_LEN], - }; - - let mut cleartext = BytesMut::new(); - serialize_lp_packet(&packet, &mut cleartext, None).unwrap(); - - let mut encrypted = BytesMut::new(); - serialize_lp_packet(&packet, &mut encrypted, Some(&outer_key)).unwrap(); - - // Outer header (receiver_idx + counter) should be the same - always cleartext - assert_eq!(&cleartext[..OUTER_HDR], &encrypted[..OUTER_HDR]); - - // Inner payload (proto + reserved + msg_type + content) should differ (encrypted) - let payload_start = OUTER_HDR; - let payload_end_cleartext = cleartext.len() - TRAILER_LEN; - let payload_end_encrypted = encrypted.len() - TRAILER_LEN; - - assert_ne!( - &cleartext[payload_start..payload_end_cleartext], - &encrypted[payload_start..payload_end_encrypted], - "Encrypted payload should differ from plaintext" + // happy path + let packet = LpPacket::new( + LpHeader::new(123, 0, 1), + LpMessage::new_opaque(b"foomp".to_vec()), ); - // Trailer should differ (zeros vs AEAD tag) - assert_ne!( - &cleartext[payload_end_cleartext..], - &encrypted[payload_end_encrypted..], - "Encrypted trailer should be a tag, not zeros" + let ciphertext = encrypt_lp_packet(packet.clone(), &mut init_transport).unwrap(); + assert_eq!(packet.header().outer, ciphertext.outer_header()); + + let plaintext = decrypt_lp_packet(ciphertext, &mut resp_transport).unwrap(); + assert_eq!(packet, plaintext); + + // incomplete ciphertext + let packet = LpPacket::new( + LpHeader::new(123, 1, 1), + LpMessage::new_opaque(b"foomp".to_vec()), ); - } + let ciphertext2 = encrypt_lp_packet(packet, &mut init_transport).unwrap(); + let l = ciphertext2.ciphertext().len(); + let malformed_content = ciphertext2.ciphertext()[..l - 1].to_vec(); + let malformed = EncryptedLpPacket::new(ciphertext2.outer_header(), malformed_content); + let dec_err = decrypt_lp_packet(malformed, &mut resp_transport).unwrap_err(); + assert!(matches!(dec_err, LpError::PSQSessionFailure { .. })); - #[test] - fn test_aead_tampered_tag_fails() { - // Verify that tampering with the tag causes decryption failure - let psk = [42u8; 32]; - let outer_key = OuterAeadKey::from_psk(&psk); - - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 12345, - counter: 999, - }, - message: LpMessage::Busy, - trailer: [0; TRAILER_LEN], - }; - - let mut encrypted = BytesMut::new(); - serialize_lp_packet(&packet, &mut encrypted, Some(&outer_key)).unwrap(); - - // Tamper with the tag (last byte) - let last_idx = encrypted.len() - 1; - encrypted[last_idx] ^= 0xFF; - - // Parsing should fail with AeadTagMismatch - let result = parse_lp_packet(&encrypted, Some(&outer_key)); - assert!(matches!(result, Err(LpError::AeadTagMismatch))); - } - - #[test] - fn test_aead_tampered_header_fails() { - // Verify that tampering with the header (AAD) causes decryption failure - let psk = [42u8; 32]; - let outer_key = OuterAeadKey::from_psk(&psk); - - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 12345, - counter: 999, - }, - message: LpMessage::Busy, - trailer: [0; TRAILER_LEN], - }; - - let mut encrypted = BytesMut::new(); - serialize_lp_packet(&packet, &mut encrypted, Some(&outer_key)).unwrap(); - - // Tamper with the outer header AAD (flip a bit in counter at byte 4) - // New format: [receiver_idx(0-3), counter(4-11)], so byte 4 is counter's LSB - encrypted[4] ^= 0x01; - - // Parsing should fail with AeadTagMismatch - let result = parse_lp_packet(&encrypted, Some(&outer_key)); - assert!(matches!(result, Err(LpError::AeadTagMismatch))); - } - - #[test] - fn test_aead_different_counters_produce_different_ciphertext() { - // Verify that different counters (nonces) produce different ciphertexts - let psk = [42u8; 32]; - let outer_key = OuterAeadKey::from_psk(&psk); - - let packet1 = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 12345, - counter: 1, - }, - message: LpMessage::Busy, - trailer: [0; TRAILER_LEN], - }; - - let packet2 = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 12345, - counter: 2, // Different counter - }, - message: LpMessage::Busy, - trailer: [0; TRAILER_LEN], - }; - - let mut encrypted1 = BytesMut::new(); - serialize_lp_packet(&packet1, &mut encrypted1, Some(&outer_key)).unwrap(); - - let mut encrypted2 = BytesMut::new(); - serialize_lp_packet(&packet2, &mut encrypted2, Some(&outer_key)).unwrap(); - - // The encrypted inner payloads should differ even though the message is the same - // (because nonce is different). Inner payload starts after outer header. - let payload_start = OUTER_HDR; - assert_ne!( - &encrypted1[payload_start..], - &encrypted2[payload_start..], - "Different counters should produce different ciphertexts" + // too small buffer + let packet = LpPacket::new( + LpHeader::new(123, 1, 1), + LpMessage::new_opaque(b"foomp".to_vec()), ); - } - - #[test] - fn test_aead_wrong_key_fails() { - // Verify that decryption with wrong key fails - let psk1 = [42u8; 32]; - let psk2 = [43u8; 32]; // Different PSK - let outer_key1 = OuterAeadKey::from_psk(&psk1); - let outer_key2 = OuterAeadKey::from_psk(&psk2); - - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 12345, - counter: 999, - }, - message: LpMessage::Busy, - trailer: [0; TRAILER_LEN], - }; - - let mut encrypted = BytesMut::new(); - serialize_lp_packet(&packet, &mut encrypted, Some(&outer_key1)).unwrap(); - - // Parsing with wrong key should fail - let result = parse_lp_packet(&encrypted, Some(&outer_key2)); - assert!(matches!(result, Err(LpError::AeadTagMismatch))); - } - - #[test] - fn test_aead_encrypted_data_message_roundtrip() { - // Test AEAD with EncryptedData message type (larger payload) - let psk = [42u8; 32]; - let outer_key = OuterAeadKey::from_psk(&psk); - - let payload_data = vec![0xDE; 100]; - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 54321, - counter: 12345678, - }, - message: LpMessage::EncryptedData(crate::message::EncryptedDataPayload( - payload_data.clone(), - )), - trailer: [0; TRAILER_LEN], - }; - - let mut encrypted = BytesMut::new(); - serialize_lp_packet(&packet, &mut encrypted, Some(&outer_key)).unwrap(); - - let decoded = parse_lp_packet(&encrypted, Some(&outer_key)).unwrap(); - - match decoded.message { - LpMessage::EncryptedData(data) => { - assert_eq!(data.0, payload_data); - } - _ => panic!("Expected EncryptedData message"), - } - } - - #[test] - fn test_aead_handshake_message_roundtrip() { - // Test AEAD with Handshake message type - let psk = [42u8; 32]; - let outer_key = OuterAeadKey::from_psk(&psk); - - let handshake_data = vec![0x01, 0x02, 0x03, 0x04, 0x05]; - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 99999, - counter: 2, - }, - message: LpMessage::Handshake(HandshakeData(handshake_data.clone())), - trailer: [0; TRAILER_LEN], - }; - - let mut encrypted = BytesMut::new(); - serialize_lp_packet(&packet, &mut encrypted, Some(&outer_key)).unwrap(); - - let decoded = parse_lp_packet(&encrypted, Some(&outer_key)).unwrap(); - - match decoded.message { - LpMessage::Handshake(data) => { - assert_eq!(data.0, handshake_data); - } - _ => panic!("Expected Handshake message"), - } - } - - // === Subsession Message Tests === - - #[test] - fn test_serialize_parse_subsession_request() { - let mut dst = BytesMut::new(); - - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, - counter: 100, - }, - message: LpMessage::SubsessionRequest, - trailer: [0; TRAILER_LEN], - }; - - serialize_lp_packet(&packet, &mut dst, None).unwrap(); - let decoded = parse_lp_packet(&dst, None).unwrap(); - - assert_eq!(decoded.header.receiver_idx, 42); - assert_eq!(decoded.header.counter, 100); - assert!(matches!(decoded.message, LpMessage::SubsessionRequest)); - } - - #[test] - fn test_serialize_parse_subsession_kk1() { - use crate::message::SubsessionKK1Data; - - let mut dst = BytesMut::new(); - - let kk1_data = SubsessionKK1Data { - payload: vec![0xAA; 50], // 50 bytes KK payload - }; - - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 123, - counter: 456, - }, - message: LpMessage::SubsessionKK1(kk1_data.clone()), - trailer: [0; TRAILER_LEN], - }; - - serialize_lp_packet(&packet, &mut dst, None).unwrap(); - let decoded = parse_lp_packet(&dst, None).unwrap(); - - assert_eq!(decoded.header.receiver_idx, 123); - match decoded.message { - LpMessage::SubsessionKK1(data) => { - assert_eq!(data.payload, kk1_data.payload); - } - _ => panic!("Expected SubsessionKK1 message"), - } - } - - #[test] - fn test_serialize_parse_subsession_kk2() { - use crate::message::SubsessionKK2Data; - - let mut dst = BytesMut::new(); - - let kk2_data = SubsessionKK2Data { - payload: vec![0x11; 60], // 60 bytes KK response payload - }; - - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 789, - counter: 1000, - }, - message: LpMessage::SubsessionKK2(kk2_data.clone()), - trailer: [0; TRAILER_LEN], - }; - - serialize_lp_packet(&packet, &mut dst, None).unwrap(); - let decoded = parse_lp_packet(&dst, None).unwrap(); - - assert_eq!(decoded.header.receiver_idx, 789); - match decoded.message { - LpMessage::SubsessionKK2(data) => { - assert_eq!(data.payload, kk2_data.payload); - } - _ => panic!("Expected SubsessionKK2 message"), - } - } - - #[test] - fn test_serialize_parse_subsession_ready() { - use crate::message::SubsessionReadyData; - - let mut dst = BytesMut::new(); - - let ready_data = SubsessionReadyData { - receiver_index: 99999, - }; - - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, - counter: 200, - }, - message: LpMessage::SubsessionReady(ready_data.clone()), - trailer: [0; TRAILER_LEN], - }; - - serialize_lp_packet(&packet, &mut dst, None).unwrap(); - let decoded = parse_lp_packet(&dst, None).unwrap(); - - assert_eq!(decoded.header.receiver_idx, 42); - match decoded.message { - LpMessage::SubsessionReady(data) => { - assert_eq!(data.receiver_index, 99999); - } - _ => panic!("Expected SubsessionReady message"), - } - } - - #[test] - fn test_subsession_request_with_payload_fails() { - // SubsessionRequest should have no payload - let mut buf = BytesMut::new(); - buf.extend_from_slice(&42u32.to_le_bytes()); // receiver_idx - buf.extend_from_slice(&123u64.to_le_bytes()); // counter - buf.extend_from_slice(&[1, 0, 0, 0]); // version + reserved - buf.extend_from_slice(&MessageType::SubsessionRequest.to_u32().to_le_bytes()); - buf.extend_from_slice(&[0xFF]); // Invalid payload for SubsessionRequest - buf.extend_from_slice(&[0; TRAILER_LEN]); - - let result = parse_lp_packet(&buf, None); - assert!(matches!( - result, - Err(LpError::InvalidPayloadSize { - expected: 0, - actual: 1 - }) - )); - } - - #[test] - fn test_aead_subsession_roundtrip() { - use crate::message::SubsessionKK1Data; - - let psk = [42u8; 32]; - let outer_key = OuterAeadKey::from_psk(&psk); - - let kk1_data = SubsessionKK1Data { - payload: vec![0xDE; 48], // 48 bytes KK payload - }; - - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 54321, - counter: 999, - }, - message: LpMessage::SubsessionKK1(kk1_data.clone()), - trailer: [0; TRAILER_LEN], - }; - - let mut encrypted = BytesMut::new(); - serialize_lp_packet(&packet, &mut encrypted, Some(&outer_key)).unwrap(); - - let decoded = parse_lp_packet(&encrypted, Some(&outer_key)).unwrap(); - - match decoded.message { - LpMessage::SubsessionKK1(data) => { - assert_eq!(data.payload, kk1_data.payload); - } - _ => panic!("Expected SubsessionKK1 message"), - } - } - - #[test] - fn test_serialize_parse_error() { - use crate::message::ErrorPacketData; - - let mut dst = BytesMut::new(); - - let error_data = ErrorPacketData { - message: "this is an error".to_string(), - }; - - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, - counter: 200, - }, - message: LpMessage::Error(error_data.clone()), - trailer: [0; TRAILER_LEN], - }; - - serialize_lp_packet(&packet, &mut dst, None).unwrap(); - let decoded = parse_lp_packet(&dst, None).unwrap(); - - assert_eq!(decoded.header.receiver_idx, 42); - match decoded.message { - LpMessage::Error(data) => { - assert_eq!(data.message, "this is an error"); - } - _ => panic!("Expected Error message"), - } + let ciphertext3 = encrypt_lp_packet(packet, &mut resp_transport).unwrap(); + let malformed = EncryptedLpPacket::new(ciphertext3.outer_header(), vec![]); + let dec_err = decrypt_lp_packet(malformed, &mut init_transport).unwrap_err(); + assert!(matches!(dec_err, LpError::InsufficientBufferSize)); } } diff --git a/common/nym-lp/src/config.rs b/common/nym-lp/src/config.rs index 4b22ad331d..bc6a0ab987 100644 --- a/common/nym-lp/src/config.rs +++ b/common/nym-lp/src/config.rs @@ -18,7 +18,7 @@ pub const DEFAULT_PSK_TTL_SECS: u64 = 3600; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LpConfig { /// KEM algorithm for PSQ key encapsulation. - /// X25519 = classical (testing), MlKem768 = PQ, XWing = hybrid. + /// Supported KEMs: MlKem768, McEliece #[serde(with = "kem_serde")] pub kem_algorithm: KEM, @@ -32,7 +32,7 @@ pub struct LpConfig { impl Default for LpConfig { fn default() -> Self { Self { - kem_algorithm: KEM::X25519, + kem_algorithm: KEM::MlKem768, psk_ttl_secs: DEFAULT_PSK_TTL_SECS, enable_kkt: true, } @@ -55,10 +55,10 @@ mod kem_serde { S: Serializer, { match kem { - KEM::X25519 => "X25519", KEM::MlKem768 => "MlKem768", - KEM::XWing => "XWing", KEM::McEliece => "McEliece", + KEM::X25519 => return Err(serde::ser::Error::custom("Unsupported KEM: X25519")), + KEM::XWing => return Err(serde::ser::Error::custom("Unsupported KEM: XWing")), } .serialize(serializer) } @@ -69,10 +69,10 @@ mod kem_serde { { let s = String::deserialize(deserializer)?; match s.as_str() { - "X25519" => Ok(KEM::X25519), "MlKem768" => Ok(KEM::MlKem768), - "XWing" => Ok(KEM::XWing), "McEliece" => Ok(KEM::McEliece), + "X25519" => Err(serde::de::Error::custom("Unsupported KEM: X25519")), + "XWing" => Err(serde::de::Error::custom("Unsupported KEM: XWing")), _ => Err(serde::de::Error::custom(format!("Unknown KEM: {}", s))), } } diff --git a/common/nym-lp/src/error.rs b/common/nym-lp/src/error.rs index cf3b5e5085..1033887a3a 100644 --- a/common/nym-lp/src/error.rs +++ b/common/nym-lp/src/error.rs @@ -1,11 +1,16 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::message::MessageType; -use crate::{noise_protocol::NoiseError, replay::ReplayError}; -use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError; -use nym_kkt::ciphersuite::{HashFunction, KEM}; +use crate::packet::MalformedLpPacketError; +use crate::peer_config::LpReceiverIndex; +use crate::replay::ReplayError; +use crate::transport::LpTransportError; +use libcrux_psq::handshake::HandshakeError; +use libcrux_psq::handshake::builders::BuilderError; +use libcrux_psq::session::SessionError; +// use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError; use nym_kkt::error::KKTError; +use nym_kkt_ciphersuite::{HashFunction, KEM}; use thiserror::Error; #[derive(Error, Debug)] @@ -13,33 +18,18 @@ pub enum LpError { #[error("IO Error: {0}")] IoError(#[from] std::io::Error), - #[error("Snow Error: {0}")] - SnowKeyError(#[from] snow::Error), - - #[error("Snow Pattern Error: {0}")] - SnowPatternError(String), - - #[error("Noise Protocol Error: {0}")] - NoiseError(#[from] NoiseError), - #[error("Replay detected: {0}")] Replay(#[from] ReplayError), - #[error("Invalid packet format: {0}")] - InvalidPacketFormat(String), - - #[error("Invalid message type: {0}")] - InvalidMessageType(u32), - - #[error("Payload too large: {0}")] - PayloadTooLarge(usize), - #[error("Insufficient buffer size provided")] InsufficientBufferSize, #[error("Attempted operation on closed session")] SessionClosed, + #[error("There already exists an LP session with receiver index {0}")] + DuplicateSessionId(LpReceiverIndex), + #[error("Internal error: {0}")] Internal(String), @@ -52,15 +42,12 @@ pub enum LpError { #[error("Deserialization error: {0}")] DeserializationError(String), - #[error("KKT protocol error: {0}")] - KKTError(String), - #[error(transparent)] InvalidBase58String(#[from] bs58::decode::Error), /// Session ID from incoming packet does not match any known session. #[error("Received packet with unknown session ID: {0}")] - UnknownSessionId(u32), + UnknownSessionId(LpReceiverIndex), /// Invalid state transition attempt in the state machine. #[error("Invalid input '{input}' for current state '{state}'")] @@ -75,27 +62,14 @@ pub enum LpError { LpSessionProcessing, /// State machine not found. - #[error("State machine not found for lp_id: {lp_id}")] - StateMachineNotFound { lp_id: u32 }, + #[error("State machine not found for lp_id: {0}")] + StateMachineNotFound(LpReceiverIndex), - /// Ed25519 to X25519 conversion error. - #[error("Ed25519 key conversion error: {0}")] - Ed25519RecoveryError(#[from] Ed25519RecoveryError), - - /// 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, + // /// Ed25519 to X25519 conversion error. + // #[error("Ed25519 key conversion error: {0}")] + // Ed25519RecoveryError(#[from] Ed25519RecoveryError), + #[error("attempted to create an LP responder without providing a valid KEM keys")] + ResponderWithMissingKEMKeys, #[error( "there are no known digests for remote's KEM key with {kem} KEM and {hash_function} hash function" @@ -113,16 +87,56 @@ pub enum LpError { #[from] source: KKTError, }, + + #[error(transparent)] + MalformedPacket(#[from] MalformedLpPacketError), + + #[error("version {version} is not supported")] + UnsupportedVersion { version: u8 }, + + #[error("failed to build PSQ responder: {inner:?}")] + PSQResponderBuilderFailure { inner: BuilderError }, + + #[error("failed to build PSQ initiator: {inner:?}")] + PSQInitiatorBuilderFailure { inner: BuilderError }, + + #[error("failed to complete the PSQ handshake: {inner:?}")] + PSQHandshakeFailure { inner: HandshakeError }, + + #[error("failed to run the PSQ session: {inner:?}")] + PSQSessionFailure { inner: SessionError }, + + #[error("failed to derive a transport channel: {inner:?}")] + TransportDerivationFailure { inner: SessionError }, + + #[error("the initiator authenticator is not available after ingesting PSQ msg1")] + MissingInitiatorAuthenticator, + + #[error("transport failure: {0}")] + TransportFailure(#[from] LpTransportError), + + #[error("the current session is not in transport state")] + NotInTransport, } impl LpError { pub fn kkt_psq_handshake(msg: impl Into) -> Self { Self::KKTPSQHandshake(msg.into()) } +} - pub fn unexpected_handshake_response(got: MessageType, expected: MessageType) -> LpError { - Self::KKTPSQHandshake(format!( - "received unexpected response, got: {got:?}, expected: {expected:?}" - )) +impl From for LpError { + fn from(handshake_error: HandshakeError) -> Self { + Self::PSQHandshakeFailure { + inner: handshake_error, + } + } +} + +impl From for LpError { + fn from(session_error: SessionError) -> Self { + Self::PSQSessionFailure { + inner: session_error, + } } } diff --git a/common/nym-lp/src/kkt_orchestrator.rs b/common/nym-lp/src/kkt_orchestrator.rs deleted file mode 100644 index 98e3cdff71..0000000000 --- a/common/nym-lp/src/kkt_orchestrator.rs +++ /dev/null @@ -1,492 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -//! KKT (Key Encapsulation Transport) orchestration for nym-lp sessions. -//! -//! This module provides functions to perform KKT key exchange before establishing -//! an nym-lp session. The KKT protocol allows secure distribution of post-quantum -//! KEM public keys, which are then used with PSQ to derive a strong pre-shared key -//! for the Noise protocol. -//! -//! # Protocol Flow -//! -//! 1. **Client (Initiator)**: -//! - Calls `create_request()` to generate a KKT request -//! - Sends `LpMessage::KKTRequest` to gateway -//! - Receives `LpMessage::KKTResponse` from gateway -//! - Calls `process_response()` to validate and extract gateway's KEM key -//! -//! 2. **Gateway (Responder)**: -//! - Receives `LpMessage::KKTRequest` from client -//! - Calls `handle_request()` to validate request and generate response -//! - Sends `LpMessage::KKTResponse` to client -//! -//! # Example -//! -//! ```ignore -//! use nym_lp::kkt_orchestrator::{create_request, process_response, handle_request}; -//! use nym_lp::message::{KKTRequestData, KKTResponseData}; -//! use nym_kkt::ciphersuite::{Ciphersuite, KEM, HashFunction, SignatureScheme, EncapsulationKey}; -//! -//! // Setup ciphersuite -//! let ciphersuite = Ciphersuite::resolve_ciphersuite( -//! KEM::X25519, -//! HashFunction::Blake3, -//! SignatureScheme::Ed25519, -//! None, -//! ).unwrap(); -//! -//! // Client: Create request -//! let (session_secret, client_context, request_data) = create_request( -//! ciphersuite, -//! &client_signing_key, -//! &responder_dh_public_key -//! ).unwrap(); -//! -//! // Gateway: Handle request -//! let response_data = handle_request( -//! &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, -//! ).unwrap(); -//! ``` - -use crate::LpError; -use crate::message::{KKTRequestData, KKTResponseData}; -use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_kkt::ciphersuite::{Ciphersuite, EncapsulationKey}; -use nym_kkt::context::KKTContext; -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. -/// -/// This is called by the **client (initiator)** to begin the KKT exchange. -/// The returned context must be used when processing the 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 -/// -/// # Errors -/// Returns `LpError::KKTError` if KKT request generation fails. -pub fn create_request( - ciphersuite: Ciphersuite, - signing_key: &ed25519::PrivateKey, - 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 (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()))?; - - Ok((session_secret, context, KKTRequestData(request_bytes))) -} - -/// Processes a KKT response and extracts the responder's KEM public key. -/// -/// This is called by the **client (initiator)** after receiving a KKT response -/// from the gateway. It verifies the signature and validates the key hash. -/// -/// # 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 -/// -/// # Returns -/// * `EncapsulationKey` - Authenticated KEM public key of the responder -/// -/// # Errors -/// Returns `LpError::KKTError` if: -/// - Response deserialization fails -/// - Signature verification fails -/// - 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, - ) - .map_err(|e| LpError::KKTError(e.to_string())) -} - -/// Handles a KKT request and generates a signed response with the responder's KEM key. -/// -/// This is called by the **gateway (responder)** when receiving a KKT request -/// from a client. It validates the request signature (if authenticated) and -/// responds with the gateway's KEM public key, signed for authenticity. -/// -/// # Arguments -/// * `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 -/// * `KKTResponseData` - Signed response frame containing the KEM public key -/// -/// # Errors -/// Returns `LpError::KKTError` if: -/// - Request deserialization fails -/// - Signature verification fails (if authenticated) -/// - Response generation fails -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 { - let mut rng = rand09::rng(); - // Handle the request and generate response - 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()))?; - - 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_ed25519, generate_keypair_libcrux, generate_keypair_x25519, - hash_encapsulation_key, - }; - use rand09::RngCore; - - #[test] - fn test_kkt_roundtrip_authenticated() { - let mut rng = rand09::rng(); - - // 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 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(); - let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); - - // Create ciphersuite - let ciphersuite = Ciphersuite::resolve_ciphersuite( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - None, - ) - .unwrap(); - - // Hash the KEM key (simulating directory storage) - let key_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &responder_kem_key.encode(), - ); - - // Client: Create request - 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_ed25519_keypair.public_key()), - responder_ed25519_keypair.private_key(), - responder_x25519.private_key(), - &responder_kem_key, - ) - .unwrap(); - - // Client: Process response - let obtained_key = process_response( - context, - &session_secret, - responder_ed25519_keypair.public_key(), - &key_hash, - &response_data, - ) - .unwrap(); - - // Verify we got the correct KEM key - assert_eq!(obtained_key.encode(), responder_kem_key.encode()); - } - - // #[test] - // fn test_kkt_roundtrip_anonymous() { - // let mut rng = rand09::rng(); - - // // Only responder has keys (anonymous initiator) - // // Generate Ed25519 keypairs for both parties - - // 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); - - // let ciphersuite = Ciphersuite::resolve_ciphersuite( - // KEM::X25519, - // HashFunction::Blake3, - // SignatureScheme::Ed25519, - // None, - // ) - // .unwrap(); - - // let key_hash = hash_encapsulation_key( - // &ciphersuite.hash_function(), - // ciphersuite.hash_len(), - // &responder_kem_key.encode(), - // ); - - // // 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()); - - // // 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(); - - // 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 responder_x25519 = generate_keypair_x25519(&mut rng); - - // Different keypair for wrong signature - let mut wrong_secret = [0u8; 32]; - rng.fill_bytes(&mut wrong_secret); - let wrong_keypair = ed25519::KeyPair::from_secret(wrong_secret, 2); - - let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); - let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); - - let ciphersuite = Ciphersuite::resolve_ciphersuite( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - None, - ) - .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_ed25519_keypair.private_key(), - responder_x25519.private_key(), - &responder_kem_key, - ); - - // Should fail signature verification - assert!(result.is_err()); - if let Err(LpError::KKTError(_)) = result { - // Expected - } else { - panic!("Expected KKTError"); - } - } - - #[test] - fn test_hash_mismatch_rejected() { - let (init, resp) = mock_peers(); - let responder_kem_key = resp.encapsulate_kem_key().unwrap(); - - let ciphersuite = Ciphersuite::resolve_ciphersuite( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - None, - ) - .unwrap(); - - // Use WRONG hash - let wrong_hash = [0u8; 32]; - - 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(init.ed25519.public_key()), - resp.ed25519.private_key(), - resp.x25519.private_key(), - &responder_kem_key, - ) - .unwrap(); - - // Client validates with WRONG hash - let result = process_response( - context, - &session_secret, - resp.ed25519.public_key(), - &wrong_hash, // Wrong! - &response_data, - ); - - // Should fail hash validation - assert!(result.is_err()); - if let Err(LpError::KKTError(_)) = result { - // Expected - } else { - panic!("Expected KKTError"); - } - } - - #[test] - fn test_malformed_request_rejected() { - let mut rng = rand09::rng(); - - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - 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); - - // Create malformed request data (invalid bytes) - let malformed_request = KKTRequestData(vec![0xFF; 100]); - - let result = handle_request( - &malformed_request, - None, - responder_ed25519_keypair.private_key(), - responder_x25519.private_key(), - &responder_kem_key, - ); - - // Should fail to parse - assert!(result.is_err()); - if let Err(LpError::KKTError(_)) = result { - // Expected - } else { - panic!("Expected KKTError"); - } - } - - #[test] - fn test_malformed_response_rejected() { - let mut rng = rand09::rng(); - - // 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 responder_x25519 = generate_keypair_x25519(&mut rng); - - let ciphersuite = Ciphersuite::resolve_ciphersuite( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - None, - ) - .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]); - let key_hash = [0u8; 32]; - - let result = process_response( - context, - &session_secret, - responder_ed25519_keypair.public_key(), - &key_hash, - &malformed_response, - ); - - // Should fail to parse - assert!(result.is_err()); - if let Err(LpError::KKTError(_)) = result { - // Expected - } else { - panic!("Expected KKTError"); - } - } -} diff --git a/common/nym-lp/src/lib.rs b/common/nym-lp/src/lib.rs index be5bb991af..7f2c80b404 100644 --- a/common/nym-lp/src/lib.rs +++ b/common/nym-lp/src/lib.rs @@ -2,32 +2,40 @@ // SPDX-License-Identifier: Apache-2.0 pub mod codec; -pub mod config; pub mod error; -// pub mod kkt_orchestrator; -pub mod message; -pub mod noise_protocol; pub mod packet; pub mod peer; -pub mod psk; -mod psq; +pub mod peer_config; +pub mod psq; pub mod replay; pub mod session; mod session_integration; pub mod session_manager; pub mod state_machine; +pub mod transport; -pub use config::LpConfig; pub use error::LpError; -pub use message::{ClientHelloData, LpMessage}; -pub use packet::{BOOTSTRAP_RECEIVER_IDX, LpPacket, OuterHeader}; +pub use nym_kkt_ciphersuite::{ + Ciphersuite, HashFunction, HashLength, KEM, KEMKeyDigests, SignatureScheme, +}; + +#[cfg(any(feature = "mock", test))] pub use replay::{ReceivingKeyCounterValidator, ReplayError}; pub use session::LpSession; pub use session_manager::SessionManager; pub use state_machine::LpStateMachine; -pub const NOISE_PATTERN: &str = "Noise_XKpsk3_25519_ChaChaPoly_SHA256"; -pub const NOISE_PSK_INDEX: u8 = 3; +#[cfg(any(feature = "mock", test))] +use nym_test_utils::helpers::u64_seeded_rng_09; + +#[cfg(any(feature = "mock", test))] +use crate::psq::{PSQ_MSG2_SIZE, initiator, psq_msg1_size, responder}; + +#[cfg(any(feature = "mock", test))] +use crate::session::PersistentSessionBinding; + +#[cfg(any(feature = "mock", test))] +use libcrux_psq::{Channel, IntoSession}; #[cfg(any(feature = "mock", test))] pub struct SessionsMock { @@ -37,118 +45,103 @@ pub struct SessionsMock { #[cfg(any(feature = "mock", test))] impl SessionsMock { - pub fn mock_post_handshake(session_id: u32) -> SessionsMock { + pub fn mock_seeded_post_handshake(seed: u64, kem: KEM) -> SessionsMock { use crate::peer::mock_peers; - use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; + use crate::peer_config::LpReceiverIndex; + use rand09::Rng; let (init, resp) = mock_peers(); let resp_remote = resp.as_remote(); - let init_remote = init.as_remote(); - let salt = [42u8; 32]; - let session_id_bytes = session_id.to_le_bytes(); + + let mut init_rng = u64_seeded_rng_09(seed); + let resp_rng = u64_seeded_rng_09(seed + 1); + + let receiver_index: LpReceiverIndex = init_rng.random(); + + let kem_keys = resp.kem_keypairs.as_ref().unwrap(); // skip KKT by just deriving the kem key locally - let kem_keys = resp.kem_psq.as_ref().unwrap(); + let encapsulation_key = kem_keys.encapsulation_key(kem).unwrap(); + let enc_key = encapsulation_key.clone(); - let libcrux_private_key = libcrux_kem::PrivateKey::decode( - libcrux_kem::Algorithm::X25519, - kem_keys.private_key().as_bytes(), - ) - .unwrap(); - let decapsulation_key = DecapsulationKey::X25519(libcrux_private_key); + let initiator_ciphersuite = + initiator::build_psq_ciphersuite(&init, &resp_remote, &enc_key).unwrap(); + let mut initiator = + initiator::build_psq_principal(init_rng, 1, initiator_ciphersuite).unwrap(); - let libcrux_public_key = libcrux_kem::PublicKey::decode( - libcrux_kem::Algorithm::X25519, - kem_keys.public_key().as_bytes(), - ) - .unwrap(); - let encapsulation_key = EncapsulationKey::X25519(libcrux_public_key); + let responder_ciphersuite = responder::build_psq_ciphersuite(&resp, kem).unwrap(); + let mut responder = + responder::build_psq_principal(resp_rng, 1, responder_ciphersuite).unwrap(); - // INIT -> RESP: PSQ MSG1 - let psq_initiator = crate::psk::psq_initiator_create_message( - init.x25519.private_key(), - &resp_remote.x25519_public, - &encapsulation_key, - init.ed25519.private_key(), - init.ed25519.public_key(), - &salt, - &session_id_bytes, - ) - .unwrap(); + // run PSQ + let mut payload_buf_responder = vec![0u8; 4096]; + let mut payload_buf_initiator = vec![0u8; 4096]; - let psk = psq_initiator.psk; - let psq_payload = psq_initiator.payload; - let outer_aead_key = crate::codec::OuterAeadKey::from_psk(&psk); + // Send first message + let mut buf = vec![0u8; psq_msg1_size(kem)]; + let len_i = initiator.write_message(&[], &mut buf).unwrap(); + assert_eq!(len_i, buf.len()); - let noise_state_init = snow::Builder::new(crate::noise_protocol::NoiseProtocol::params()) - .local_private_key(init.x25519().private_key().as_bytes()) - .remote_public_key(resp_remote.x25519_public.as_bytes()) - .psk(crate::NOISE_PSK_INDEX, &psk) - .build_initiator() + // Read first message + let (_, _) = responder + .read_message(&buf, &mut payload_buf_responder) .unwrap(); - let mut noise_protocol_init = crate::noise_protocol::NoiseProtocol::new(noise_state_init); - let noise_msg1 = noise_protocol_init.get_bytes_to_send().unwrap().unwrap(); - let psq_responder = crate::psk::psq_responder_process_message( - resp.x25519.private_key(), - &init_remote.x25519_public, - (&decapsulation_key, &encapsulation_key), - &init_remote.ed25519_public, - &psq_payload, - &salt, - &session_id_bytes, - ) - .unwrap(); + // Get the authenticator out here, so we can deserialize the session later. + let Some(initiator_authenticator) = responder.initiator_authenticator() else { + panic!("No initiator authenticator found") + }; - let noise_state_resp = snow::Builder::new(crate::noise_protocol::NoiseProtocol::params()) - .local_private_key(resp.x25519().private_key().as_bytes()) - .remote_public_key(init_remote.x25519_public.as_bytes()) - .psk(crate::NOISE_PSK_INDEX, &psk) - .build_responder() + // Respond + let mut buf = [0u8; PSQ_MSG2_SIZE]; + let len_r = responder.write_message(&[], &mut buf).unwrap(); + assert_eq!(len_r, buf.len()); + + // Finalize on registration initiator + let (_, _) = initiator + .read_message(&buf, &mut payload_buf_initiator) .unwrap(); - let mut noise_protocol_resp = crate::noise_protocol::NoiseProtocol::new(noise_state_resp); - noise_protocol_resp.read_message(&noise_msg1).unwrap(); - let noise_msg2 = noise_protocol_resp.get_bytes_to_send().unwrap().unwrap(); - noise_protocol_init.read_message(&noise_msg2).unwrap(); - let noise_msg3 = noise_protocol_init.get_bytes_to_send().unwrap().unwrap(); + assert!(initiator.is_handshake_finished()); + assert!(responder.is_handshake_finished()); - assert!(noise_protocol_init.is_handshake_finished()); - - noise_protocol_resp.read_message(&noise_msg3).unwrap(); - assert!(noise_protocol_resp.is_handshake_finished()); + let binding = PersistentSessionBinding { + initiator_authenticator, + responder_ecdh_pk: resp_remote.x25519_public, + responder_pq_pk: Some(encapsulation_key), + }; SessionsMock { initiator: LpSession::new( - session_id, + initiator.into_session().unwrap(), + binding.clone(), + receiver_index, 1, - outer_aead_key.clone(), - init, - resp_remote, - crate::session::PqSharedSecret::new(psq_initiator.pq_shared_secret), - noise_protocol_init, - ), + ) + .unwrap(), responder: LpSession::new( - session_id, + responder.into_session().unwrap(), + binding, + receiver_index, 1, - outer_aead_key, - resp, - init_remote, - crate::session::PqSharedSecret::new(psq_responder.pq_shared_secret), - noise_protocol_resp, - ), + ) + .unwrap(), } } + pub fn mock_post_handshake(kem: KEM) -> SessionsMock { + Self::mock_seeded_post_handshake(1, kem) + } + // we just need a dummy 'valid' session for simpler tests pub fn mock_initiator() -> LpSession { - Self::mock_post_handshake(1234).initiator + Self::mock_post_handshake(KEM::default()).initiator } } #[cfg(any(feature = "mock", test))] pub fn sessions_for_tests() -> (LpSession, LpSession) { - let sessions = SessionsMock::mock_post_handshake(69); + let sessions = SessionsMock::mock_post_handshake(KEM::default()); (sessions.initiator, sessions.responder) } @@ -156,224 +149,3 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) { pub fn mock_session_for_test() -> LpSession { SessionsMock::mock_initiator() } - -#[cfg(test)] -mod tests { - use crate::message::LpMessage; - use crate::packet::{LpHeader, LpPacket, TRAILER_LEN}; - use crate::session_manager::SessionManager; - use crate::{LpError, SessionsMock, mock_session_for_test}; - use bytes::BytesMut; - - // Import the new standalone functions - use crate::codec::{parse_lp_packet, serialize_lp_packet}; - - #[test] - fn test_replay_protection_integration() { - // Create session - let mut session = mock_session_for_test(); - - // === Packet 1 (Counter 0 - Should succeed) === - let packet1 = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, // Matches session's sending_index assumption for this test - counter: 0, - }, - message: LpMessage::Busy, - trailer: [0u8; TRAILER_LEN], - }; - - // Serialize packet - let mut buf1 = BytesMut::new(); - serialize_lp_packet(&packet1, &mut buf1, None).unwrap(); - - // Parse packet - let parsed_packet1 = parse_lp_packet(&buf1, None).unwrap(); - - // Perform replay check (should pass) - session - .receiving_counter_quick_check(parsed_packet1.header.counter) - .expect("Initial packet failed replay check"); - - // Mark received (simulating successful processing) - session - .receiving_counter_mark(parsed_packet1.header.counter) - .expect("Failed to mark initial packet received"); - - // === Packet 2 (Counter 0 - Replay, should fail check) === - let packet2 = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, - counter: 0, // Same counter as before (replay) - }, - message: LpMessage::Busy, - trailer: [0u8; TRAILER_LEN], - }; - - // Serialize packet - let mut buf2 = BytesMut::new(); - serialize_lp_packet(&packet2, &mut buf2, None).unwrap(); - - // Parse packet - let parsed_packet2 = parse_lp_packet(&buf2, None).unwrap(); - - // Perform replay check (should fail) - let replay_result = session.receiving_counter_quick_check(parsed_packet2.header.counter); - assert!(replay_result.is_err()); - match replay_result.unwrap_err() { - LpError::Replay(e) => { - assert!(matches!(e, crate::replay::ReplayError::DuplicateCounter)); - } - e => panic!("Expected replay error, got {:?}", e), - } - // Do not mark received as it failed validation - - // === Packet 3 (Counter 1 - Should succeed) === - let packet3 = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, - counter: 1, // Incremented counter - }, - message: LpMessage::Busy, - trailer: [0u8; TRAILER_LEN], - }; - - // Serialize packet - let mut buf3 = BytesMut::new(); - serialize_lp_packet(&packet3, &mut buf3, None).unwrap(); - - // Parse packet - let parsed_packet3 = parse_lp_packet(&buf3, None).unwrap(); - - // Perform replay check (should pass) - session - .receiving_counter_quick_check(parsed_packet3.header.counter) - .expect("Packet 3 failed replay check"); - - // Mark received - session - .receiving_counter_mark(parsed_packet3.header.counter) - .expect("Failed to mark packet 3 received"); - - // Verify validator state directly on the session - let state = session.current_packet_cnt(); - assert_eq!(state.0, 2); // Next expected counter (correct - was 1, now expects 2) - assert_eq!(state.1, 2); // Total marked received (correct - packets 1 and 3) - } - - #[test] - fn test_session_manager_integration() { - // Create session manager - let mut local_manager = SessionManager::new(); - let mut remote_manager = SessionManager::new(); - - // Use fixed receiver_index for deterministic test - let receiver_index: u32 = 54321; - - let sessions = SessionsMock::mock_post_handshake(receiver_index); - let local_session = sessions.initiator; - let remote_session = sessions.responder; - - // Create a session via manager - let _ = local_manager.create_session_state_machine(local_session); - let _ = remote_manager.create_session_state_machine(remote_session); - - // === Packet 1 (Counter 0 - Should succeed) === - let packet1 = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: receiver_index, - counter: 0, - }, - message: LpMessage::Busy, - trailer: [0u8; TRAILER_LEN], - }; - - // Serialize - let mut buf1 = BytesMut::new(); - serialize_lp_packet(&packet1, &mut buf1, None).unwrap(); - - // Parse - let parsed_packet1 = parse_lp_packet(&buf1, None).unwrap(); - - // Process via SessionManager method (which should handle checks + marking) - // NOTE: We might need a method on SessionManager/LpSession like `process_incoming_packet` - // that encapsulates parse -> check -> process_noise -> mark. - // For now, we simulate the steps using the retrieved session. - - // Perform replay check - local_manager - .receiving_counter_quick_check(receiver_index, parsed_packet1.header.counter) - .expect("Packet 1 check failed"); - // Mark received - local_manager - .receiving_counter_mark(receiver_index, parsed_packet1.header.counter) - .expect("Packet 1 mark failed"); - - // === Packet 2 (Counter 1 - Should succeed on same session) === - let packet2 = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: receiver_index, - counter: 1, - }, - message: LpMessage::Busy, - trailer: [0u8; TRAILER_LEN], - }; - - // Serialize - let mut buf2 = BytesMut::new(); - serialize_lp_packet(&packet2, &mut buf2, None).unwrap(); - - // Parse - let parsed_packet2 = parse_lp_packet(&buf2, None).unwrap(); - - // Perform replay check - local_manager - .receiving_counter_quick_check(receiver_index, parsed_packet2.header.counter) - .expect("Packet 2 check failed"); - // Mark received - local_manager - .receiving_counter_mark(receiver_index, parsed_packet2.header.counter) - .expect("Packet 2 mark failed"); - - // === Packet 3 (Counter 0 - Replay, should fail check) === - let packet3 = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: receiver_index, - counter: 0, // Replay of first packet - }, - message: LpMessage::Busy, - trailer: [0u8; TRAILER_LEN], - }; - - // Serialize - let mut buf3 = BytesMut::new(); - serialize_lp_packet(&packet3, &mut buf3, None).unwrap(); - - // Parse - let parsed_packet3 = parse_lp_packet(&buf3, None).unwrap(); - - // Perform replay check (should fail) - let replay_result = local_manager - .receiving_counter_quick_check(receiver_index, parsed_packet3.header.counter); - assert!(replay_result.is_err()); - match replay_result.unwrap_err() { - LpError::Replay(e) => { - assert!(matches!(e, crate::replay::ReplayError::DuplicateCounter)); - } - e => panic!("Expected replay error for packet 3, got {:?}", e), - } - // Do not mark received - } -} diff --git a/common/nym-lp/src/message.rs b/common/nym-lp/src/message.rs deleted file mode 100644 index fbd692175b..0000000000 --- a/common/nym-lp/src/message.rs +++ /dev/null @@ -1,892 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::packet::LpHeader; -use crate::peer::LpRemotePeer; -use crate::{BOOTSTRAP_RECEIVER_IDX, LpError, LpPacket}; -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, Copy, Serialize, Deserialize, PartialEq)] -pub struct ClientHelloData { - /// Client-proposed receiver index for session identification (4 bytes) - /// 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: x25519::PublicKey, - /// Client's Ed25519 public key (32 bytes) - for PSQ authentication - 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; - - pub fn into_lp_packet(self, protocol_version: u8) -> LpPacket { - LpPacket::new( - LpHeader::new( - BOOTSTRAP_RECEIVER_IDX, // session_id not yet established - 0, // counter starts at 0 - protocol_version, - ), - LpMessage::ClientHello(self), - ) - } - - fn len(&self) -> usize { - Self::LEN - } - - fn generate_receiver_index() -> u32 { - loop { - let candidate = rand::random(); - if candidate != BOOTSTRAP_RECEIVER_IDX { - return candidate; - } - } - } - - /// Generates a new ClientHelloData with fresh salt. - /// - /// Salt format: 8 bytes timestamp (u64 LE) + 24 bytes random nonce - /// - /// # Arguments - /// * `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: x25519::PublicKey, - client_ed25519_public_key: ed25519::PublicKey, - timestamp: u64, - ) -> Self { - // Generate salt: timestamp + nonce - let mut salt = [0u8; 32]; - - // First 8 bytes: current timestamp as u64 little-endian - salt[..8].copy_from_slice(×tamp.to_le_bytes()); - - // Last 24 bytes: random nonce - rand::thread_rng().fill_bytes(&mut salt[8..]); - - Self { - receiver_index: Self::generate_receiver_index(), // Auto-generate random receiver index - client_lp_public_key, - client_ed25519_public_key, - salt, - } - } - - /// Extracts the timestamp from the salt. - /// - /// # Returns - /// Unix timestamp in seconds - pub fn extract_timestamp(&self) -> u64 { - let mut timestamp_bytes = [0u8; 8]; - timestamp_bytes.copy_from_slice(&self.salt[..8]); - u64::from_le_bytes(timestamp_bytes) - } - - 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!( - "Expected {} bytes to deserialise ClientHelloData. got {}", - Self::LEN, - b.len() - ))); - } - - // 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: 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)] -#[repr(u32)] -pub enum MessageType { - Busy = 0x0000, - Handshake = 0x0001, - EncryptedData = 0x0002, - ClientHello = 0x0003, - KKTRequest = 0x0004, - KKTResponse = 0x0005, - ForwardPacket = 0x0006, - /// Receiver index collision - client should retry with new index - Collision = 0x0007, - /// Acknowledgment - gateway confirms receipt of message - Ack = 0x0008, - /// Subsession request - client initiates subsession creation - SubsessionRequest = 0x0009, - /// Subsession KK1 - first message of Noise KK handshake - SubsessionKK1 = 0x000A, - /// Subsession KK2 - second message of Noise KK handshake - SubsessionKK2 = 0x000B, - /// Subsession ready - subsession established confirmation - SubsessionReady = 0x000C, - /// Subsession abort - race winner tells loser to become responder - SubsessionAbort = 0x000D, - /// General error - Error = 0x00FF, -} - -impl MessageType { - pub(crate) fn from_u32(value: u32) -> Option { - MessageType::try_from(value).ok() - } - - pub fn to_u32(&self) -> u32 { - u32::from(*self) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct HandshakeData(pub Vec); - -impl HandshakeData { - pub(crate) fn new(bytes: Vec) -> Self { - Self(bytes) - } - 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 { - #[allow(dead_code)] - pub(crate) fn new(bytes: Vec) -> Self { - Self(bytes) - } - - 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 { - pub(crate) fn new(bytes: Vec) -> Self { - Self(bytes) - } - - 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 { - pub(crate) fn new(bytes: Vec) -> Self { - Self(bytes) - } - - 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())) - } -} - -/// General human-readable error message -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ErrorPacketData { - pub message: String, -} - -impl ErrorPacketData { - pub(crate) fn new(message: impl Into) -> Self { - ErrorPacketData { - message: message.into(), - } - } - - fn len(&self) -> usize { - // length-encoding + message - 4 + self.message.len() - } - - fn encode(&self, dst: &mut BytesMut) { - dst.put_u32_le(self.message.len() as u32); - dst.put_slice(self.message.as_bytes()); - } - - fn decode(bytes: &[u8]) -> Result { - if bytes.len() < 4 { - return Err(LpError::DeserializationError(format!( - "Too few bytes to deserialise ErrorPacketData. got {}", - bytes.len() - ))); - } - - let message_len = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize; - if bytes[4..].len() != message_len { - return Err(LpError::DeserializationError(format!( - "Wrong number of bytes to deserialise ErrorPacketData. got {}. Expected {}", - bytes.len(), - 4 + message_len - ))); - } - - let message = String::from_utf8_lossy(&bytes[4..]).to_string(); - - Ok(ErrorPacketData { message }) - } -} - -/// Packet forwarding request with embedded inner LP packet -#[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: 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, 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, 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, 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, - Handshake(HandshakeData), - EncryptedData(EncryptedDataPayload), - ClientHello(ClientHelloData), - KKTRequest(KKTRequestData), - KKTResponse(KKTResponseData), - ForwardPacket(ForwardPacketData), - /// Receiver index collision - client should retry with new receiver_index - Collision, - /// Acknowledgment - gateway confirms receipt of message - Ack, - /// Subsession request - client initiates subsession creation (empty, signal only) - SubsessionRequest, - /// Subsession KK1 - first message of Noise KK handshake - SubsessionKK1(SubsessionKK1Data), - /// Subsession KK2 - second message of Noise KK handshake - SubsessionKK2(SubsessionKK2Data), - /// Subsession ready - subsession established confirmation - SubsessionReady(SubsessionReadyData), - /// Subsession abort - race winner tells loser to become responder (empty, signal only) - SubsessionAbort, - /// An error has occurred - Error(ErrorPacketData), -} - -impl From for LpMessage { - fn from(value: HandshakeData) -> Self { - LpMessage::Handshake(value) - } -} - -impl From for LpMessage { - fn from(value: EncryptedDataPayload) -> Self { - LpMessage::EncryptedData(value) - } -} - -impl From for LpMessage { - fn from(value: ClientHelloData) -> Self { - LpMessage::ClientHello(value) - } -} - -impl From for LpMessage { - fn from(value: KKTRequestData) -> Self { - LpMessage::KKTRequest(value) - } -} - -impl From for LpMessage { - fn from(value: KKTResponseData) -> Self { - LpMessage::KKTResponse(value) - } -} - -impl From for LpMessage { - fn from(value: ForwardPacketData) -> Self { - LpMessage::ForwardPacket(value) - } -} - -impl From for LpMessage { - fn from(value: SubsessionKK1Data) -> Self { - LpMessage::SubsessionKK1(value) - } -} - -impl From for LpMessage { - fn from(value: SubsessionKK2Data) -> Self { - LpMessage::SubsessionKK2(value) - } -} - -impl From for LpMessage { - fn from(value: SubsessionReadyData) -> Self { - LpMessage::SubsessionReady(value) - } -} - -impl Display for LpMessage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LpMessage::Busy => write!(f, "Busy"), - LpMessage::Handshake(_) => write!(f, "Handshake"), - LpMessage::EncryptedData(_) => write!(f, "EncryptedData"), - LpMessage::ClientHello(_) => write!(f, "ClientHello"), - LpMessage::KKTRequest(_) => write!(f, "KKTRequest"), - LpMessage::KKTResponse(_) => write!(f, "KKTResponse"), - LpMessage::ForwardPacket(_) => write!(f, "ForwardPacket"), - LpMessage::Collision => write!(f, "Collision"), - LpMessage::Ack => write!(f, "Ack"), - LpMessage::SubsessionRequest => write!(f, "SubsessionRequest"), - LpMessage::SubsessionKK1(_) => write!(f, "SubsessionKK1"), - LpMessage::SubsessionKK2(_) => write!(f, "SubsessionKK2"), - LpMessage::SubsessionReady(_) => write!(f, "SubsessionReady"), - LpMessage::SubsessionAbort => write!(f, "SubsessionAbort"), - LpMessage::Error(_) => write!(f, "Error"), - } - } -} - -impl LpMessage { - pub fn payload(&self) -> &[u8] { - match self { - LpMessage::Busy => &[], - LpMessage::Handshake(payload) => payload.0.as_slice(), - LpMessage::EncryptedData(payload) => payload.0.as_slice(), - LpMessage::ClientHello(_) => &[], // Structured data, serialized in encode_content - LpMessage::KKTRequest(payload) => payload.0.as_slice(), - LpMessage::KKTResponse(payload) => payload.0.as_slice(), - LpMessage::ForwardPacket(_) => &[], // Structured data, serialized in encode_content - LpMessage::Collision => &[], - LpMessage::Ack => &[], - LpMessage::SubsessionRequest => &[], - LpMessage::SubsessionKK1(_) => &[], // Structured data, serialized in encode_content - LpMessage::SubsessionKK2(_) => &[], // Structured data, serialized in encode_content - LpMessage::SubsessionReady(_) => &[], // Structured data, serialized in encode_content - LpMessage::SubsessionAbort => &[], - LpMessage::Error(_) => &[], // Structured data, serialized in encode_content (?) - } - } - - pub fn is_empty(&self) -> bool { - match self { - LpMessage::Busy => true, - LpMessage::Handshake(payload) => payload.0.is_empty(), - LpMessage::EncryptedData(payload) => payload.0.is_empty(), - LpMessage::ClientHello(_) => false, // Always has data - LpMessage::KKTRequest(payload) => payload.0.is_empty(), - LpMessage::KKTResponse(payload) => payload.0.is_empty(), - LpMessage::ForwardPacket(_) => false, // Always has data - LpMessage::Collision => true, - LpMessage::Ack => true, - LpMessage::SubsessionRequest => true, // Empty signal - LpMessage::SubsessionKK1(_) => false, // Always has payload - LpMessage::SubsessionKK2(_) => false, // Always has payload - LpMessage::SubsessionReady(_) => false, // Always has receiver_index - LpMessage::SubsessionAbort => true, // Empty signal - LpMessage::Error(_) => false, - } - } - - pub fn len(&self) -> usize { - match self { - LpMessage::Busy => 0, - 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, - LpMessage::SubsessionKK1(payload) => payload.len(), - LpMessage::SubsessionKK2(payload) => payload.len(), - LpMessage::SubsessionReady(payload) => payload.len(), - LpMessage::SubsessionAbort => 0, - LpMessage::Error(payload) => payload.len(), - } - } - - pub fn typ(&self) -> MessageType { - match self { - LpMessage::Busy => MessageType::Busy, - LpMessage::Handshake(_) => MessageType::Handshake, - LpMessage::EncryptedData(_) => MessageType::EncryptedData, - LpMessage::ClientHello(_) => MessageType::ClientHello, - LpMessage::KKTRequest(_) => MessageType::KKTRequest, - LpMessage::KKTResponse(_) => MessageType::KKTResponse, - LpMessage::ForwardPacket(_) => MessageType::ForwardPacket, - LpMessage::Collision => MessageType::Collision, - LpMessage::Ack => MessageType::Ack, - LpMessage::SubsessionRequest => MessageType::SubsessionRequest, - LpMessage::SubsessionKK1(_) => MessageType::SubsessionKK1, - LpMessage::SubsessionKK2(_) => MessageType::SubsessionKK2, - LpMessage::SubsessionReady(_) => MessageType::SubsessionReady, - LpMessage::SubsessionAbort => MessageType::SubsessionAbort, - LpMessage::Error(_) => MessageType::Error, - } - } - - pub fn encode_content(&self, dst: &mut BytesMut) { - match self { - LpMessage::Busy => { /* No content */ } - 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) => data.encode(dst), - LpMessage::SubsessionKK2(data) => data.encode(dst), - LpMessage::SubsessionReady(data) => data.encode(dst), - LpMessage::SubsessionAbort => { /* No content - signal only */ } - LpMessage::Error(data) => data.encode(dst), - } - } - - /// 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) - } - MessageType::Error => Ok(LpMessage::Error(ErrorPacketData::decode(content)?)), - } - } -} - -/// 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)] -mod tests { - use std::time::{SystemTime, UNIX_EPOCH}; - - use super::*; - use crate::LpPacket; - use crate::packet::{LpHeader, TRAILER_LEN}; - - #[test] - fn encoding() { - let message = LpMessage::EncryptedData(EncryptedDataPayload(vec![11u8; 124])); - - let resp_header = LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 0, - counter: 0, - }; - - let packet = LpPacket { - header: resp_header, - message, - trailer: [80; TRAILER_LEN], - }; - - // Just print packet for debug, will be captured in test output - println!("{packet:?}"); - - // Verify message type - assert!(matches!(packet.message.typ(), MessageType::EncryptedData)); - - // Verify correct data in message - match &packet.message { - LpMessage::EncryptedData(data) => { - assert_eq!(*data, EncryptedDataPayload(vec![11u8; 124])); - } - _ => panic!("Wrong message type"), - } - } - - #[test] - fn test_client_hello_salt_generation() { - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("System time before UNIX epoch") - .as_secs(); - - 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 = - ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); - - // Different salts should be generated - assert_ne!(hello1.salt, hello2.salt); - - // But timestamps should be very close (within 1 second) - let ts1 = hello1.extract_timestamp(); - let ts2 = hello2.extract_timestamp(); - assert!((ts1 as i64 - ts2 as i64).abs() <= 1); - } - - #[test] - fn test_client_hello_timestamp_extraction() { - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("System time before UNIX epoch") - .as_secs(); - 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(); - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs(); - - // Timestamp should be within 1 second of now - assert!((timestamp as i64 - now as i64).abs() <= 1); - } - - #[test] - fn test_client_hello_salt_format() { - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("System time before UNIX epoch") - .as_secs(); - 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 - let timestamp_bytes = &hello.salt[..8]; - assert_ne!(timestamp_bytes, &[0u8; 8]); - - // Salt should be 32 bytes total - assert_eq!(hello.salt.len(), 32); - } -} diff --git a/common/nym-lp/src/noise_protocol.rs b/common/nym-lp/src/noise_protocol.rs deleted file mode 100644 index c56f7cd9b9..0000000000 --- a/common/nym-lp/src/noise_protocol.rs +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -//! Sans-IO Noise protocol state machine, adapted from noise-psq. - -use snow::{TransportState, params::NoiseParams}; -use thiserror::Error; - -// --- Error Definition --- - -/// Errors related to the Noise protocol state machine. -#[derive(Error, Debug)] -pub enum NoiseError { - #[error("encountered a Noise decryption error")] - DecryptionError, - - #[error("encountered a Noise Protocol error - {0}")] - ProtocolError(snow::Error), - - #[error("operation is invalid in the current protocol state")] - IncorrectStateError, - - #[error("attempted transport mode operation without real PSK injection")] - PskNotInjected, - - #[error("Other Noise-related error: {0}")] - Other(String), - - #[error("session is read-only after demotion")] - SessionReadOnly, -} - -impl From for NoiseError { - fn from(err: snow::Error) -> Self { - match err { - snow::Error::Decrypt => NoiseError::DecryptionError, - err => NoiseError::ProtocolError(err), - } - } -} - -// --- Protocol State and Structs --- - -/// Represents the possible states of the Noise protocol machine. -#[derive(Debug)] -pub enum NoiseProtocolState { - /// The protocol is currently performing the handshake. - /// Contains the Snow handshake state. - Handshaking(Box), - - /// The handshake is complete, and the protocol is in transport mode. - /// Contains the Snow transport state. - Transport(TransportState), - - /// The protocol has encountered an unrecoverable error. - /// Stores the error description. - Failed(String), -} - -/// The core sans-io Noise protocol state machine. -#[derive(Debug)] -pub struct NoiseProtocol { - state: NoiseProtocolState, - // We might need buffers for incoming/outgoing data later if we add internal buffering - // read_buffer: Vec, - // write_buffer: Vec, -} - -/// Represents the outcome of processing received bytes via `read_message`. -#[derive(Debug, PartialEq)] -pub enum ReadResult { - /// A handshake or transport message was successfully processed, but yielded no application data - /// and did not complete the handshake. - NoOp, - /// A complete application data message was decrypted. - DecryptedData(Vec), - /// The handshake successfully completed during this read operation. - HandshakeComplete, - // NOTE: NeedMoreBytes variant removed as read_message expects full frames. -} - -// --- Implementation --- - -impl NoiseProtocol { - pub fn params() -> NoiseParams { - // SAFETY: the hardcoded pattern must be valid - // and if for some reason it was not, we MUST fail non-gracefully for there is no possible recovery - #[allow(clippy::unwrap_used)] - crate::NOISE_PATTERN.parse().unwrap() - } - - /// Creates a new `NoiseProtocol` instance in the Handshaking state. - /// - /// Takes an initialized `snow::HandshakeState` (e.g., from `snow::Builder`). - pub fn new(initial_state: snow::HandshakeState) -> Self { - NoiseProtocol { - state: NoiseProtocolState::Handshaking(Box::new(initial_state)), - } - } - - fn prepare_handshake_state<'a>( - local_private_key: &'a [u8], - remote_public_key: &'a [u8], - psk: &'a [u8], - ) -> snow::Builder<'a> { - let psk_index = crate::NOISE_PSK_INDEX; - let noise_params = NoiseProtocol::params(); - - snow::Builder::new(noise_params) - .local_private_key(local_private_key) - .remote_public_key(remote_public_key) - .psk(psk_index, psk) - } - - /// Builds a new `NoiseProtocol` initiator instance with the provided local private key, - /// remote public key and psk - pub fn build_new_initiator( - local_private_key: &[u8], - remote_public_key: &[u8], - psk: &[u8], - ) -> Result { - let handshake_state = - Self::prepare_handshake_state(local_private_key, remote_public_key, psk) - .build_initiator()?; - Ok(Self::new(handshake_state)) - } - - /// Builds a new `NoiseProtocol` responder instance with the provided local private key, - /// remote public key and psk - pub fn build_new_responder( - local_private_key: &[u8], - remote_public_key: &[u8], - psk: &[u8], - ) -> Result { - let handshake_state = - Self::prepare_handshake_state(local_private_key, remote_public_key, psk) - .build_responder()?; - Ok(Self::new(handshake_state)) - } - - /// Processes a single, complete incoming Noise message frame. - /// - /// Assumes the caller handles buffering and framing to provide one full message. - /// Returns the result of processing the message. - pub fn read_message(&mut self, input: &[u8]) -> Result { - // Allocate a buffer large enough for the maximum possible Noise message size. - // TODO: Consider reusing a buffer for efficiency. - let mut buffer = vec![0u8; 65535]; // Max Noise message size - - match &mut self.state { - NoiseProtocolState::Handshaking(handshake_state) => { - match handshake_state.read_message(input, &mut buffer) { - Ok(_) => { - if handshake_state.is_handshake_finished() { - // Transition to Transport state. - let current_state = std::mem::replace( - &mut self.state, - // Temporary placeholder needed for mem::replace - NoiseProtocolState::Failed( - NoiseError::IncorrectStateError.to_string(), - ), - ); - if let NoiseProtocolState::Handshaking(state_to_convert) = current_state - { - match state_to_convert.into_transport_mode() { - Ok(transport_state) => { - self.state = NoiseProtocolState::Transport(transport_state); - Ok(ReadResult::HandshakeComplete) - } - Err(e) => { - let err = NoiseError::from(e); - self.state = NoiseProtocolState::Failed(err.to_string()); - Err(err) - } - } - } else { - // Should be unreachable - let err = NoiseError::IncorrectStateError; - self.state = NoiseProtocolState::Failed(err.to_string()); - Err(err) - } - } else { - // Handshake continues - Ok(ReadResult::NoOp) - } - } - Err(e) => { - let err = NoiseError::from(e); - self.state = NoiseProtocolState::Failed(err.to_string()); - Err(err) - } - } - } - NoiseProtocolState::Transport(transport_state) => { - match transport_state.read_message(input, &mut buffer) { - Ok(len) => Ok(ReadResult::DecryptedData(buffer[..len].to_vec())), - Err(e) => { - let err = NoiseError::from(e); - self.state = NoiseProtocolState::Failed(err.to_string()); - Err(err) - } - } - } - NoiseProtocolState::Failed(_) => Err(NoiseError::IncorrectStateError), - } - } - - /// Checks if there are pending handshake messages to send. - /// - /// If in Handshaking state and it's our turn, generates the message. - /// Transitions state to Transport if the handshake completes after this message. - /// Returns `None` if not in Handshaking state or not our turn. - pub fn get_bytes_to_send(&mut self) -> Option, NoiseError>> { - match &mut self.state { - NoiseProtocolState::Handshaking(handshake_state) => { - if handshake_state.is_my_turn() { - let mut buffer = vec![0u8; 65535]; - match handshake_state.write_message(&[], &mut buffer) { - // Empty payload for handshake msg - Ok(len) => { - if handshake_state.is_handshake_finished() { - // Transition to Transport state. - let current_state = std::mem::replace( - &mut self.state, - NoiseProtocolState::Failed( - NoiseError::IncorrectStateError.to_string(), - ), - ); - if let NoiseProtocolState::Handshaking(state_to_convert) = - current_state - { - match state_to_convert.into_transport_mode() { - Ok(transport_state) => { - self.state = - NoiseProtocolState::Transport(transport_state); - Some(Ok(buffer[..len].to_vec())) // Return final handshake msg - } - Err(e) => { - let err = NoiseError::from(e); - self.state = - NoiseProtocolState::Failed(err.to_string()); - Some(Err(err)) - } - } - } else { - // Should be unreachable - let err = NoiseError::IncorrectStateError; - self.state = NoiseProtocolState::Failed(err.to_string()); - Some(Err(err)) - } - } else { - // Handshake continues - Some(Ok(buffer[..len].to_vec())) - } - } - Err(e) => { - let err = NoiseError::from(e); - self.state = NoiseProtocolState::Failed(err.to_string()); - Some(Err(err)) - } - } - } else { - // Not our turn - None - } - } - NoiseProtocolState::Transport(_) | NoiseProtocolState::Failed(_) => { - // No handshake messages to send in these states - None - } - } - } - - /// Encrypts an application data payload for sending during the Transport phase. - /// - /// Returns the ciphertext (payload + 16-byte tag). - /// Errors if not in Transport state or encryption fails. - pub fn write_message(&mut self, payload: &[u8]) -> Result, NoiseError> { - match &mut self.state { - NoiseProtocolState::Transport(transport_state) => { - let mut buffer = vec![0u8; payload.len() + 16]; // Payload + tag - match transport_state.write_message(payload, &mut buffer) { - Ok(len) => Ok(buffer[..len].to_vec()), - Err(e) => { - let err = NoiseError::from(e); - self.state = NoiseProtocolState::Failed(err.to_string()); - Err(err) - } - } - } - NoiseProtocolState::Handshaking(_) | NoiseProtocolState::Failed(_) => { - Err(NoiseError::IncorrectStateError) - } - } - } - - /// Returns true if the protocol is in the transport phase (handshake complete). - pub fn is_transport(&self) -> bool { - matches!(self.state, NoiseProtocolState::Transport(_)) - } - - /// Returns true if the protocol has failed. - pub fn is_failed(&self) -> bool { - matches!(self.state, NoiseProtocolState::Failed(_)) - } - - /// Check if the handshake has finished and the protocol is in transport mode. - pub fn is_handshake_finished(&self) -> bool { - matches!(self.state, NoiseProtocolState::Transport(_)) - } - - /// Inject a PSK into the Noise HandshakeState. - /// - /// This allows dynamic PSK injection after HandshakeState construction, - /// which is required for PSQ (Post-Quantum Secure PSK) integration where - /// the PSK is derived during the handshake process. - /// - /// # Arguments - /// * `index` - PSK index (typically 3 for XKpsk3 pattern) - /// * `psk` - The pre-shared key bytes to inject - /// - /// # Errors - /// Returns an error if: - /// - Not in handshake state - /// - The underlying snow library rejects the PSK - pub fn set_psk(&mut self, index: u8, psk: &[u8]) -> Result<(), NoiseError> { - match &mut self.state { - NoiseProtocolState::Handshaking(handshake_state) => { - handshake_state - .set_psk(index as usize, psk) - .map_err(NoiseError::ProtocolError)?; - Ok(()) - } - _ => Err(NoiseError::IncorrectStateError), - } - } -} diff --git a/common/nym-lp/src/packet.rs b/common/nym-lp/src/packet.rs deleted file mode 100644 index db2644b5fa..0000000000 --- a/common/nym-lp/src/packet.rs +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::LpError; -use crate::message::{LpMessage, MessageType}; -use crate::replay::ReceivingKeyCounterValidator; -use bytes::{BufMut, BytesMut}; -use nym_lp_common::format_debug_bytes; -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; -#[allow(dead_code)] -pub(crate) const IP_HEADER_LEN: usize = 40; // v4 - 20, v6 - 40 -#[allow(dead_code)] -pub(crate) const MTU: usize = 1500; -#[allow(dead_code)] -pub(crate) const UDP_OVERHEAD: usize = UDP_HEADER_LEN + IP_HEADER_LEN; - -#[allow(dead_code)] -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, - pub(crate) message: LpMessage, - pub(crate) trailer: [u8; TRAILER_LEN], -} - -impl Debug for LpPacket { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", format_debug_bytes(&self.debug_bytes())?) - } -} - -impl LpPacket { - pub fn new(header: LpHeader, message: LpMessage) -> Self { - Self { - header, - message, - trailer: [0; TRAILER_LEN], - } - } - - pub fn typ(&self) -> MessageType { - self.message.typ() - } - - /// Compute a hash of the message payload - /// - /// This can be used for message integrity verification or deduplication - pub fn hash_payload(&self) -> [u8; 32] { - use sha2::{Digest, Sha256}; - - let mut hasher = Sha256::new(); - let mut buffer = BytesMut::new(); - - // Include message type and content in the hash - buffer.put_slice(&(self.message.typ() as u16).to_le_bytes()); - self.message.encode_content(&mut buffer); - - hasher.update(&buffer); - hasher.finalize().into() - } - - pub fn hash_payload_hex(&self) -> String { - let hash = self.hash_payload(); - hash.iter() - .fold(String::with_capacity(hash.len() * 2), |mut acc, byte| { - let _ = write!(acc, "{:02x}", byte); - acc - }) - } - - pub fn message(&self) -> &LpMessage { - &self.message - } - - pub fn header(&self) -> &LpHeader { - &self.header - } - - pub(crate) fn debug_bytes(&self) -> Vec { - let mut bytes = BytesMut::new(); - self.encode(&mut bytes); - bytes.freeze().to_vec() - } - - pub(crate) fn encode(&self, dst: &mut BytesMut) { - self.header.encode(dst); - - dst.put_slice(&(self.message.typ() as u16).to_le_bytes()); - self.message.encode_content(dst); - - dst.put_slice(&self.trailer) - } - - /// Validate packet counter against a replay protection validator - /// - /// This performs a quick check to see if the packet counter is valid before - /// any expensive processing is done. - pub fn validate_counter( - &self, - validator: &Arc>, - ) -> Result<(), LpError> { - let guard = validator.lock(); - guard.will_accept_branchless(self.header.counter)?; - Ok(()) - } - - /// Mark packet as received in the replay protection validator - /// - /// This should be called after a packet has been successfully processed. - pub fn mark_received( - &self, - validator: &Arc>, - ) -> Result<(), LpError> { - let mut guard = validator.lock(); - guard.mark_did_receive_branchless(self.header.counter)?; - Ok(()) - } -} - -/// Session ID used for ClientHello bootstrap packets before session is established. -/// -/// When a client first connects, it sends a ClientHello packet with receiver_idx=0 -/// because neither side can compute the deterministic session ID yet (requires -/// both parties' X25519 keys). After ClientHello is processed, both sides derive -/// the same session ID from their keys, and all subsequent packets use that ID. -pub const BOOTSTRAP_RECEIVER_IDX: u32 = 0; - -/// Outer header (12 bytes) - always cleartext, used for routing. -/// -/// This is the first 12 bytes of every LP packet, containing only the fields -/// needed for session lookup (receiver_idx) and replay protection (counter). -/// For encrypted packets, this is the AAD (additional authenticated data). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct OuterHeader { - pub receiver_idx: u32, - pub counter: u64, -} - -impl OuterHeader { - pub const SIZE: usize = 12; // receiver_idx(4) + counter(8) - - pub fn new(receiver_idx: u32, counter: u64) -> Self { - Self { - receiver_idx, - counter, - } - } - - pub fn parse(src: &[u8]) -> Result { - if src.len() < Self::SIZE { - return Err(LpError::InsufficientBufferSize); - } - Ok(Self { - receiver_idx: u32::from_le_bytes(src[0..4].try_into().unwrap()), - counter: u64::from_le_bytes(src[4..12].try_into().unwrap()), - }) - } - - pub fn encode(&self) -> [u8; Self::SIZE] { - let mut buf = [0u8; Self::SIZE]; - buf[0..4].copy_from_slice(&self.receiver_idx.to_le_bytes()); - buf[4..12].copy_from_slice(&self.counter.to_le_bytes()); - buf - } - - /// Encode directly into a BytesMut buffer - pub fn encode_into(&self, dst: &mut BytesMut) { - dst.put_slice(&self.receiver_idx.to_le_bytes()); - dst.put_slice(&self.counter.to_le_bytes()); - } -} - -/// Internal LP header representation containing all logical header fields. -/// -/// **Note**: This struct represents the LOGICAL header, not the wire format. -/// On the wire, packets use the unified format where: -/// - `OuterHeader` (receiver_idx + counter) always comes first (12 bytes, cleartext) -/// - Inner content (version + reserved + payload) follows (cleartext or encrypted) -/// -/// The `LpHeader::encode()` method outputs the old logical format for debug purposes only. -/// Use `serialize_lp_packet()` in codec.rs for actual wire serialization. -#[derive(Debug, Clone)] -pub struct LpHeader { - pub protocol_version: u8, - pub reserved: [u8; 3], - pub receiver_idx: u32, - pub counter: u64, -} - -impl LpHeader { - pub const SIZE: usize = 16; -} - -impl LpHeader { - pub fn new(receiver_idx: u32, counter: u64, protocol_version: u8) -> Self { - Self { - protocol_version, - reserved: [0u8; 3], - receiver_idx, - counter, - } - } - - pub fn encode(&self, dst: &mut BytesMut) { - // protocol version - dst.put_u8(self.protocol_version); - - // reserved - dst.put_slice(&self.reserved); - - // sender index - dst.put_slice(&self.receiver_idx.to_le_bytes()); - - // counter - dst.put_slice(&self.counter.to_le_bytes()); - } - - pub fn parse(src: &[u8]) -> Result { - if src.len() < Self::SIZE { - return Err(LpError::InsufficientBufferSize); - } - - let protocol_version = src[0]; - - // 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]); - let receiver_idx = u32::from_le_bytes(receiver_idx_bytes); - - let mut counter_bytes = [0u8; 8]; - counter_bytes.copy_from_slice(&src[8..16]); - let counter = u64::from_le_bytes(counter_bytes); - - Ok(LpHeader { - protocol_version, - reserved, - receiver_idx, - counter, - }) - } - - /// Get the counter value from the header - pub fn counter(&self) -> u64 { - self.counter - } - - /// Get the sender index from the header - pub fn receiver_idx(&self) -> u32 { - self.receiver_idx - } -} - -// subsequent data: MessageType || Data diff --git a/common/nym-lp/src/packet/error.rs b/common/nym-lp/src/packet/error.rs new file mode 100644 index 0000000000..baf1146cbb --- /dev/null +++ b/common/nym-lp/src/packet/error.rs @@ -0,0 +1,33 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum MalformedLpPacketError { + #[error("failed to deserialise received data: {0}")] + DeserialisationFailure(String), + + #[error("provided insufficient data to fully deserialise the struct")] + InsufficientData, + + #[error("{0} is not a valid LpDataKind")] + InvalidLpDataKind(u16), + + #[error("invalid payload size: expected {expected}, got {actual}")] + InvalidPayloadSize { expected: usize, actual: usize }, + + /// 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 }, +} + +impl MalformedLpPacketError { + pub fn invalid_data_kind(message_type: u16) -> Self { + MalformedLpPacketError::InvalidLpDataKind(message_type) + } +} diff --git a/common/nym-lp/src/packet/header.rs b/common/nym-lp/src/packet/header.rs new file mode 100644 index 0000000000..4055284f58 --- /dev/null +++ b/common/nym-lp/src/packet/header.rs @@ -0,0 +1,148 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::packet::version; +use crate::{packet::error::MalformedLpPacketError, peer_config::LpReceiverIndex}; +use bytes::{BufMut, BytesMut}; +use tracing::warn; + +/// Outer header (12 bytes) - always cleartext, used for routing. +/// +/// This is the first 12 bytes of every LP packet, containing only the fields +/// needed for session lookup (receiver_idx) and replay protection (counter). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OuterHeader { + pub receiver_idx: LpReceiverIndex, + pub counter: u64, +} + +impl OuterHeader { + pub const SIZE: usize = 12; // receiver_idx(4) + counter(8) + + pub fn new(receiver_idx: LpReceiverIndex, counter: u64) -> Self { + Self { + receiver_idx, + counter, + } + } + + pub fn parse(src: &[u8]) -> Result { + if src.len() < Self::SIZE { + return Err(MalformedLpPacketError::InsufficientData); + } + #[allow(clippy::unwrap_used)] + Ok(Self { + receiver_idx: LpReceiverIndex::from_le_bytes(src[0..4].try_into().unwrap()), + counter: u64::from_le_bytes(src[4..12].try_into().unwrap()), + }) + } + + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + let mut bytes = [0u8; Self::SIZE]; + bytes[0..4].copy_from_slice(&self.receiver_idx.to_le_bytes()); + bytes[4..12].copy_from_slice(&self.counter.to_le_bytes()); + bytes + } + + /// Encode directly into a BytesMut buffer + pub fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.receiver_idx.to_le_bytes()); + dst.put_slice(&self.counter.to_le_bytes()); + } +} + +/// InnerHeader header (8 bytes) - encrypted, used for message parsing +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InnerHeader { + pub protocol_version: u8, + pub reserved: [u8; 3], +} + +impl InnerHeader { + pub const SIZE: usize = 4; // protocol_version(1) + reserved(3) + + pub fn encode(&self, dst: &mut BytesMut) { + // protocol version + dst.put_u8(self.protocol_version); + + // reserved + dst.put_slice(&self.reserved); + } + + pub fn parse(src: &[u8]) -> Result { + if src.len() < Self::SIZE { + return Err(MalformedLpPacketError::InsufficientData); + } + + let protocol_version = src[0]; + + // Ensure we are using compatible protocol + // right now only support a single version + if protocol_version > version::CURRENT { + return Err(MalformedLpPacketError::IncompatibleFuturePacketVersion { + got: protocol_version, + highest_supported: version::CURRENT, + }); + } + + if protocol_version < version::CURRENT { + return Err(MalformedLpPacketError::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:?}"); + } + + Ok(InnerHeader { + protocol_version, + reserved, + }) + } +} + +/// Internal LP header representation containing all logical header fields. +/// +/// **Note**: This struct represents the LOGICAL header, not the wire format. +/// On the wire, packets use the unified format where: +/// - `OuterHeader` (receiver_idx + counter) always comes first (12 bytes, cleartext) +/// - Inner content (version + reserved + payload) follows (cleartext or encrypted) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct LpHeader { + pub outer: OuterHeader, + pub inner: InnerHeader, +} + +impl LpHeader { + pub fn new(receiver_idx: LpReceiverIndex, counter: u64, protocol_version: u8) -> Self { + Self { + outer: OuterHeader { + receiver_idx, + counter, + }, + inner: InnerHeader { + protocol_version, + reserved: [0u8; 3], + }, + } + } + + pub(crate) fn dbg_encode(&self, dst: &mut BytesMut) { + self.outer.encode(dst); + self.inner.encode(dst); + } + + /// Get the counter value from the header + pub fn counter(&self) -> u64 { + self.outer.counter + } + + /// Get the sender index from the header + pub fn receiver_idx(&self) -> LpReceiverIndex { + self.outer.receiver_idx + } +} diff --git a/common/nym-lp/src/packet/message.rs b/common/nym-lp/src/packet/message.rs new file mode 100644 index 0000000000..97a2c94fb9 --- /dev/null +++ b/common/nym-lp/src/packet/message.rs @@ -0,0 +1,255 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::packet::error::MalformedLpPacketError; +use bytes::{BufMut, Bytes, BytesMut}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + +#[derive(Debug, Clone, PartialEq)] +pub struct LpMessageHeader { + pub kind: LpMessageType, + pub message_attributes: [u8; 14], +} + +impl LpMessageHeader { + pub const SIZE: usize = 16; // message_kind(2) + message_attributes(14) + + pub fn new(kind: LpMessageType, message_attributes: [u8; 14]) -> Self { + Self { + kind, + message_attributes, + } + } + + pub fn new_no_attributes(kind: LpMessageType) -> Self { + Self { + kind, + message_attributes: [0; 14], + } + } + + /// Encode directly into a BytesMut buffer + pub fn encode(&self, dst: &mut BytesMut) { + dst.put_u16_le(self.kind as u16); + dst.put_slice(&self.message_attributes); + } + + pub fn parse(src: &[u8]) -> Result { + if src.len() < Self::SIZE { + return Err(MalformedLpPacketError::InsufficientData); + } + let raw_kind = u16::from_le_bytes([src[0], src[1]]); + + let kind = LpMessageType::try_from(raw_kind) + .map_err(|_| MalformedLpPacketError::invalid_data_kind(raw_kind))?; + + #[allow(clippy::unwrap_used)] + let message_attributes = src[2..16].try_into().unwrap(); + Ok(Self { + kind, + message_attributes, + }) + } +} + +/// Represent application data being sent in Transport mode +#[derive(Debug, Clone, PartialEq)] +pub struct LpMessage { + pub header: LpMessageHeader, + pub content: Bytes, +} + +impl AsRef<[u8]> for LpMessage { + fn as_ref(&self) -> &[u8] { + &self.content + } +} + +impl LpMessage { + pub fn new(kind: LpMessageType, content: impl Into) -> Self { + Self { + header: LpMessageHeader::new_no_attributes(kind), + content: content.into(), + } + } + + pub fn encode(&self, dst: &mut BytesMut) { + self.header.encode(dst); + + dst.put_slice(&self.content); + } + + pub fn decode(src: &[u8]) -> Result { + let header = LpMessageHeader::parse(src)?; + let content = src[LpMessageHeader::SIZE..].to_vec().into(); + + Ok(Self { header, content }) + } + + pub fn kind(&self) -> LpMessageType { + self.header.kind + } + + pub fn new_opaque(content: impl Into) -> Self { + Self::new(LpMessageType::Opaque, content) + } + + pub fn new_registration(data: impl Into) -> Self { + Self::new(LpMessageType::Registration, data) + } + + pub fn new_forward(data: impl Into) -> Self { + Self::new(LpMessageType::Forward, data) + } + + pub(crate) fn len(&self) -> usize { + LpMessageHeader::SIZE + self.content.len() + } +} + +/// Represent kind of application data being sent in Transport mode +#[derive(Clone, Copy, PartialEq, Eq, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u16)] +pub enum LpMessageType { + Opaque = 0, + Registration = 1, + Forward = 2, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ExpectedResponseSize { + /// We've sent a handshake message and expect response of predefined size + Handshake(u32), + + /// We've sent a transport message and the response is length-prefixed + Transport, +} + +impl ExpectedResponseSize { + pub fn to_bytes(&self) -> [u8; 4] { + // there are no empty handshake messages, so we use 0 bytes to indicate Transport variant + match self { + ExpectedResponseSize::Handshake(size) => size.to_le_bytes(), + ExpectedResponseSize::Transport => [0u8; 4], + } + } + + pub fn from_bytes(b: [u8; 4]) -> Self { + let size = u32::from_le_bytes(b); + if size == 0 { + ExpectedResponseSize::Transport + } else { + ExpectedResponseSize::Handshake(size) + } + } +} + +/// Packet forwarding request with embedded inner LP packet +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ForwardPacketData { + /// Target gateway's LP address (IP:port string) + pub target_lp_address: SocketAddr, + + /// Indication of the expected size of the response + /// to allow the proxy to read correct data from the stream + pub expected_response_size: ExpectedResponseSize, + + /// 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_lp_address: SocketAddr, + expected_response_size: ExpectedResponseSize, + inner_packet_bytes: Vec, + ) -> Self { + ForwardPacketData { + target_lp_address, + expected_response_size, + inner_packet_bytes, + } + } + + // 0 || [4B ipv4] || [2B port] || [4B res size] || [4B plen] || payload + // 1 || [16B ipv6] || [2B port] || [4B res size] || [4B plen] || payload + 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_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_slice(&self.expected_response_size.to_bytes()); + 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(b: &[u8]) -> Result { + // smallest possible packet with ipv4 and empty data + if b.len() < 15 { + // 1 + 4 + 2 + 4 + 4 + 0 + return Err(MalformedLpPacketError::DeserialisationFailure(format!( + "Too few bytes to deserialise ForwardPacketData. got {}", + b.len() + ))); + } + + let target_lp_address_is_ipv6 = b[0] != 0; + + let (target_lp_address, i) = if target_lp_address_is_ipv6 { + // IPv6, first check we have actually enough bytes + // smallest possible packet with ipv6 and empty data + if b.len() < 27 { + // 1 + 16 + 2 + 4 + 4+ 0 + return Err(MalformedLpPacketError::DeserialisationFailure(format!( + "Too few bytes to deserialise ipv6 ForwardPacketData. got {}", + b.len() + ))); + } + // Ipv6Addr::from_octets is not available until 1.91 so we have to use + // the slightly less obvious u128 conversion + // SAFETY: we ensured we have sufficient data, and the length is correct for casting + #[allow(clippy::unwrap_used)] + let ipv6 = IpAddr::V6(Ipv6Addr::from_bits(u128::from_be_bytes( + b[1..17].try_into().unwrap(), + ))); + let port = u16::from_le_bytes([b[17], b[18]]); + (SocketAddr::new(ipv6, port), 19) + } else { + // IPv4. Length check done at the start + + // Ipv4Addr::from_octets is not available until 1.91 + let ipv4 = IpAddr::V4(Ipv4Addr::new(b[1], b[2], b[3], b[4])); + let port = u16::from_le_bytes([b[5], b[6]]); + (SocketAddr::new(ipv4, port), 7) + }; + + let expected_response_size_bytes = [b[i], b[i + 1], b[i + 2], b[i + 3]]; + let inner_packet_bytes_len = u32::from_le_bytes([b[i + 4], b[i + 5], b[i + 6], b[i + 7]]); + + if b[i + 8..].len() != inner_packet_bytes_len as usize { + return Err(MalformedLpPacketError::DeserialisationFailure(format!( + "Expected {inner_packet_bytes_len} bytes to deserialise inner packet bytes of ForwardPacketData. got {}", + b[i + 8..].len() + ))); + } + let inner_packet_bytes = b[i + 8..].to_vec(); + + Ok(ForwardPacketData { + target_lp_address, + expected_response_size: ExpectedResponseSize::from_bytes(expected_response_size_bytes), + inner_packet_bytes, + }) + } +} diff --git a/common/nym-lp/src/packet/mod.rs b/common/nym-lp/src/packet/mod.rs new file mode 100644 index 0000000000..6ece484603 --- /dev/null +++ b/common/nym-lp/src/packet/mod.rs @@ -0,0 +1,120 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::packet::utils::format_debug_bytes; +use bytes::{BufMut, BytesMut}; +use std::fmt::{Debug, Formatter}; + +pub use error::MalformedLpPacketError; +pub use header::{InnerHeader, LpHeader, OuterHeader}; +pub use message::{ForwardPacketData, LpMessage}; + +pub mod error; +pub mod header; +pub mod message; +pub mod replay; +pub mod utils; + +pub mod version { + /// The current version of the Lewes Protocol that is put into each new constructed header. + pub const CURRENT: u8 = 1; +} + +#[allow(dead_code)] +pub(crate) const UDP_HEADER_LEN: usize = 8; +#[allow(dead_code)] +pub(crate) const IP_HEADER_LEN: usize = 40; // v4 - 20, v6 - 40 +#[allow(dead_code)] +pub(crate) const MTU: usize = 1500; +#[allow(dead_code)] +pub(crate) const UDP_OVERHEAD: usize = UDP_HEADER_LEN + IP_HEADER_LEN; +#[allow(dead_code)] +pub(crate) const UDP_PAYLOAD_SIZE: usize = MTU - UDP_OVERHEAD; + +#[derive(Clone)] +pub struct EncryptedLpPacket { + // The outer header that's sent in plaintext + pub(crate) outer_header: OuterHeader, + + // The ciphertext containing the inner header and the payload + pub(crate) ciphertext: Vec, +} + +impl std::fmt::Debug for EncryptedLpPacket { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", format_debug_bytes(&self.debug_bytes())?) + } +} + +impl EncryptedLpPacket { + pub fn new(outer_header: OuterHeader, ciphertext: Vec) -> EncryptedLpPacket { + EncryptedLpPacket { + outer_header, + ciphertext, + } + } + + pub fn encoded_length(&self) -> usize { + OuterHeader::SIZE + self.ciphertext.len() + } + + pub(crate) fn debug_bytes(&self) -> Vec { + let mut bytes = BytesMut::new(); + self.encode(&mut bytes); + bytes.freeze().to_vec() + } + + pub fn encode(&self, dst: &mut BytesMut) { + self.outer_header.encode(dst); + dst.put_slice(&self.ciphertext) + } + + pub fn ciphertext(&self) -> &[u8] { + &self.ciphertext + } + + pub fn outer_header(&self) -> OuterHeader { + self.outer_header + } +} + +#[derive(Clone, PartialEq)] +pub struct LpPacket { + pub(crate) header: LpHeader, + pub(crate) message: LpMessage, +} + +impl Debug for LpPacket { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", format_debug_bytes(&self.debug_bytes())?) + } +} + +impl LpPacket { + pub fn new(header: LpHeader, message: LpMessage) -> Self { + Self { header, message } + } + + pub fn message(&self) -> &LpMessage { + &self.message + } + + pub fn into_message(self) -> LpMessage { + self.message + } + + pub fn header(&self) -> &LpHeader { + &self.header + } + + pub(crate) fn debug_bytes(&self) -> Vec { + let mut bytes = BytesMut::new(); + self.dbg_encode(&mut bytes); + bytes.freeze().to_vec() + } + + pub(crate) fn dbg_encode(&self, dst: &mut BytesMut) { + self.header.dbg_encode(dst); + self.message.encode(dst) + } +} diff --git a/common/nym-lp/src/packet/replay.rs b/common/nym-lp/src/packet/replay.rs new file mode 100644 index 0000000000..5cbdc3c0d2 --- /dev/null +++ b/common/nym-lp/src/packet/replay.rs @@ -0,0 +1,33 @@ +use crate::{LpError, packet::LpPacket, replay::ReceivingKeyCounterValidator}; + +pub trait LpPacketReplayExt { + /// Validate packet counter against a replay protection validator + /// + /// This performs a quick check to see if the packet counter is valid before + /// any expensive processing is done. + fn validate_counter(&self, validator: &ReceivingKeyCounterValidator) -> Result<(), LpError>; + + /// Mark packet as received in the replay protection validator + /// + /// This should be called after a packet has been successfully processed. + fn mark_received(&self, validator: &mut ReceivingKeyCounterValidator) -> Result<(), LpError>; +} + +impl LpPacketReplayExt for LpPacket { + /// Validate packet counter against a replay protection validator + /// + /// This performs a quick check to see if the packet counter is valid before + /// any expensive processing is done. + fn validate_counter(&self, validator: &ReceivingKeyCounterValidator) -> Result<(), LpError> { + validator.will_accept_branchless(self.header().outer.counter)?; + Ok(()) + } + + /// Mark packet as received in the replay protection validator + /// + /// This should be called after a packet has been successfully processed. + fn mark_received(&self, validator: &mut ReceivingKeyCounterValidator) -> Result<(), LpError> { + validator.mark_did_receive_branchless(self.header().outer.counter)?; + Ok(()) + } +} diff --git a/common/nym-lp-common/src/lib.rs b/common/nym-lp/src/packet/utils.rs similarity index 84% rename from common/nym-lp-common/src/lib.rs rename to common/nym-lp/src/packet/utils.rs index 98da6cebd3..4abe90c213 100644 --- a/common/nym-lp-common/src/lib.rs +++ b/common/nym-lp/src/packet/utils.rs @@ -1,8 +1,4 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt; -use std::fmt::Write; +use std::fmt::{self, Write}; pub fn format_debug_bytes(bytes: &[u8]) -> Result { let mut out = String::new(); diff --git a/common/nym-lp/src/peer.rs b/common/nym-lp/src/peer.rs index 085ea8be5b..d68d1caf9f 100644 --- a/common/nym-lp/src/peer.rs +++ b/common/nym-lp/src/peer.rs @@ -1,102 +1,77 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::{ClientHelloData, LpError}; -use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_kkt::ciphersuite::{Ciphersuite, KEM, KEMKeyDigests, SignatureScheme, SigningKeyDigests}; -use std::collections::HashMap; +use crate::LpError; +use nym_kkt_ciphersuite::{Ciphersuite, KEM, KEMKeyDigests}; +use std::collections::BTreeMap; +use std::fmt::Debug; use std::sync::Arc; +pub use libcrux_psq::handshake::types::{DHKeyPair, DHPublicKey}; +pub use nym_kkt::key_utils::{ + generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519, +}; +pub use nym_kkt::keys::KEMKeys; + /// Representation of a local Lewes Protocol peer /// encapsulating all the known information and keys. -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct LpLocalPeer { - /// Local Ed25519 keys for PSQ authentication - pub(crate) ed25519: Arc, + pub(crate) ciphersuite: Ciphersuite, /// Local x25519 keys (Noise static key) - pub(crate) x25519: Arc, + pub(crate) x25519: Arc, - /// Local KEM key used for PSQ - pub(crate) kem_psq: Option>, + /// Local KEM keys used for PSQ + pub(crate) kem_keypairs: Option, } impl LpLocalPeer { - pub fn new(ed25519: Arc, x25519: Arc) -> Self { + pub fn new(ciphersuite: Ciphersuite, x25519: Arc) -> Self { LpLocalPeer { - ed25519, + ciphersuite, x25519, - kem_psq: None, + kem_keypairs: Default::default(), } } - 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); + pub fn with_kem_keys(mut self, kem_keys: KEMKeys) -> Self { + self.kem_keypairs = Some(kem_keys); self } - pub fn ed25519(&self) -> &Arc { - &self.ed25519 - } - - pub fn x25519(&self) -> &Arc { + pub fn x25519(&self) -> &Arc { &self.x25519 } - /// 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.kem_psq - .as_ref() - .map(|kp| kp.public_key()) - .ok_or(LpError::ResponderWithMissingKEMKey) - } - /// 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()), - ); + let expected_kem_key_digests = self + .kem_keypairs + .as_ref() + .map(|k| k.encapsulation_keys_digests()) + .unwrap_or_default(); LpRemotePeer { - ed25519_public: *self.ed25519.public_key(), - x25519_public: *self.x25519.public_key(), + x25519_public: self.x25519.pk, 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()?; + pub fn ciphersuite(&self) -> Ciphersuite { + self.ciphersuite + } +} - Some(nym_kkt::ciphersuite::EncapsulationKey::X25519(libcrux_pk)) +impl Debug for LpLocalPeer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LpLocalPeer") + .field("ciphersuite", &self.ciphersuite) + .field("x25519", &self.x25519.pk) + .field("kem_keypairs", &self.kem_keypairs) + .finish() } } @@ -104,45 +79,31 @@ impl LpLocalPeer { /// 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, + pub(crate) x25519_public: DHPublicKey, /// 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, + pub(crate) expected_kem_key_digests: BTreeMap, } impl LpRemotePeer { - pub fn new(ed25519_public: ed25519::PublicKey, x25519_public: x25519::PublicKey) -> Self { + pub fn new(x25519_public: DHPublicKey) -> 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 + pub fn x25519(&self) -> &DHPublicKey { + &self.x25519_public } #[must_use] pub fn with_key_digests( mut self, - expected_kem_key_digests: HashMap, - expected_signing_key_digests: HashMap, + expected_kem_key_digests: BTreeMap, ) -> Self { self.expected_kem_key_digests = expected_kem_key_digests; - self.expected_signing_key_digests = expected_signing_key_digests; self } @@ -168,30 +129,40 @@ impl LpRemotePeer { } } +impl From for LpRemotePeer { + fn from(value: DHPublicKey) -> Self { + LpRemotePeer { + x25519_public: value, + expected_kem_key_digests: Default::default(), + } + } +} + #[cfg(any(feature = "mock", test))] pub fn mock_peer() -> LpLocalPeer { // use deterministic rng - let mut rng = nym_test_utils::helpers::deterministic_rng(); + let mut rng = nym_test_utils::helpers::deterministic_rng_09(); random_peer(&mut rng) } #[cfg(any(feature = "mock", 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()); +pub fn random_peer(rng: &mut R) -> LpLocalPeer { + let x25519 = Arc::new(nym_kkt::key_utils::generate_lp_keypair_x25519(rng)); LpLocalPeer { - ed25519, + ciphersuite: Ciphersuite::default(), + x25519, - kem_psq, + kem_keypairs: Some(KEMKeys::new( + nym_kkt::key_utils::generate_keypair_mceliece(rng), + nym_kkt::key_utils::generate_keypair_mlkem(rng), + )), } } #[cfg(any(feature = "mock", test))] pub fn mock_peers() -> (LpLocalPeer, LpLocalPeer) { - // use deterministic rng - let mut rng = nym_test_utils::helpers::deterministic_rng(); + let mut rng = nym_test_utils::helpers::deterministic_rng_09(); (random_peer(&mut rng), random_peer(&mut rng)) } diff --git a/common/nym-lp/src/peer_config.rs b/common/nym-lp/src/peer_config.rs new file mode 100644 index 0000000000..19c4e8e608 --- /dev/null +++ b/common/nym-lp/src/peer_config.rs @@ -0,0 +1,482 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::LpError; +use libcrux_psq::handshake::types::Authenticator; + +use nym_crypto::hkdf::blake3::derive_key_blake3_multi_input; +use nym_kkt::keys::EncapsulationKey; +use rand09::{self, CryptoRng, Rng}; +use tls_codec::Serialize; +use zeroize::Zeroize; + +pub type LpReceiverIndex = u32; + +pub const MAX_HOPS: u8 = 16; +pub const LP_PEER_CONFIG_SIZE: usize = 20; + +const SEED_LEN: usize = 16; +const CONFIG_LEN: usize = 1; +const FILLER_LEN: usize = LP_PEER_CONFIG_SIZE - SEED_LEN - CONFIG_LEN; + +const RECEIVER_INDEX_DERIVATION_CONTEXT: &str = "LP_PEER_CONFIG_RECEIVER_INDEX_DERIVATION_V1"; + +// 20 bytes +#[derive(PartialEq)] +pub struct LpPeerConfig { + // The first 4 fields will be packed in one u8 + // with 1 bit left at the end + + // Determine the hop id. + // Should be 0 if node_initiator is true + // Should be > 1 if is_exit is true + hop_id: u8, + + // Determine if the recipient should be an exit node + is_exit: bool, + + // Determine if we are establishing a node<>node connection + // Should be false if is_exit is true + node_initiator: bool, + + // Enable censorship resistance countermeasures + censorship_resistance: bool, + + // If we add more config params later, we can use this + filler: [u8; FILLER_LEN], + + seed: [u8; SEED_LEN], +} + +impl LpPeerConfig { + /// Creates a new client to entry config. + /// Sets `hop_id` to 0. + /// Input: censorship_resistance flag to enable censorship resistance features. + pub fn new_client_to_entry(rng: &mut R, censorship_resistance: bool) -> Self + where + R: Rng + CryptoRng, + { + Self::build( + 0, + false, + false, + censorship_resistance, + rng.random(), + rng.random(), + ) + } + /// Creates a new client to exit config. + /// Inputs: + /// hop_id: this value must be in the range (1..=15). This function returns an error if this is not the case. + /// censorship_resistance flag to enable censorship resistance features. + pub fn new_client_to_exit( + rng: &mut R, + hop_id: u8, + censorship_resistance: bool, + ) -> Result + where + R: Rng + CryptoRng, + { + Self::new(rng, hop_id, true, false, censorship_resistance) + } + /// Creates a new client to an intermediate node config. + /// Inputs: + /// hop_id: this value must be in the range (1..=14). This function returns an error if this is not the case. + /// censorship_resistance flag to enable censorship resistance features. + pub fn new_client_to_intermediate( + rng: &mut R, + hop_id: u8, + censorship_resistance: bool, + ) -> Result + where + R: Rng + CryptoRng, + { + if hop_id == 0 || hop_id == 15 { + Err(LpError::Internal(format!( + "An intermediate hop cannot be the first or last hop. Requested hop id {hop_id}" + ))) + } else { + Self::new(rng, hop_id, false, false, censorship_resistance) + } + } + + /// Creates a new node to node config. + /// Censorship resistance features are disabled by default between nodes. + pub fn new_node_to_node(rng: &mut R) -> Result + where + R: Rng + CryptoRng, + { + // no need for censorship resistance between nodes (for now) + // hop_id between nodes is 0 + Self::new(rng, 0, false, true, false) + } + + pub fn new( + rng: &mut R, + hop_id: u8, + is_exit: bool, + node_initiator: bool, + censorship_resistance: bool, + ) -> Result + where + R: Rng + CryptoRng, + { + Self::build_checked( + hop_id, + is_exit, + node_initiator, + censorship_resistance, + rng.random(), + rng.random(), + ) + } + fn build( + hop_id: u8, + is_exit: bool, + node_initiator: bool, + censorship_resistance: bool, + seed: [u8; SEED_LEN], + filler: [u8; FILLER_LEN], + ) -> Self { + Self { + hop_id, + is_exit, + node_initiator, + censorship_resistance, + filler, + seed, + } + } + fn build_checked( + hop_id: u8, + is_exit: bool, + node_initiator: bool, + censorship_resistance: bool, + seed: [u8; SEED_LEN], + filler: [u8; FILLER_LEN], + ) -> Result { + if node_initiator && is_exit { + Err(LpError::Internal( + "A node cannot establish an exit node for itself.".into(), + )) + } else if node_initiator && hop_id != 0 { + Err(LpError::Internal( + "Hop id in node to node connections must be zero.".into(), + )) + } else if !node_initiator && hop_id >= MAX_HOPS { + Err(LpError::Internal(format!( + "Requested hop index ({}) is greater than the allowed maximum {}.", + hop_id, + MAX_HOPS - 1 + ))) + } else if !node_initiator && is_exit && hop_id == 0 { + Err(LpError::Internal( + "Hop id for exit node cannot be zero.".into(), + )) + } else if !node_initiator && !is_exit && hop_id == 15 { + Err(LpError::Internal( + "The hop with id 15 must be an exit node.".into(), + )) + } else { + Ok(Self::build( + hop_id, + is_exit, + node_initiator, + censorship_resistance, + seed, + filler, + )) + } + } + + pub fn hop_id(&self) -> u8 { + self.hop_id + } + + pub fn seed(&self) -> &[u8; SEED_LEN] { + &self.seed + } + + pub fn serialize(&self) -> [u8; LP_PEER_CONFIG_SIZE] { + let mut output_bytes: [u8; LP_PEER_CONFIG_SIZE] = [0u8; LP_PEER_CONFIG_SIZE]; + output_bytes[0..4].copy_from_slice(self.pack_config().as_slice()); + output_bytes[4..].copy_from_slice(&self.seed); + output_bytes + } + pub fn deserialize(bytes: &[u8]) -> Result { + if bytes.len() != LP_PEER_CONFIG_SIZE { + Err(LpError::DeserializationError(format!( + "Invalid Lp Config Length ({}), expected ({})", + bytes.len(), + LP_PEER_CONFIG_SIZE + ))) + } else { + let (hop_id, is_exit, node_initiator, censorship_resistance) = + Self::unpack_first_byte(bytes[0]); + + let mut filler: [u8; FILLER_LEN] = [0u8; FILLER_LEN]; + filler.copy_from_slice(&bytes[CONFIG_LEN..CONFIG_LEN + FILLER_LEN]); + + let mut seed: [u8; SEED_LEN] = [0u8; SEED_LEN]; + seed.copy_from_slice(&bytes[CONFIG_LEN + FILLER_LEN..LP_PEER_CONFIG_SIZE]); + + Self::build_checked( + hop_id, + is_exit, + node_initiator, + censorship_resistance, + seed, + filler, + ) + } + } + + fn pack_config(&self) -> [u8; 4] { + [ + self.pack_first_byte(), + self.filler[0], + self.filler[1], + self.filler[2], + ] + } + + fn pack_first_byte(&self) -> u8 { + let mut byte = self.hop_id; + + // Set the 5th bit to determine if the node is an exit node + if self.is_exit { + byte |= 0b0001_0000; + } + // Set the 6th bit to determine if we're establishing a node to node connection + if self.node_initiator { + byte |= 0b0010_0000; + } + // Set the 7th bit to determine if we should use censorship resistance measures + if self.censorship_resistance { + byte |= 0b0100_0000; + } + + // There will be 1 free bit at the end + + byte + } + + fn unpack_first_byte(byte: u8) -> (u8, bool, bool, bool) { + // extract 4 bits + let hop_id = byte & 0b0000_1111; + + // extract 5th bit + let is_exit = (byte & 0b0001_0000) >> 4 == 1; + // extract 6th bit + let node_initiator = (byte & 0b0010_0000) >> 5 == 1; + // extract 7th bit + let censorship_resistance = (byte & 0b0100_0000) >> 6 == 1; + + // If we need to use the last bit, we can add something here + (hop_id, is_exit, node_initiator, censorship_resistance) + } + + pub fn is_client_entry(&self) -> bool { + self.hop_id == 0 && !self.is_exit && !self.node_initiator + } + + pub fn is_client_intermediate_node(&self) -> bool { + self.hop_id > 0 && !self.is_exit && !self.node_initiator + } + + pub fn is_client_exit(&self) -> bool { + self.hop_id > 0 && self.is_exit && !self.node_initiator + } + + pub fn is_node_to_node(&self) -> bool { + self.hop_id == 0 && !self.is_exit && self.node_initiator + } + + // This returns a LpReceiverIndex made out of the first 4 bytes from + // KDF(RECEIVER_INDEX_DERIVATION_CONTEXT, initiator_pub_key || responder_kem_key, seed) + pub fn derive_receiver_index( + &self, + initiator_public_key: &Authenticator, + responder_kem_pk: &EncapsulationKey, + ) -> Result { + let initiator_public_key = initiator_public_key.tls_serialize_detached().map_err(|_| { + LpError::Internal( + "Failed to serialize initiator public key when computing receiver index".into(), + ) + })?; + let mut h = derive_key_blake3_multi_input( + RECEIVER_INDEX_DERIVATION_CONTEXT, + &[initiator_public_key.as_slice(), responder_kem_pk.as_bytes()], + self.seed(), + ); + let index = LpReceiverIndex::from_le_bytes([h[0], h[1], h[2], h[3]]); + h.zeroize(); + Ok(index) + } +} + +#[cfg(test)] +mod test { + use crate::peer_config::LpPeerConfig; + + #[test] + fn test_pack_config() { + let mut rng = rand09::rng(); + + // Node to node, no censorship resistance + { + let expected_conf = 0b0010_0000; + let conf = LpPeerConfig::new(&mut rng, 0, false, true, false).unwrap(); + let conf_bytes = conf.serialize(); + let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes) + .unwrap() + .pack_config()[0]; + + assert_eq!(expected_conf, conf_bytes[0]); + assert_eq!(expected_conf, deserialized_conf_first_byte); + assert_eq!( + conf_bytes[0], + LpPeerConfig::new_node_to_node(&mut rng) + .unwrap() + .serialize()[0] + ); + assert!(conf.is_node_to_node()); + } + + // Node to node, with censorship resistance + { + let expected_conf = 0b0110_0000; + let conf = LpPeerConfig::new(&mut rng, 0, false, true, true).unwrap(); + let conf_bytes = conf.serialize(); + let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes) + .unwrap() + .pack_config()[0]; + + assert_eq!(expected_conf, conf_bytes[0]); + assert_eq!(expected_conf, deserialized_conf_first_byte); + assert!(conf.is_node_to_node()); + } + + // Client to Entry, no censorship resistance + { + let expected_conf = 0b0000_0000; + let conf = LpPeerConfig::new(&mut rng, 0, false, false, false).unwrap(); + let conf_bytes = conf.serialize(); + let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes) + .unwrap() + .pack_config()[0]; + let conf_alt_first_byte = + LpPeerConfig::new_client_to_entry(&mut rng, false).serialize()[0]; + + assert_eq!(expected_conf, conf_bytes[0]); + assert_eq!(expected_conf, deserialized_conf_first_byte); + assert_eq!(conf_bytes[0], conf_alt_first_byte); + assert!(conf.is_client_entry()) + } + + // Client to Entry, with censorship resistance + { + let expected_conf = 0b0100_0000; + let conf = LpPeerConfig::new(&mut rng, 0, false, false, true).unwrap(); + let conf_bytes = conf.serialize(); + let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes) + .unwrap() + .pack_config()[0]; + let conf_alt_first_byte = + LpPeerConfig::new_client_to_entry(&mut rng, true).serialize()[0]; + + assert_eq!(expected_conf, conf_bytes[0]); + assert_eq!(expected_conf, deserialized_conf_first_byte); + assert_eq!(conf_bytes[0], conf_alt_first_byte); + assert!(conf.is_client_entry()); + } + + // Client to Exit(exit hop = 1), with censorship resistance + { + let expected_conf = 0b0101_0001; + let conf = LpPeerConfig::new(&mut rng, 1, true, false, true).unwrap(); + let conf_bytes = conf.serialize(); + let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes) + .unwrap() + .pack_config()[0]; + let conf_alt_first_byte = LpPeerConfig::new_client_to_exit(&mut rng, 1, true) + .unwrap() + .serialize()[0]; + + assert_eq!(expected_conf, conf_bytes[0]); + assert_eq!(expected_conf, deserialized_conf_first_byte); + assert_eq!(conf_bytes[0], conf_alt_first_byte); + assert!(conf.is_client_exit()); + } + + // Client to Exit(exit hop = 2), without censorship resistance + { + let expected_conf = 0b0001_0010; + let conf = LpPeerConfig::new(&mut rng, 2, true, false, false).unwrap(); + let conf_bytes = conf.serialize(); + let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes) + .unwrap() + .pack_config()[0]; + let conf_alt_first_byte = LpPeerConfig::new_client_to_exit(&mut rng, 2, false) + .unwrap() + .serialize()[0]; + + assert_eq!(expected_conf, conf_bytes[0]); + assert_eq!(expected_conf, deserialized_conf_first_byte); + assert_eq!(conf_bytes[0], conf_alt_first_byte); + assert!(conf.is_client_exit()); + } + // Client to Intermediate (hop_id = 14), without censorship resistance + { + let expected_conf = 0b0000_1110; + let conf = LpPeerConfig::new(&mut rng, 14, false, false, false).unwrap(); + let conf_bytes = conf.serialize(); + let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes) + .unwrap() + .pack_config()[0]; + let conf_alt_first_byte = LpPeerConfig::new_client_to_intermediate(&mut rng, 14, false) + .unwrap() + .serialize()[0]; + + assert_eq!(expected_conf, conf_bytes[0]); + assert_eq!(expected_conf, deserialized_conf_first_byte); + assert_eq!(conf_bytes[0], conf_alt_first_byte); + assert!(conf.is_client_intermediate_node()); + } + } + + #[test] + fn test_failures() { + let mut rng = rand09::rng(); + // Hop with id 15 must be an exit node + assert!(LpPeerConfig::new(&mut rng, 15, false, false, false).is_err()); + + // intermediate hop cannot be the first hop + assert!(LpPeerConfig::new_client_to_intermediate(&mut rng, 0, false).is_err()); + // intermediate hop cannot be the last hop + assert!(LpPeerConfig::new_client_to_intermediate(&mut rng, 15, false).is_err()); + + // Hop with id 0 must be an entry node + assert!(LpPeerConfig::new_client_to_intermediate(&mut rng, 0, false).is_err()); + assert!(LpPeerConfig::new_client_to_exit(&mut rng, 0, false).is_err()); + assert!(LpPeerConfig::new(&mut rng, 0, true, false, false).is_err()); + + // cannot be node to node with hop_id > 0 + assert!(LpPeerConfig::new(&mut rng, 1, false, true, false).is_err()); + + // cannot be node to node and exit at the same time + assert!(LpPeerConfig::new(&mut rng, 0, true, true, false).is_err()); + + // cannot have hop_id greater than 15 + // this is a valid config + assert!(LpPeerConfig::new(&mut rng, 0, false, false, false).is_ok()); + // this is a valid config + assert!(LpPeerConfig::new(&mut rng, 14, false, false, false).is_ok()); + // this is a valid config + assert!(LpPeerConfig::new(&mut rng, 15, true, false, false).is_ok()); + // these are not valid configs + assert!(LpPeerConfig::new(&mut rng, 16, false, false, false).is_err()); + assert!(LpPeerConfig::new(&mut rng, 16, true, false, false).is_err()); + assert!(LpPeerConfig::new(&mut rng, 240, false, false, false).is_err()); + } +} diff --git a/common/nym-lp/src/psk.rs b/common/nym-lp/src/psk.rs deleted file mode 100644 index 45c352f490..0000000000 --- a/common/nym-lp/src/psk.rs +++ /dev/null @@ -1,792 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -//! PSK (Pre-Shared Key) derivation for LP sessions using Blake3 KDF. -//! -//! This module implements identity-bound PSK derivation where both client and gateway -//! derive the same PSK from their LP keypairs. -//! -//! PSQ is embedded in Noise (not separate protocol) because: -//! 1. Single round-trip: PSQ ciphertext piggybacks on Noise handshake messages -//! 2. PSK binding: Noise XKpsk3 pattern authenticates both ECDH and PSQ-derived PSK -//! 3. Simpler state machine: No separate PSQ negotiation phase needed -//! 4. Atomic security: Session establishment either succeeds fully or fails completely -//! -//! Two approaches are supported: -//! - **Legacy ECDH-only** (`derive_psk`) - Simple but no post-quantum security -//! - **PSQ-enhanced** (`derive_psk_with_psq_*`) - Combines ECDH with post-quantum KEM -//! -//! ## Error Handling Strategy -//! -//! **PSQ failures always abort the handshake cleanly with no retry or fallback.** -//! -//! ### Rationale -//! -//! PSQ errors indicate: -//! - **Authentication failures** (CredError) - Potential attack or misconfiguration -//! - **Timing failures** (TimestampElapsed) - Replay attacks or clock skew -//! - **Crypto failures** (CryptoError) - Library bugs or hardware faults -//! - **Serialization failures** (Serialization) - Protocol violations or corruption -//! -//! None of these are transient errors that benefit from retry. Falling back to -//! ECDH-only PSK would silently degrade post-quantum security. -//! -//! ### Error Recovery Behavior -//! -//! On any PSQ error: -//! 1. Function returns `Err(LpError)` immediately -//! 2. Session state remains unchanged (dummy PSK, clean Noise state) -//! 3. Handshake aborts - caller must start fresh connection -//! 4. Error is logged with diagnostic context -//! -//! ### State Guarantees on Error -//! -//! - **`psq_state`**: Remains in `NotStarted` (initiator) or `ResponderWaiting` (responder) -//! - **Noise `HandshakeState`**: PSK slot 3 = dummy `[0u8; 32]` (not modified on error) -//! - **No partial data**: All allocations are stack-local to failed function -//! - **No cleanup needed**: No state was mutated - -use crate::LpError; -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, x25519}; -use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; -use std::time::Duration; -use tls_codec::{Deserialize as TlsDeserializeTrait, Serialize as TlsSerializeTrait}; - -/// Context string for Blake3 KDF domain separation (PSQ-enhanced). -const PSK_PSQ_CONTEXT: &str = "nym-lp-psk-psq-v1"; - -/// Session context for PSQ protocol. -const PSQ_SESSION_CONTEXT: &[u8] = b"nym-lp-psq-session"; - -/// Context string for subsession PSK derivation. -const SUBSESSION_PSK_CONTEXT: &str = "lp-subsession-psk-v1"; - -/// Result from PSQ initiator message creation. -/// -/// Contains all outputs needed for session establishment: -/// - `psk`: Final derived PSK for Noise handshake (ECDH || K_pq || salt → Blake3) -/// - `payload`: Serialized PSQ message to send to responder -/// - `pq_shared_secret`: Raw K_pq from KEM encapsulation (for subsession derivation) -#[derive(Debug)] -pub struct PsqInitiatorResult { - /// Final PSK for Noise XKpsk3 handshake - pub psk: [u8; 32], - /// Serialized PSQ payload to embed in handshake message - pub payload: Vec, - /// Raw PQ shared secret (K_pq) before KDF combination. - /// Used for deriving subsession PSKs to preserve PQ protection. - pub pq_shared_secret: [u8; 32], -} - -/// Result from PSQ responder message processing. -/// -/// Contains all outputs needed for session establishment: -/// - `psk`: Final derived PSK for Noise handshake (matches initiator's) -/// - `psk_handle`: Encrypted PSK handle (ctxt_B) to send back to initiator -/// - `pq_shared_secret`: Raw K_pq from KEM decapsulation (for subsession derivation) -#[derive(Debug)] -pub struct PsqResponderResult { - /// Final PSK for Noise XKpsk3 handshake - pub psk: [u8; 32], - /// Encrypted PSK handle (ctxt_B) from PSQ responder message - pub psk_handle: Vec, - /// Raw PQ shared secret (K_pq) before KDF combination. - /// Used for deriving subsession PSKs to preserve PQ protection. - pub pq_shared_secret: [u8; 32], -} - -/// Derives a PSK using PSQ (Post-Quantum Secure PSK) protocol - Initiator side. -/// -/// This function combines classical ECDH with post-quantum KEM to provide forward secrecy -/// and HNDL (Harvest-Now, Decrypt-Later) resistance. -/// -/// # Formula -/// ```text -/// ecdh_secret = ECDH(local_x25519_private, remote_x25519_public) -/// (psq_psk, ct) = PSQ_Encapsulate(remote_kem_public, session_context) -/// psk = Blake3_derive_key( -/// context="nym-lp-psk-psq-v1", -/// input=ecdh_secret || psq_psk || salt -/// ) -/// ``` -/// -/// # Arguments -/// * `local_x25519_private` - Initiator's X25519 private key (for Noise) -/// * `remote_x25519_public` - Responder's X25519 public key (for Noise) -/// * `remote_kem_public` - Responder's KEM public key (obtained via KKT) -/// * `salt` - 32-byte salt for session binding -/// -/// # Returns -/// * `Ok((psk, ciphertext))` - PSK and ciphertext to send to responder -/// * `Err(LpError)` - If PSQ encapsulation fails -/// -/// # Example -/// ```ignore -/// // Client side (after KKT exchange) -/// let (psk, ciphertext) = derive_psk_with_psq_initiator( -/// client_x25519_private, -/// gateway_x25519_public, -/// &gateway_kem_key, // from KKT -/// &salt -/// )?; -/// // Send ciphertext to gateway -/// ``` -pub fn derive_psk_with_psq_initiator( - local_x25519_private: &x25519::PrivateKey, - remote_x25519_public: &x25519::PublicKey, - remote_kem_public: &EncapsulationKey, - salt: &[u8; 32], -) -> Result<([u8; 32], Vec), LpError> { - // Step 1: Classical ECDH for baseline security - let ecdh_secret = local_x25519_private.diffie_hellman(remote_x25519_public); - - // Step 2: PSQ encapsulation for post-quantum security - // KEM algorithm migration path: - // - X25519: Current default for testing/compatibility (no HNDL resistance) - // - MlKem768: Future production default (NIST PQ Level 3, HNDL resistant) - // - XWing: Maximum security option (hybrid X25519 + ML-KEM) - // Migration: Update LpConfig.kem_algorithm, no protocol changes needed. - // KKT protocol adapts automatically to different KEM key sizes. - let kem_pk = match remote_kem_public { - EncapsulationKey::X25519(pk) => pk, - _ => { - return Err(LpError::KKTError( - "Only X25519 KEM is currently supported for PSQ".to_string(), - )); - } - }; - - let mut rng = rand09::rng(); - let (psq_psk, ciphertext) = - PsqX25519::encapsulate_psq(kem_pk, PSQ_SESSION_CONTEXT, &mut rng) - .map_err(|e| LpError::Internal(format!("PSQ encapsulation failed: {:?}", e)))?; - - // Step 3: Combine ECDH + PSQ via Blake3 KDF - let mut combined = Vec::with_capacity(64 + psq_psk.len()); - combined.extend_from_slice(&ecdh_secret); - combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & - combined.extend_from_slice(salt); - - let final_psk = nym_crypto::kdf::derive_key_blake3(PSK_PSQ_CONTEXT, &combined, &[]); - - // Serialize ciphertext using TLS encoding for transport - let ct_bytes = ciphertext - .tls_serialize_detached() - .map_err(|e| LpError::Internal(format!("Ciphertext serialization failed: {:?}", e)))?; - - Ok((final_psk, ct_bytes)) -} - -/// Derives a PSK using PSQ (Post-Quantum Secure PSK) protocol - Responder side. -/// -/// This function decapsulates the ciphertext from the initiator and combines it with -/// ECDH to derive the same PSK. -/// -/// # Formula -/// ```text -/// ecdh_secret = ECDH(local_x25519_private, remote_x25519_public) -/// psq_psk = PSQ_Decapsulate(local_kem_keypair, ciphertext, session_context) -/// psk = Blake3_derive_key( -/// context="nym-lp-psk-psq-v1", -/// input=ecdh_secret || psq_psk || salt -/// ) -/// ``` -/// -/// # Arguments -/// * `local_x25519_private` - Responder's X25519 private key (for Noise) -/// * `remote_x25519_public` - Initiator's X25519 public key (for Noise) -/// * `local_kem_keypair` - Responder's KEM keypair (decapsulation key, public key) -/// * `ciphertext` - PSQ ciphertext from initiator -/// * `salt` - 32-byte salt for session binding -/// -/// # Returns -/// * `Ok(psk)` - Derived PSK -/// * `Err(LpError)` - If PSQ decapsulation fails -/// -/// # Example -/// ```ignore -/// // Gateway side (after receiving ciphertext) -/// let psk = derive_psk_with_psq_responder( -/// gateway_x25519_private, -/// client_x25519_public, -/// (&gateway_kem_sk, &gateway_kem_pk), -/// &ciphertext, // from client -/// &salt -/// )?; -/// ``` -pub fn derive_psk_with_psq_responder( - local_x25519_private: &x25519::PrivateKey, - remote_x25519_public: &x25519::PublicKey, - local_kem_keypair: (&DecapsulationKey, &EncapsulationKey), - ciphertext: &[u8], - salt: &[u8; 32], -) -> Result<[u8; 32], LpError> { - // Step 1: Classical ECDH for baseline security - let ecdh_secret = local_x25519_private.diffie_hellman(remote_x25519_public); - - // Step 2: Extract X25519 keypair from DecapsulationKey/EncapsulationKey - let (kem_sk, kem_pk) = match (local_kem_keypair.0, local_kem_keypair.1) { - (DecapsulationKey::X25519(sk), EncapsulationKey::X25519(pk)) => (sk, pk), - _ => { - return Err(LpError::KKTError( - "Only X25519 KEM is currently supported for PSQ".to_string(), - )); - } - }; - - // Step 3: Deserialize ciphertext using TLS decoding - let ct = PsqCiphertext::::tls_deserialize(&mut &ciphertext[..]) - .map_err(|e| LpError::Internal(format!("Ciphertext deserialization failed: {:?}", e)))?; - - // Step 4: PSQ decapsulation for post-quantum security - let psq_psk = PsqX25519::decapsulate_psq(kem_sk, kem_pk, &ct, PSQ_SESSION_CONTEXT) - .map_err(|e| LpError::Internal(format!("PSQ decapsulation failed: {:?}", e)))?; - - // 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); - combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & - combined.extend_from_slice(salt); - - let final_psk = nym_crypto::kdf::derive_key_blake3(PSK_PSQ_CONTEXT, &combined, &[]); - - Ok(final_psk) -} - -/// PSQ protocol wrapper for initiator (client) side. -/// -/// Creates a PSQ initiator message with Ed25519 authentication, following the protocol: -/// 1. Encapsulate PSK using responder's KEM key -/// 2. Derive PSK and AEAD keys from K_pq -/// 3. Sign the encapsulation with Ed25519 -/// 4. AEAD encrypt (timestamp || signature || public_key) -/// -/// Returns (PSK, serialized_payload) where payload includes enc_pq and encrypted auth data. -/// -/// # Arguments -/// * `local_x25519_private` - Client's X25519 private key (for hybrid ECDH) -/// * `remote_x25519_public` - Gateway's X25519 public key (for hybrid ECDH) -/// * `remote_kem_public` - Gateway's PQ KEM public key (from KKT) -/// * `client_ed25519_sk` - Client's Ed25519 signing key -/// * `client_ed25519_pk` - Client's Ed25519 public key (credential) -/// * `salt` - Session salt -/// * `session_context` - Context bytes for PSQ (e.g., b"nym-lp-psq-session") -/// -/// # Returns -/// `PsqInitiatorResult` containing PSK, payload, and raw PQ shared secret -pub fn psq_initiator_create_message( - local_x25519_private: &x25519::PrivateKey, - remote_x25519_public: &x25519::PublicKey, - remote_kem_public: &EncapsulationKey, - client_ed25519_sk: &ed25519::PrivateKey, - client_ed25519_pk: &ed25519::PublicKey, - salt: &[u8; 32], - session_context: &[u8], -) -> Result { - // Step 1: Classical ECDH for baseline security - let ecdh_secret = local_x25519_private.diffie_hellman(remote_x25519_public); - - // Step 2: PSQ v1 with Ed25519 authentication - // Extract X25519 KEM key from EncapsulationKey - let kem_pk = match remote_kem_public { - EncapsulationKey::X25519(pk) => pk, - _ => { - return Err(LpError::KKTError( - "Only X25519 KEM is currently supported for PSQ".to_string(), - )); - } - }; - - // Convert nym Ed25519 keys to libcrux format - type Ed25519VerificationKey = ::VerificationKey; - let ed25519_sk_bytes = client_ed25519_sk.to_bytes(); - let ed25519_pk_bytes = client_ed25519_pk.to_bytes(); - let ed25519_verification_key = Ed25519VerificationKey::from_bytes(ed25519_pk_bytes); - - // Use PSQ v1 API with Ed25519 authentication - let mut rng = rand09::rng(); - let (state, initiator_msg) = Initiator::send_initial_message::( - session_context, - Duration::from_secs(3600), // 1 hour expiry - kem_pk, - &ed25519_sk_bytes, - &ed25519_verification_key, - &mut rng, - ) - .map_err(|e| { - tracing::error!( - "PSQ initiator failed - KEM encapsulation or signing error: {:?}", - e - ); - LpError::Internal(format!("PSQ v1 send_initial_message failed: {:?}", e)) - })?; - - // Extract PSQ shared secret (unregistered PSK) - this is K_pq - let psq_psk = state.unregistered_psk(); - - // pq_shared_secret is the raw K_pq from KEM encapsulation. - // Store it for subsession derivation before it's combined with ECDH. - let pq_shared_secret: [u8; 32] = *psq_psk; - - // Step 3: Combine ECDH + PSQ via Blake3 KDF - let mut combined = Vec::with_capacity(64 + psq_psk.len()); - combined.extend_from_slice(&ecdh_secret); - combined.extend_from_slice(psq_psk); // psq_psk is already a &[u8; 32] - combined.extend_from_slice(salt); - - let final_psk = nym_crypto::kdf::derive_key_blake3(PSK_PSQ_CONTEXT, &combined, &[]); - - // Serialize InitiatorMsg with TLS encoding for transport - let msg_bytes = initiator_msg - .tls_serialize_detached() - .map_err(|e| LpError::Internal(format!("InitiatorMsg serialization failed: {:?}", e)))?; - - Ok(PsqInitiatorResult { - psk: final_psk, - payload: msg_bytes, - pq_shared_secret, - }) -} - -/// PSQ protocol wrapper for responder (gateway) side. -/// -/// Processes a PSQ initiator message, verifies authentication, and derives PSK. -/// Follows the protocol: -/// 1. Decapsulate to get K_pq -/// 2. Derive AEAD keys and verify encrypted auth data -/// 3. Verify Ed25519 signature -/// 4. Check timestamp validity -/// 5. Derive PSK -/// -/// # Arguments -/// * `local_x25519_private` - Gateway's X25519 private key (for hybrid ECDH) -/// * `remote_x25519_public` - Client's X25519 public key (for hybrid ECDH) -/// * `local_kem_keypair` - Gateway's PQ KEM keypair -/// * `initiator_ed25519_pk` - Client's Ed25519 public key (for signature verification) -/// * `psq_payload` - Serialized PSQ payload from initiator -/// * `salt` - Session salt (must match initiator's) -/// * `session_context` - Context bytes for PSQ -/// -/// # Returns -/// `PsqResponderResult` containing PSK, PSK handle, and raw PQ shared secret -pub fn psq_responder_process_message( - local_x25519_private: &x25519::PrivateKey, - remote_x25519_public: &x25519::PublicKey, - local_kem_keypair: (&DecapsulationKey, &EncapsulationKey), - initiator_ed25519_pk: &ed25519::PublicKey, - psq_payload: &[u8], - salt: &[u8; 32], - session_context: &[u8], -) -> Result { - // Step 1: Classical ECDH for baseline security - let ecdh_secret = local_x25519_private.diffie_hellman(remote_x25519_public); - - // Step 2: Extract X25519 keypair from DecapsulationKey/EncapsulationKey - let (kem_sk, kem_pk) = match (local_kem_keypair.0, local_kem_keypair.1) { - (DecapsulationKey::X25519(sk), EncapsulationKey::X25519(pk)) => (sk, pk), - _ => { - return Err(LpError::KKTError( - "Only X25519 KEM is currently supported for PSQ".to_string(), - )); - } - }; - - // Step 3: Deserialize InitiatorMsg using TLS decoding - let initiator_msg = InitiatorMsg::::tls_deserialize(&mut &psq_payload[..]) - .map_err(|e| LpError::Internal(format!("InitiatorMsg deserialization failed: {:?}", e)))?; - - // Step 4: Convert nym Ed25519 public key to libcrux VerificationKey format - type Ed25519VerificationKey = ::VerificationKey; - let initiator_ed25519_pk_bytes = initiator_ed25519_pk.to_bytes(); - let initiator_verification_key = Ed25519VerificationKey::from_bytes(initiator_ed25519_pk_bytes); - - // Step 5: PSQ v1 responder processing with Ed25519 verification - let (registered_psk, responder_msg) = Responder::send::( - b"nym-lp-handle", // PSK storage handle - Duration::from_secs(3600), // 1 hour expiry (must match initiator) - session_context, // Must match initiator's session_context - kem_pk, // Responder's public key - kem_sk, // Responder's secret key - &initiator_verification_key, // Initiator's Ed25519 public key for verification - &initiator_msg, // InitiatorMsg to verify and process - ) - .map_err(|e| { - use libcrux_psq::v1::Error as PsqError; - match e { - PsqError::CredError => { - tracing::warn!( - "PSQ responder auth failure - invalid Ed25519 signature (potential attack)" - ); - } - PsqError::TimestampElapsed | PsqError::RegistrationError => { - tracing::warn!( - "PSQ responder timing failure - TTL expired (potential replay attack)" - ); - } - _ => { - tracing::error!("PSQ responder failed - {:?}", e); - } - } - LpError::Internal(format!("PSQ v1 responder send failed: {:?}", e)) - })?; - - // Extract the PSQ PSK from the registered PSK - this is K_pq - let psq_psk = registered_psk.psk; - - // pq_shared_secret is the raw K_pq from KEM decapsulation. - // Store it for subsession derivation before it's combined with ECDH. - let pq_shared_secret: [u8; 32] = psq_psk; - - // 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); - combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & - combined.extend_from_slice(salt); - - let final_psk = nym_crypto::kdf::derive_key_blake3(PSK_PSQ_CONTEXT, &combined, &[]); - - // Step 7: Serialize ResponderMsg (contains ctxt_B - encrypted PSK handle) - use tls_codec::Serialize; - let responder_msg_bytes = responder_msg - .tls_serialize_detached() - .map_err(|e| LpError::Internal(format!("ResponderMsg serialization failed: {:?}", e)))?; - - Ok(PsqResponderResult { - psk: final_psk, - psk_handle: responder_msg_bytes, - pq_shared_secret, - }) -} - -/// Derive subsession PSK from parent's PQ shared secret. -/// -/// Uses Blake3 KDF with domain separation to derive unique PSK for each subsession. -/// This preserves PQ protection: subsession keys inherit quantum resistance from -/// parent's KEM shared secret (K_pq). -/// -/// # Security Model -/// -/// Subsessions use Noise KKpsk0 pattern where: -/// - Both parties already know each other's static X25519 keys (from parent session) -/// - PSK provides PQ protection by deriving from parent's K_pq -/// - Each subsession gets unique PSK via index parameter (prevents key reuse) -/// -/// # Arguments -/// * `pq_shared_secret` - Parent session's K_pq (32 bytes from KEM) -/// * `subsession_index` - Monotonic index for this subsession (prevents reuse) -/// -/// # Returns -/// 32-byte PSK for Noise KKpsk0 handshake -pub fn derive_subsession_psk(pq_shared_secret: &[u8; 32], subsession_index: u64) -> [u8; 32] { - nym_crypto::kdf::derive_key_blake3( - SUBSESSION_PSK_CONTEXT, - pq_shared_secret, - &subsession_index.to_le_bytes(), - ) -} - -#[cfg(test)] -mod tests { - use super::*; - 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 = generate_x25519_keypair(); - let keypair_2 = generate_x25519_keypair(); - let salt = [2u8; 32]; - - let mut rng = &mut rand09::rng(); - let (_kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); - let enc_key = EncapsulationKey::X25519(kem_pk); - let dec_key = DecapsulationKey::X25519(_kem_sk); - - // Client derives PSK - let (client_psk, ciphertext) = derive_psk_with_psq_initiator( - keypair_1.private_key(), - keypair_2.public_key(), - &enc_key, - &salt, - ) - .unwrap(); - - // Gateway derives PSK from their perspective - let gateway_psk = derive_psk_with_psq_responder( - keypair_2.private_key(), - keypair_1.public_key(), - (&dec_key, &enc_key), - &ciphertext, - &salt, - ) - .unwrap(); - - assert_eq!( - client_psk, gateway_psk, - "Both sides should derive identical PSK" - ); - } - - #[test] - fn test_different_salts_produce_different_psks() { - let keypair_1 = generate_x25519_keypair(); - let keypair_2 = generate_x25519_keypair(); - - let salt1 = [1u8; 32]; - let salt2 = [2u8; 32]; - let mut rng = &mut rand09::rng(); - let (_kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); - let enc_key = EncapsulationKey::X25519(kem_pk); - - let psk1 = derive_psk_with_psq_initiator( - keypair_1.private_key(), - keypair_2.public_key(), - &enc_key, - &salt1, - ) - .unwrap(); - let psk2 = derive_psk_with_psq_initiator( - keypair_1.private_key(), - keypair_2.public_key(), - &enc_key, - &salt2, - ) - .unwrap(); - - assert_ne!(psk1, psk2, "Different salts should produce different PSKs"); - } - - #[test] - fn test_different_keys_produce_different_psks() { - 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(); - let (_kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); - let enc_key = EncapsulationKey::X25519(kem_pk); - - let psk1 = derive_psk_with_psq_initiator( - keypair_1.private_key(), - keypair_2.public_key(), - &enc_key, - &salt, - ) - .unwrap(); - let psk2 = derive_psk_with_psq_initiator( - keypair_1.private_key(), - keypair_3.public_key(), - &enc_key, - &salt, - ) - .unwrap(); - - assert_ne!( - psk1, psk2, - "Different remote keys should produce different PSKs" - ); - } - - // PSQ-enhanced PSK tests - use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey, KEM}; - use nym_kkt::key_utils::generate_keypair_libcrux; - - #[test] - fn test_psq_derivation_deterministic() { - let mut rng = rand09::rng(); - - // Generate X25519 keypairs for Noise - 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(); - let enc_key = EncapsulationKey::X25519(kem_pk); - let dec_key = DecapsulationKey::X25519(kem_sk); - - let salt = [1u8; 32]; - - // Derive PSK twice with same inputs (initiator side) - let (_psk1, ct1) = derive_psk_with_psq_initiator( - client_keypair.private_key(), - gateway_keypair.public_key(), - &enc_key, - &salt, - ) - .unwrap(); - - let (_psk2, _ct2) = derive_psk_with_psq_initiator( - client_keypair.private_key(), - gateway_keypair.public_key(), - &enc_key, - &salt, - ) - .unwrap(); - - // PSKs will be different due to randomness in PSQ, but ciphertexts too - // This test verifies the function is deterministic given the SAME ciphertext - let psk_responder1 = derive_psk_with_psq_responder( - gateway_keypair.private_key(), - client_keypair.public_key(), - (&dec_key, &enc_key), - &ct1, - &salt, - ) - .unwrap(); - - let psk_responder2 = derive_psk_with_psq_responder( - gateway_keypair.private_key(), - client_keypair.public_key(), - (&dec_key, &enc_key), - &ct1, // Same ciphertext - &salt, - ) - .unwrap(); - - assert_eq!( - psk_responder1, psk_responder2, - "Same ciphertext should produce same PSK" - ); - } - - #[test] - fn test_psq_derivation_symmetric() { - let mut rng = rand09::rng(); - - // Generate X25519 keypairs for Noise - 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(); - let enc_key = EncapsulationKey::X25519(kem_pk); - let dec_key = DecapsulationKey::X25519(kem_sk); - - let salt = [2u8; 32]; - - // Client derives PSK (initiator) - let (client_psk, ciphertext) = derive_psk_with_psq_initiator( - client_keypair.private_key(), - gateway_keypair.public_key(), - &enc_key, - &salt, - ) - .unwrap(); - - // Gateway derives PSK from ciphertext (responder) - let gateway_psk = derive_psk_with_psq_responder( - gateway_keypair.private_key(), - client_keypair.public_key(), - (&dec_key, &enc_key), - &ciphertext, - &salt, - ) - .unwrap(); - - assert_eq!( - client_psk, gateway_psk, - "Both sides should derive identical PSK via PSQ" - ); - } - - #[test] - fn test_different_kem_keys_different_psk() { - let mut rng = rand09::rng(); - - 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(); - let (_, kem_pk2) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); - - let enc_key1 = EncapsulationKey::X25519(kem_pk1); - let enc_key2 = EncapsulationKey::X25519(kem_pk2); - - let salt = [3u8; 32]; - - let (psk1, _) = derive_psk_with_psq_initiator( - client_keypair.private_key(), - gateway_keypair.public_key(), - &enc_key1, - &salt, - ) - .unwrap(); - - let (psk2, _) = derive_psk_with_psq_initiator( - client_keypair.private_key(), - gateway_keypair.public_key(), - &enc_key2, - &salt, - ) - .unwrap(); - - assert_ne!( - psk1, psk2, - "Different KEM keys should produce different PSKs" - ); - } - - #[test] - fn test_psq_psk_output_length() { - let mut rng = rand09::rng(); - - 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); - - let salt = [4u8; 32]; - - let (psk, _) = derive_psk_with_psq_initiator( - client_keypair.private_key(), - gateway_keypair.public_key(), - &enc_key, - &salt, - ) - .unwrap(); - - assert_eq!(psk.len(), 32, "PSQ PSK should be exactly 32 bytes"); - } - - #[test] - fn test_psq_different_salts_different_psks() { - let mut rng = rand09::rng(); - - 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); - - let salt1 = [1u8; 32]; - let salt2 = [2u8; 32]; - - let (psk1, _) = derive_psk_with_psq_initiator( - client_keypair.private_key(), - gateway_keypair.public_key(), - &enc_key, - &salt1, - ) - .unwrap(); - - let (psk2, _) = derive_psk_with_psq_initiator( - client_keypair.private_key(), - gateway_keypair.public_key(), - &enc_key, - &salt2, - ) - .unwrap(); - - assert_ne!(psk1, psk2, "Different salts should produce different PSKs"); - } -} diff --git a/common/nym-lp/src/psq/handshake_message.rs b/common/nym-lp/src/psq/handshake_message.rs new file mode 100644 index 0000000000..2877049b46 --- /dev/null +++ b/common/nym-lp/src/psq/handshake_message.rs @@ -0,0 +1,145 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::psq::{PSQ_MSG2_SIZE, psq_msg1_size}; +use crate::transport::{LpTransportError, traits::HandshakeMessage}; +use nym_kkt::context::KKTMode; +use nym_kkt_ciphersuite::KEM; +use std::ops::Deref; + +pub struct KKTRequest(nym_kkt::message::KKTRequest); + +impl From for KKTRequest { + fn from(request: nym_kkt::message::KKTRequest) -> Self { + KKTRequest(request) + } +} + +impl From for nym_kkt::message::KKTRequest { + fn from(request: KKTRequest) -> Self { + request.0 + } +} + +impl HandshakeMessage for KKTRequest { + fn into_bytes(self) -> Vec { + self.0.into_bytes() + } + + fn try_from_bytes(bytes: Vec) -> Result { + Ok(KKTRequest( + nym_kkt::message::KKTRequest::try_from_bytes(&bytes) + .map_err(|err| LpTransportError::MalformedPacket(err.to_string()))?, + )) + } + + fn expected_size(mode: KKTMode, expected_kem: KEM, payload_size: usize) -> usize { + nym_kkt::message::KKTRequest::size_excluding_payload(mode, expected_kem) + payload_size + } + + fn response_size(&self, expected_kem: KEM) -> Option { + Some(nym_kkt::message::KKTResponse::size_excluding_payload( + expected_kem, + )) + } +} + +pub struct KKTResponse(nym_kkt::message::KKTResponse); + +impl From for KKTResponse { + fn from(request: nym_kkt::message::KKTResponse) -> Self { + KKTResponse(request) + } +} + +impl From for nym_kkt::message::KKTResponse { + fn from(request: KKTResponse) -> Self { + request.0 + } +} + +impl HandshakeMessage for KKTResponse { + fn into_bytes(self) -> Vec { + self.0.into_bytes() + } + + fn try_from_bytes(bytes: Vec) -> Result { + Ok(KKTResponse(nym_kkt::message::KKTResponse::from_bytes( + bytes, + ))) + } + + fn expected_size(_: KKTMode, expected_kem: KEM, payload_size: usize) -> usize { + nym_kkt::message::KKTResponse::size_excluding_payload(expected_kem) + payload_size + } + + fn response_size(&self, expected_kem: KEM) -> Option { + Some(psq_msg1_size(expected_kem)) + } +} + +pub struct PSQMsg1(Vec); + +impl Deref for PSQMsg1 { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PSQMsg1 { + pub fn new(bytes: Vec) -> Self { + PSQMsg1(bytes) + } +} + +impl HandshakeMessage for PSQMsg1 { + fn into_bytes(self) -> Vec { + self.0 + } + + fn try_from_bytes(bytes: Vec) -> Result { + Ok(PSQMsg1(bytes)) + } + + fn expected_size(_: KKTMode, expected_kem: KEM, payload_size: usize) -> usize { + psq_msg1_size(expected_kem) + payload_size + } + + fn response_size(&self, _: KEM) -> Option { + Some(PSQ_MSG2_SIZE) + } +} + +pub struct PSQMsg2(Vec); + +impl Deref for PSQMsg2 { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PSQMsg2 { + pub fn new(bytes: Vec) -> Self { + PSQMsg2(bytes) + } +} + +impl HandshakeMessage for PSQMsg2 { + fn into_bytes(self) -> Vec { + self.0 + } + + fn try_from_bytes(bytes: Vec) -> Result { + Ok(PSQMsg2(bytes)) + } + + fn expected_size(_: KKTMode, _: KEM, payload_size: usize) -> usize { + PSQ_MSG2_SIZE + payload_size + } + + fn response_size(&self, _: KEM) -> Option { + None + } +} diff --git a/common/nym-lp/src/psq/helpers.rs b/common/nym-lp/src/psq/helpers.rs index 7c488187a6..ab4edfa7cb 100644 --- a/common/nym-lp/src/psq/helpers.rs +++ b/common/nym-lp/src/psq/helpers.rs @@ -1,52 +1,12 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; -use crate::{LpError, LpPacket}; -use bytes::BytesMut; -use nym_lp_transport::traits::LpTransport; +use libcrux_psq::handshake::ciphersuites::CiphersuiteName; +use nym_kkt_ciphersuite::KEM; -#[cfg(test)] -use mock_instant::thread_local::{SystemTime, UNIX_EPOCH}; -#[cfg(not(test))] -use std::time::{SystemTime, UNIX_EPOCH}; - -pub(crate) fn current_timestamp() -> Result { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(|_| LpError::Internal("System time before UNIX epoch".into())) - .map(|d| d.as_secs()) -} - -// only used in internal code (and tests) -#[allow(async_fn_in_trait)] -pub trait LpTransportHandshakeExt: LpTransport { - // the outer key is temporary until the algorithm is changed with psqv2 - async fn receive_packet( - &mut self, - outer_key: Option<&OuterAeadKey>, - ) -> Result - where - Self: Unpin, - { - let raw = self.receive_raw_packet().await?; - parse_lp_packet(&raw, outer_key) - } - - async fn send_packet( - &mut self, - packet: LpPacket, - outer_key: Option<&OuterAeadKey>, - ) -> Result<(), LpError> - where - Self: Unpin, - { - let mut packet_buf = BytesMut::new(); - - serialize_lp_packet(&packet, &mut packet_buf, outer_key)?; - self.send_serialised_packet(&packet_buf).await?; - Ok(()) +pub(crate) fn kem_to_ciphersuite(kem: KEM) -> CiphersuiteName { + match kem { + KEM::MlKem768 => CiphersuiteName::X25519_MLKEM768_X25519_AESGCM128_HKDFSHA256, + KEM::McEliece => CiphersuiteName::X25519_CLASSICMCELIECE_X25519_AESGCM128_HKDFSHA256, } } - -impl LpTransportHandshakeExt for T where T: LpTransport {} diff --git a/common/nym-lp/src/psq/initiator.rs b/common/nym-lp/src/psq/initiator.rs index 61804124a9..ef450158f0 100644 --- a/common/nym-lp/src/psq/initiator.rs +++ b/common/nym-lp/src/psq/initiator.rs @@ -1,391 +1,340 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::codec::OuterAeadKey; -use crate::message::{HandshakeData, KKTRequestData, MessageType}; -use crate::noise_protocol::NoiseProtocol; -use crate::peer::LpRemotePeer; -use crate::psk::psq_initiator_create_message; -use crate::psq::helpers::{LpTransportHandshakeExt, current_timestamp}; -use crate::psq::{IntermediateHandshakeFailure, PSQHandshakeState}; -use crate::session::PqSharedSecret; -use crate::{ClientHelloData, LpError, LpMessage, LpSession}; -use nym_kkt::KKT_RESPONSE_AAD; -use nym_kkt::ciphersuite::EncapsulationKey; -use nym_kkt::context::KKTContext; -use nym_kkt::encryption::{KKTSessionSecret, decrypt_kkt_frame, encrypt_initial_kkt_frame}; -use nym_kkt::session::{anonymous_initiator_process, initiator_ingest_response}; -use nym_lp_transport::traits::LpTransport; -use rand09::rng; +use crate::peer::{LpLocalPeer, LpRemotePeer}; +use crate::peer_config::LpPeerConfig; +use crate::psq::handshake_message::{PSQMsg1, PSQMsg2}; +use crate::psq::helpers::kem_to_ciphersuite; +use crate::psq::{ + AAD_INITIATOR_INNER_V1, AAD_INITIATOR_OUTER_V1, InitiatorData, PSQ_MSG2_SIZE, + PSQHandshakeState, SESSION_CONTEXT_V1, handshake_message, psq_msg1_size, +}; +use crate::session::PersistentSessionBinding; +use crate::transport::traits::LpHandshakeChannel; +use crate::{LpError, LpSession}; +use libcrux_psq::handshake::RegistrationInitiator; +use libcrux_psq::handshake::builders::{ + CiphersuiteBuilder, InitiatorCiphersuite, PrincipalBuilder, +}; +use libcrux_psq::handshake::types::Authenticator; +use libcrux_psq::{Channel, IntoSession}; +use nym_kkt::initiator::KKTInitiator; +use nym_kkt::keys::EncapsulationKey; +use nym_kkt::message::{KKTRequest, KKTResponse}; +use rand09::SeedableRng; use tracing::debug; -impl<'a, S> PSQHandshakeState<'a, S> +pub struct PSQHandshakeStateInitiator<'a, S> { + pub(super) inner_state: PSQHandshakeState<'a, S>, + pub(super) initiator_data: InitiatorData, +} + +pub(crate) fn build_psq_principal( + rng: R, + version: u8, + ciphersuite: InitiatorCiphersuite, +) -> Result, LpError> where - S: LpTransport + Unpin, + R: rand09::CryptoRng, { - /// Generate and send client hello to the responder - pub(crate) async fn send_client_hello(&mut self) -> Result { - let protocol = self.protocol_version()?; + let (ctx, inner_aad, outer_aad) = match version { + 1 => ( + SESSION_CONTEXT_V1, + AAD_INITIATOR_INNER_V1, + AAD_INITIATOR_OUTER_V1, + ), + other => return Err(LpError::UnsupportedVersion { version: other }), + }; - // 1. Generate and send ClientHelloData with fresh salt and both public keys - let timestamp = current_timestamp()?; + PrincipalBuilder::new(rng) + .outer_aad(outer_aad) + .inner_aad(inner_aad) + .context(ctx) + .build_registration_initiator(ciphersuite) + .map_err(|inner| LpError::PSQInitiatorBuilderFailure { inner }) +} - let client_hello_data = self.local_peer.build_client_hello_data(timestamp); - self.connection - .send_packet(client_hello_data.into_lp_packet(protocol), None) - .await?; - Ok(client_hello_data) - } - - /// Attempt to receive an ack to sent client hello. returns a boolean indicating - /// whether the request has been successful or whether there has been a collision in receiver - /// index requiring a retry - pub(crate) async fn receive_client_hello_ack(&mut self) -> Result { - match self.receive_non_error(None).await?.message { - LpMessage::Ack => Ok(true), - LpMessage::Collision => Ok(false), - other => { - // TODO: retry on collision - Err(LpError::unexpected_handshake_response( - other.typ(), - MessageType::Ack, - )) - } - } +pub(crate) fn build_psq_ciphersuite<'a>( + init: &'a LpLocalPeer, + responder: &'a LpRemotePeer, + kem_key: &'a EncapsulationKey, +) -> Result, LpError> { + let psq_ciphersuite = kem_to_ciphersuite(kem_key.kem()); + + let builder = CiphersuiteBuilder::new(psq_ciphersuite) + .longterm_x25519_keys(init.x25519()) + .peer_longterm_x25519_pk(responder.x25519()); + + match kem_key { + EncapsulationKey::McEliece(kem_key) => builder.peer_longterm_cmc_pk(kem_key), + EncapsulationKey::MlKem768(kem_key) => builder.peer_longterm_mlkem_pk(kem_key), } + .build_initiator_ciphersuite() + .map_err(|inner| LpError::PSQInitiatorBuilderFailure { inner }) +} +impl<'a, S> PSQHandshakeStateInitiator<'a, S> +where + S: LpHandshakeChannel + Unpin, +{ /// Attempt to send KKT request to begin the handshake - pub(crate) async fn send_kkt_request( - &mut self, - session_id: u32, - remote_peer: &LpRemotePeer, - ) -> Result<(KKTContext, KKTSessionSecret), LpError> { - let protocol = self.protocol_version()?; + async fn send_kkt_request(&mut self, request: KKTRequest) -> Result<(), LpError> { + let kem = self.inner_state.local_peer.ciphersuite.kem(); - let (kkt_context, kkt_frame) = anonymous_initiator_process(&mut rng(), self.ciphersuite)?; - let (session_secret, encrypted_frame) = - encrypt_initial_kkt_frame(&mut rng(), &remote_peer.x25519_public, &kkt_frame)?; - let lp_message = KKTRequestData::new(encrypted_frame).into(); - let lp_packet = self.next_packet(session_id, protocol, lp_message); - self.connection.send_packet(lp_packet, None).await?; - Ok((kkt_context, session_secret)) - } - - /// Attempt to receive a KKT response to the previously sent request and extract (and validate) - /// the received encapsulation key - pub(crate) async fn receive_kkt_response( - &mut self, - (kkt_context, session_secret): (KKTContext, KKTSessionSecret), - remote_peer: &LpRemotePeer, - ) -> Result, LpError> { - let kkt_response = match self.receive_non_error(None).await?.message { - LpMessage::KKTResponse(response) => response, - other => { - return Err(LpError::unexpected_handshake_response( - other.typ(), - MessageType::KKTResponse, - )); - } - }; - debug!("received KKT response"); - let expected_kem_key_digest = remote_peer.expected_kem_key_hash(self.ciphersuite)?; - - let (response_frame, remote_context) = - decrypt_kkt_frame(&session_secret, &kkt_response.0, KKT_RESPONSE_AAD)?; - let encapsulation_key = initiator_ingest_response( - &kkt_context, - &response_frame, - &remote_context, - &remote_peer.ed25519_public, - &expected_kem_key_digest, - )?; - Ok(encapsulation_key) - } - - /// Attempt to prepare and send initial PSQ msg1 - pub(crate) async fn send_psq_initiator_message( - &mut self, - remote_peer: &LpRemotePeer, - encapsulation_key: &EncapsulationKey<'_>, - salt: &[u8; 32], - session_id_bytes: &[u8; 4], - ) -> Result<(OuterAeadKey, NoiseProtocol, PqSharedSecret), LpError> { - let protocol = self.protocol_version()?; - let session_id = u32::from_le_bytes(*session_id_bytes); - - let psq_initiator = psq_initiator_create_message( - self.local_peer.x25519.private_key(), - &remote_peer.x25519_public, - encapsulation_key, - self.local_peer.ed25519.private_key(), - self.local_peer.ed25519.public_key(), - salt, - session_id_bytes, - )?; - let psk = psq_initiator.psk; - let psq_payload = psq_initiator.payload; - - // TEMP \/ - let outer_aead_key = OuterAeadKey::from_psk(&psk); - // TEMP /\ - - // prepare noise state and msg1 - let mut noise_protocol = NoiseProtocol::build_new_initiator( - self.local_peer.x25519().private_key().as_bytes(), - remote_peer.x25519_public.as_bytes(), - &psk, - )?; - - // prepare noise msg1 - let noise_msg1 = noise_protocol - .get_bytes_to_send() - .ok_or_else(|| LpError::kkt_psq_handshake("failed to generate noise msg1"))??; - let psq_len = psq_payload.len() as u16; - let mut combined = Vec::with_capacity(2 + psq_payload.len() + noise_msg1.len()); - combined.extend_from_slice(&psq_len.to_le_bytes()); - combined.extend_from_slice(&psq_payload); - combined.extend_from_slice(&noise_msg1); - - let lp_message = HandshakeData::new(combined).into(); - let lp_packet = self.next_packet(session_id, protocol, lp_message); - - self.connection.send_packet(lp_packet, None).await?; - Ok(( - outer_aead_key, - noise_protocol, - PqSharedSecret::new(psq_initiator.pq_shared_secret), - )) - } - - /// Attempt to receive and validate received PSQ msg2 - pub(crate) async fn receive_psq_responder_message( - &mut self, - outer_aead_key: &OuterAeadKey, - noise_protocol: &mut NoiseProtocol, - ) -> Result<(), LpError> { - let psq_msg2 = match self + self.inner_state .connection - .receive_packet(Some(outer_aead_key)) - .await? - .message - { - LpMessage::Handshake(response) => response.0, - other => { - return Err(LpError::unexpected_handshake_response( - other.typ(), - MessageType::Handshake, - )); - } - }; - - // Extract PSK handle: [u16 handle_len][handle_bytes][noise_msg] - if psq_msg2.len() < 2 { - return Err(LpError::kkt_psq_handshake("too short msg2 received")); - } - let handle_len = u16::from_le_bytes([psq_msg2[0], psq_msg2[1]]) as usize; - if psq_msg2.len() < 2 + handle_len { - return Err(LpError::kkt_psq_handshake("too short msg2 received")); - } - // Extract and "store" the PSK handle - let _psq_handle_bytes = &psq_msg2[2..2 + handle_len]; - let noise_payload = &psq_msg2[2 + handle_len..]; - - // *sigh* ignore the message - let _noise_msg2 = noise_protocol.read_message(noise_payload)?; + .send_handshake_message::(request.into(), kem) + .await?; Ok(()) } - /// Attempt to prepare and send final PSQ msg3 - pub(crate) async fn send_final_psq_message( - &mut self, - session_id: u32, - outer_aead_key: &OuterAeadKey, - noise_protocol: &mut NoiseProtocol, - ) -> Result<(), LpError> { - let protocol = self.protocol_version()?; + /// Attempt to receive a KKT response to the previously sent request + async fn receive_kkt_response(&mut self) -> Result { + // no response payload + let packet_len = + KKTResponse::size_excluding_payload(self.inner_state.local_peer.ciphersuite.kem()); - let noise_msg3 = noise_protocol - .get_bytes_to_send() - .ok_or_else(|| LpError::kkt_psq_handshake("failed to generate noise msg3"))??; - - let lp_message = HandshakeData::new(noise_msg3).into(); - let lp_packet = self.next_packet(session_id, protocol, lp_message); - self.connection - .send_packet(lp_packet, Some(outer_aead_key)) + let resp = self + .inner_state + .connection + .receive_handshake_message::(packet_len) .await?; - if !noise_protocol.is_handshake_finished() { - return Err(LpError::kkt_psq_handshake( - "noise handshake not finished after msg3", - )); - } - - Ok(()) + Ok(resp.into()) } - /// Receive final ACK that indicates finalisation of the handshake - pub(crate) async fn receive_final_ack( - &mut self, - outer_aead_key: &OuterAeadKey, - ) -> Result<(), LpError> { - match self - .connection - .receive_packet(Some(outer_aead_key)) - .await? - .message - { - LpMessage::Ack => Ok(()), - other => Err(LpError::unexpected_handshake_response( - other.typ(), - MessageType::Ack, - )), - } - } - - async fn complete_as_initiator_inner( - &mut self, - ) -> Result + pub async fn complete_handshake(self) -> Result where - S: LpTransport + Unpin, + S: LpHandshakeChannel + Unpin, { - // 0. retrieve the expected kem key hash. if we don't know it, - // there's no point in even trying to start the handshake - let Some(remote_peer) = self.remote_peer.take() else { - return Err(IntermediateHandshakeFailure::plain( - LpError::kkt_psq_handshake("initiator can't proceed without remote information"), - )); - }; + let mut rng = rand09::rngs::StdRng::from_os_rng(); + self.complete_handshake_with_rng(&mut rng).await + } - // 1. Generate and send ClientHelloData with fresh salt and both public keys - // and keep retrying until we manage to establish a receiver index without collisions - let mut attempt = 0; - let client_hello_data = loop { - attempt += 1; + pub async fn complete_handshake_with_rng(mut self, rng: &mut R) -> Result + where + S: LpHandshakeChannel + Unpin, + R: rand09::CryptoRng, + { + let ciphersuite = self.inner_state.local_peer.ciphersuite(); + let kem = ciphersuite.kem(); - debug!("sending client hello"); - let client_hello = self - .send_client_hello() - .await - .map_err(IntermediateHandshakeFailure::plain)?; - if self - .receive_client_hello_ack() - .await - .map_err(IntermediateHandshakeFailure::plain)? - { - debug!("received client hello ACK"); - break client_hello; - } - debug!("received client hello collision"); + let lp_peer_config = LpPeerConfig::new_client_to_entry(rng, false); - // TODO: make it configurable - if attempt > 3 { - return Err(IntermediateHandshakeFailure::plain( - LpError::kkt_psq_handshake( - "failed to establish receiver index without collision", - ), - )); - } - }; - let session_id = client_hello_data.receiver_index; - let session_id_bytes = session_id.to_le_bytes(); - let salt = client_hello_data.salt; + // 1. retrieve the expected kem key hash. if we don't know it, + let dir_hash = self + .initiator_data + .remote_peer + .expected_kem_key_hash(ciphersuite)?; + + // 2. prepare and send KKT request + let (mut initiator, kkt_request) = KKTInitiator::generate_one_way_request( + rng, + ciphersuite, + self.initiator_data.remote_peer.x25519(), + &dir_hash, + self.initiator_data.protocol_version, + Some(Vec::from(lp_peer_config.serialize())), + )?; + // derive the receiver index from the request + // let receiver_index = kkt_request - // 3. prepare and send KKT request debug!("sending KKT request"); - let kkt_data = self - .send_kkt_request(session_id, &remote_peer) - .await - .map_err(|source| IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: None, - source, - })?; + self.send_kkt_request(kkt_request).await?; - // 4. receive and process KKT response - let encapsulation_key = self - .receive_kkt_response(kkt_data, &remote_peer) - .await - .map_err(|source| IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: None, - source, - })?; + // 3. receive and process KKT response + let raw_response = self.receive_kkt_response().await?; debug!("received KKT response"); - // 5. prepare and send PSQ msg1 - debug!("sending PSQ msg1"); - let (outer_aead_key, mut noise_protocol, pq_shared_secret) = self - .send_psq_initiator_message(&remote_peer, &encapsulation_key, &salt, &session_id_bytes) - .await - .map_err(|source| IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: None, - source, - })?; + // the responder does not send a payload + let response = initiator.process_response(raw_response, 0)?; - // 6. receive and process PSQ msg2 - debug!("received PSQ msg2"); - if let Err(source) = self - .receive_psq_responder_message(&outer_aead_key, &mut noise_protocol) - .await - { - return Err(IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: Some(outer_aead_key), - source, - }); + // 4. generate and send PSQ request + let protocol = self.initiator_data.protocol_version; + let conn = self.inner_state.connection; + + // note: the clone is cheap due to internal Arcs + let encapsulation_key = response.encapsulation_key.clone(); + + // build the PSQ initiator + let initiator_ciphersuite = build_psq_ciphersuite( + &self.inner_state.local_peer, + &self.initiator_data.remote_peer, + &response.encapsulation_key, + )?; + + let mut psq_initiator = build_psq_principal(rng, protocol, initiator_ciphersuite)?; + + // PSQ msg 1 send + let mut buf = vec![0u8; psq_msg1_size(kem)]; + // annoyingly `RegistrationInitiator` has to write into unresizable `&mut [u8]`... + let n = psq_initiator.write_message(&[], &mut buf)?; + debug!("sending PSQ handshake msg"); + if n != buf.len() { + return Err(LpError::Internal( + "unexpected changes in PSQ msg1 size".to_string(), + )); + } + let msg = PSQMsg1::new(buf); + conn.send_handshake_message(msg, kem).await?; + + // 5. receive and process PSQ response + let psq_msg: PSQMsg2 = conn.receive_handshake_message(PSQ_MSG2_SIZE).await?; + debug!("received PSQ handshake msg"); + psq_initiator.read_message(&psq_msg, &mut [])?; + + if !psq_initiator.is_handshake_finished() { + return Err(LpError::kkt_psq_handshake( + "handshake not finished after receiving psq response", + )); } - // 7. prepare and send PSQ msg3 - debug!("sending PSQ msg3"); - if let Err(source) = self - .send_final_psq_message(session_id, &outer_aead_key, &mut noise_protocol) - .await - { - return Err(IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: Some(outer_aead_key), - source, - }); - } + let initiator_authenticator = Authenticator::Dh(self.inner_state.local_peer.x25519().pk); - // 8. receive final ACK and finalise - debug!("received final ACK"); - if let Err(source) = self.receive_final_ack(&outer_aead_key).await { - return Err(IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: Some(outer_aead_key), - source, - }); - } + let receiver_index = + lp_peer_config.derive_receiver_index(&initiator_authenticator, &encapsulation_key)?; - #[allow(clippy::expect_used)] - Ok(LpSession::new( - session_id, - self.protocol_version() - .expect("protocol version is known at this point"), - outer_aead_key, - self.local_peer.clone(), - remote_peer, - pq_shared_secret, - noise_protocol, - )) - } + let binding = PersistentSessionBinding { + initiator_authenticator, + responder_ecdh_pk: self.initiator_data.remote_peer.x25519_public, + responder_pq_pk: Some(encapsulation_key), + }; - // TODO: missing: receive counter check - pub async fn complete_as_initiator(mut self) -> Result - where - S: LpTransport + Unpin, - { - match self.complete_as_initiator_inner().await { - Ok(res) => Ok(res), - Err(err) => Err(self.try_send_error_packet(err).await), - } + let psq_session = psq_initiator.into_session()?; + LpSession::new(psq_session, binding, receiver_index, protocol) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::codec::{decrypt_data, encrypt_data}; + use crate::peer::mock_peers; + use crate::peer_config::LP_PEER_CONFIG_SIZE; + use crate::psq::{PSQ_MSG2_SIZE, psq_msg1_size, responder}; + use nym_kkt::context::KKTMode; + use nym_kkt::responder::KKTResponder; + use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, IntoEnumIterator, KEM, SignatureScheme}; + use nym_test_utils::helpers::{DeterministicRng09Send, u64_seeded_rng_09}; + use nym_test_utils::mocks::async_read_write::MockIOStream; + use nym_test_utils::traits::{Leak, Timeboxed}; + + #[tokio::test] + async fn initiator_test_plain() -> anyhow::Result<()> { + for kem in KEM::iter() { + let conn_init = MockIOStream::default(); + let conn_resp = conn_init.try_get_remote_handle(); + + // leak the connections (JUST FOR THE PURPOSE OF THIS TEST!) + // so they'd get 'static lifetime + let conn_init = conn_init.leak(); + let conn_resp = conn_resp.leak(); + + let (mut init, mut resp) = mock_peers(); + let resp_remote = resp.as_remote(); + + let ciphersuite = Ciphersuite::default().with_kem(kem); + init.ciphersuite = ciphersuite; + resp.ciphersuite = ciphersuite; + let initiator_data = InitiatorData::new(1, resp_remote); + + let handshake_init = + PSQHandshakeState::new(conn_init, init).as_initiator(initiator_data); + + let mut init_rng = DeterministicRng09Send::new(u64_seeded_rng_09(1)); + + let init_fut = tokio::spawn(async move { + handshake_init + .complete_handshake_with_rng(&mut init_rng) + .timeboxed() + .await + }); + + // responder: + let supported_sigs = [SignatureScheme::Ed25519]; + let supported_hash = [ + HashFunction::Blake3, + HashFunction::Shake256, + HashFunction::Shake128, + HashFunction::SHA256, + ]; + let resp_keys = resp.kem_keypairs.as_ref().unwrap(); + let responder_x25519_keypair = resp.x25519(); + + let kkt_responder = KKTResponder::new( + responder_x25519_keypair, + resp_keys, + &supported_hash, + &supported_sigs, + &[1], + )?; + + // 1. read KKT request + let raw_kkt_req: handshake_message::KKTRequest = conn_resp + .receive_handshake_message( + KKTRequest::size_excluding_payload(KKTMode::OneWay, kem) + LP_PEER_CONFIG_SIZE, + ) + .timeboxed() + .await??; + let req = raw_kkt_req.into(); + + // 2. process + let processed_req = kkt_responder.process_request(req, LP_PEER_CONFIG_SIZE)?; + conn_resp + .send_handshake_message::( + processed_req.response.into(), + kem, + ) + .timeboxed() + .await??; + + // 3. read PSQ req + let responder_ciphersuite = responder::build_psq_ciphersuite(&resp, kem)?; + let mut responder = + responder::build_psq_principal(rand09::rng(), 1, responder_ciphersuite)?; + let response_len = psq_msg1_size(kem); + + let msg: PSQMsg1 = conn_resp + .receive_handshake_message(response_len) + .timeboxed() + .await??; + responder.read_message(&msg, &mut []).unwrap(); + + // 4 send PSQ response + let mut buf = vec![0u8; PSQ_MSG2_SIZE]; + let n = responder.write_message(&[], &mut buf).unwrap(); + assert_eq!(n, buf.len()); + let msg = PSQMsg2::new(buf); + conn_resp + .send_handshake_message(msg, kem) + .timeboxed() + .await??; + + assert!(responder.is_handshake_finished()); + + let mut session_init = init_fut.await???; + + let mut r_transport = responder.into_session().unwrap(); + + // test serialization, deserialization + let channel_i = session_init.active_transport(); + let mut channel_r = r_transport.transport_channel().unwrap(); + + assert_eq!(channel_i.identifier(), channel_r.identifier()); + + let app_data_i = b"Derived session hey".as_slice(); + let app_data_r = b"Derived session ho".as_slice(); + + let ct_i = encrypt_data(app_data_i, channel_i)?; + let pt_r = decrypt_data(&ct_i, &mut channel_r)?; + + assert_eq!(app_data_i, pt_r); + + let ct_r = encrypt_data(app_data_r, &mut channel_r)?; + let pt_i = decrypt_data(&ct_r, channel_i)?; + + assert_eq!(app_data_r, pt_i); + } + Ok(()) } } diff --git a/common/nym-lp/src/psq/mod.rs b/common/nym-lp/src/psq/mod.rs index 06be6f26ec..b00ba087f0 100644 --- a/common/nym-lp/src/psq/mod.rs +++ b/common/nym-lp/src/psq/mod.rs @@ -1,172 +1,108 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::codec::OuterAeadKey; -use crate::message::ErrorPacketData; -use crate::packet::LpHeader; +use crate::packet::version; use crate::peer::{LpLocalPeer, LpRemotePeer}; -use crate::psq::helpers::LpTransportHandshakeExt; -use crate::{LpError, LpMessage, LpPacket}; -use nym_kkt::ciphersuite::Ciphersuite; -use nym_lp_transport::traits::LpTransport; -use tracing::debug; +use crate::transport::traits::LpHandshakeChannel; +use nym_kkt_ciphersuite::{HashFunction, IntoEnumIterator, KEM, SignatureScheme}; +pub(crate) mod handshake_message; mod helpers; -mod initiator; -mod responder; +pub mod initiator; +pub mod responder; -pub(crate) struct IntermediateHandshakeFailure { - /// Session id established during exchange if we managed to derive it - session_id: Option, +pub use initiator::PSQHandshakeStateInitiator; +pub use responder::PSQHandshakeStateResponder; - /// Protocol version established during the exchange - protocol_version: Option, +pub(crate) const AAD_INITIATOR_OUTER_V1: &[u8] = b"NYM-PQ-AAD-INIT-OUTER-V1"; +pub(crate) const AAD_INITIATOR_INNER_V1: &[u8] = b"NYM-PQ-AAD-INIT-INNER-V1"; +pub(crate) const AAD_RESPONDER_V1: &[u8] = b"NYM-PQ-AAD-RESP-V1"; +pub(crate) const SESSION_CONTEXT_V1: &[u8] = b"NYM-PQ-SESSION-CONTEXT-V1"; - /// Outer aead key established during exchange if we managed to derive it - outer_aead_key: Option, - - /// The error source - source: LpError, -} - -impl IntermediateHandshakeFailure { - fn plain(source: LpError) -> IntermediateHandshakeFailure { - IntermediateHandshakeFailure { - session_id: None, - protocol_version: None, - outer_aead_key: None, - source, - } +/// Size of the first (initiator) PSQ message including all serialisation overheads if no additional payload has been attached +pub(crate) fn psq_msg1_size(kem: KEM) -> usize { + match kem { + KEM::MlKem768 => 1247, + KEM::McEliece => 315, } } +/// Size of the second (responder) PSQ message including all serialisation overheads if no additional payload has been attached +pub(crate) const PSQ_MSG2_SIZE: usize = 70; + pub struct PSQHandshakeState<'a, S> { /// The underlying connection established for the handshake connection: &'a mut S, - /// Protocol version used for the exchange. - /// either known implicitly through the directory (initiator) - /// or established through client hello (responder) - protocol_version: Option, - - /// Ciphersuite selected for the KKT/PSQ exchange - ciphersuite: Ciphersuite, - /// Representation of a local Lewes Protocol peer /// encapsulating all the known information and keys. local_peer: LpLocalPeer, +} + +#[derive(Debug)] +pub struct InitiatorData { + /// Protocol version used for the exchange known implicitly through the directory + pub protocol_version: u8, /// Representation of a remote Lewes Protocol peer /// encapsulating all the known information and keys. - remote_peer: Option, + pub remote_peer: LpRemotePeer, +} - /// Counter for outgoing packets - sending_counter: u64, +impl InitiatorData { + pub fn new(protocol_version: u8, remote_peer: LpRemotePeer) -> Self { + InitiatorData { + protocol_version, + remote_peer, + } + } +} + +#[derive(Debug, Clone)] +pub struct ResponderData { + /// List of supported Hash Functions by this Responder + pub supported_hash_functions: Vec, + + /// List of supported Signature Schemes by this Responder + pub supported_signature_schemes: Vec, + + /// List of supported outer (LP) protocol version by this Responder + pub supported_outer_protocol_versions: Vec, +} + +impl Default for ResponderData { + fn default() -> Self { + // by default all schemes are supported + ResponderData { + supported_hash_functions: HashFunction::iter().collect(), + supported_signature_schemes: SignatureScheme::iter().collect(), + supported_outer_protocol_versions: vec![version::CURRENT], + } + } } impl<'a, S> PSQHandshakeState<'a, S> where - S: LpTransport + Unpin, + S: LpHandshakeChannel + Unpin, { - pub fn new(connection: &'a mut S, ciphersuite: Ciphersuite, local_peer: LpLocalPeer) -> Self { + pub fn new(connection: &'a mut S, local_peer: LpLocalPeer) -> Self { PSQHandshakeState { connection, - protocol_version: None, - ciphersuite, local_peer, - remote_peer: None, - sending_counter: 0, } } - #[must_use] - pub fn with_protocol_version(mut self, protocol_version: u8) -> Self { - self.protocol_version = Some(protocol_version); - self - } - - #[must_use] - pub fn with_remote_peer(mut self, remote_peer: LpRemotePeer) -> Self { - self.remote_peer = Some(remote_peer); - self - } - - fn protocol_version(&self) -> Result { - self.protocol_version - .ok_or_else(|| LpError::kkt_psq_handshake("unknown protocol version")) - } - - /// Generates the next counter value for outgoing packets. - pub fn next_counter(&mut self) -> u64 { - let counter = self.sending_counter; - self.sending_counter += 1; - counter - } - - pub fn next_packet( - &mut self, - session_id: u32, - protocol_version: u8, - message: LpMessage, - ) -> LpPacket { - let counter = self.next_counter(); - let header = LpHeader::new(session_id, counter, protocol_version); - LpPacket::new(header, message) - } - - pub(crate) async fn try_send_error_packet( - &mut self, - err: IntermediateHandshakeFailure, - ) -> LpError { - // if session_id is not known, we can't send the packet back (with the current design) - let (Some(session_id), Some(protocol)) = (err.session_id, err.protocol_version) else { - return err.source; - }; - if let Err(err) = self - .send_error_packet( - session_id, - protocol, - err.source.to_string(), - err.outer_aead_key.as_ref(), - ) - .await - { - debug!("failed to send back error response: {err}") + pub fn as_initiator(self, initiator_data: InitiatorData) -> PSQHandshakeStateInitiator<'a, S> { + PSQHandshakeStateInitiator { + initiator_data, + inner_state: self, } - err.source } - /// Attempt to send an error packet - pub(crate) async fn send_error_packet( - &mut self, - session_id: u32, - protocol_version: u8, - msg: impl Into, - outer_aead_key: Option<&OuterAeadKey>, - ) -> Result<(), LpError> { - let packet = self.next_packet( - session_id, - protocol_version, - LpMessage::Error(ErrorPacketData::new(msg)), - ); - self.connection.send_packet(packet, outer_aead_key).await?; - Ok(()) - } - - /// Attempt to receive a packet from connection, explicitly checking for an error response - /// and returning corresponding message if received - pub(crate) async fn receive_non_error( - &mut self, - outer_aead_key: Option<&OuterAeadKey>, - ) -> Result { - let packet = self.connection.receive_packet(outer_aead_key).await?; - - match &packet.message { - LpMessage::Error(error_packet) => Err(LpError::kkt_psq_handshake(format!( - "remote error: {}", - error_packet.message - ))), - _ => Ok(packet), + pub fn as_responder(self, responder_data: ResponderData) -> PSQHandshakeStateResponder<'a, S> { + PSQHandshakeStateResponder { + responder_data, + inner_state: self, } } } @@ -174,167 +110,263 @@ where #[cfg(test)] mod tests { use super::*; + use crate::codec::{decrypt_data, encrypt_data}; use crate::peer::mock_peers; - use crate::psq::helpers::LpTransportHandshakeExt; - use crate::psq::responder::DEFAULT_TIMESTAMP_TOLERANCE; - use mock_instant::thread_local::MockClock; - use nym_kkt::ciphersuite::{HashFunction, HashLength, KEM, SignatureScheme}; + use crate::peer_config::{LP_PEER_CONFIG_SIZE, LpPeerConfig}; + use libcrux_psq::handshake::types::Authenticator; + use libcrux_psq::session::{Session, SessionBinding}; + use libcrux_psq::{Channel, IntoSession}; + use nym_kkt::initiator::KKTInitiator; + use nym_kkt::responder::KKTResponder; + use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, KEM, SignatureScheme}; + use nym_test_utils::helpers::{ + DeterministicRng09Send, deterministic_rng_09, u64_seeded_rng_09, + }; use nym_test_utils::mocks::async_read_write::MockIOStream; use nym_test_utils::traits::{Leak, TimeboxedSpawnable}; - use std::time::Duration; use tokio::join; - #[allow(dead_code)] - async fn extract_error(conn: &mut MockIOStream) -> String { - let packet = conn.receive_packet(None).await.unwrap(); - match packet.message { - LpMessage::Error(error) => error.message, - _ => panic!("non error packet"), - } - } - #[tokio::test] async fn e2e_psq_handshake() -> anyhow::Result<()> { - let conn_init = MockIOStream::default(); - let conn_resp = conn_init.try_get_remote_handle(); + for kem in KEM::iter() { + let conn_init = MockIOStream::default(); + let conn_resp = conn_init.try_get_remote_handle(); - // leak the connections (JUST FOR THE PURPOSE OF THIS TEST!) - // so they'd get 'static lifetime - let conn_init = conn_init.leak(); - let conn_resp = conn_resp.leak(); + // leak the connections (JUST FOR THE PURPOSE OF THIS TEST!) + // so they'd get 'static lifetime + let conn_init = conn_init.leak(); + let conn_resp = conn_resp.leak(); + let ciphersuite = Ciphersuite::default().with_kem(kem); - let ciphersuite = Ciphersuite::new( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - HashLength::Default, - ); + let (mut init, mut resp) = mock_peers(); + init.ciphersuite = ciphersuite; + resp.ciphersuite = ciphersuite; + let resp_remote = resp.as_remote(); - let (init, resp) = mock_peers(); - let resp_remote = resp.as_remote(); + let handshake_init = PSQHandshakeState::new(conn_init, init) + .as_initiator(InitiatorData::new(1, resp_remote)); + let handshake_resp = + PSQHandshakeState::new(conn_resp, resp).as_responder(ResponderData::default()); - let handshake_init = PSQHandshakeState::new(conn_init, ciphersuite, init) - .with_protocol_version(1) - .with_remote_peer(resp_remote); - let handshake_resp = PSQHandshakeState::new(conn_resp, ciphersuite, resp); + let init_rng = DeterministicRng09Send::new(u64_seeded_rng_09(1)); + let resp_rng = DeterministicRng09Send::new(u64_seeded_rng_09(2)); - let resp_fut = handshake_resp.complete_as_responder().spawn_timeboxed(); - let init_fut = handshake_init.complete_as_initiator().spawn_timeboxed(); + // similarly leak the rngs to get the static lifetimes + let init_rng = init_rng.leak(); + let resp_rng = resp_rng.leak(); - let (session_init, session_resp) = join!(init_fut, resp_fut); + let init_fut = handshake_init + .complete_handshake_with_rng(init_rng) + .spawn_timeboxed(); + let resp_fut = handshake_resp + .complete_handshake_with_rng(resp_rng) + .spawn_timeboxed(); - let session_init = session_init???; - let session_resp = session_resp???; + let (session_init, session_resp) = join!(init_fut, resp_fut); - assert_eq!(session_init.id(), session_resp.id()); - assert_eq!( - session_init.outer_aead_key().as_bytes(), - session_resp.outer_aead_key().as_bytes() - ); - assert_eq!( - session_init.pq_shared_secret().as_bytes(), - session_resp.pq_shared_secret().as_bytes() - ); + let mut session_init = session_init???; + let mut session_resp = session_resp???; + + assert_eq!(session_init.receiver_index(), session_resp.receiver_index()); + + assert_eq!( + session_init.session_identifier(), + session_resp.session_identifier() + ); + + // test serialization, deserialization + let channel_i = session_init.active_transport(); + let channel_r = session_resp.active_transport(); + + assert_eq!(channel_i.identifier(), channel_r.identifier()); + + let app_data_i = b"Derived session hey".as_slice(); + let app_data_r = b"Derived session ho".as_slice(); + + let ct_i = encrypt_data(app_data_i, channel_i)?; + let pt_r = decrypt_data(&ct_i, channel_r)?; + + assert_eq!(app_data_i, pt_r); + + let ct_r = encrypt_data(app_data_r, channel_r)?; + let pt_i = decrypt_data(&ct_r, channel_i)?; + + assert_eq!(app_data_r, pt_i); + } Ok(()) } - #[tokio::test] - async fn preparing_client_hello_initiator() -> anyhow::Result<()> { - let mut conn_init = MockIOStream::default(); - let mut conn_resp = conn_init.try_get_remote_handle(); + // plain test without any wrappers + #[test] + fn e2e_test_plain() { + let mut rng = deterministic_rng_09(); - let ciphersuite = Ciphersuite::new( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - HashLength::Default, - ); - let (init, resp) = mock_peers(); - let resp_remote = resp.as_remote(); + for kem in KEM::iter() { + // SETUP START: + let protocol_version = 1; + let (mut init, resp) = mock_peers(); + init.ciphersuite = Ciphersuite::default().with_kem(kem); + let resp_remote = resp.as_remote(); + let dir_hash = resp_remote.expected_kem_key_hash(init.ciphersuite).unwrap(); - // as initiator - let mut handshake_init = PSQHandshakeState::new(&mut conn_init, ciphersuite, init) - .with_protocol_version(1) - .with_remote_peer(resp_remote); + let resp_keys = resp.kem_keypairs.as_ref().unwrap(); + let responder_x25519_keypair = resp.x25519(); - // you can generate and send (valid) client hello as initiator - let client_hello = handshake_init.send_client_hello().await?; - let LpMessage::ClientHello(received_client_hello) = - conn_resp.receive_packet(None).await?.message - else { - panic!("wrong message type"); - }; - assert_eq!(client_hello, received_client_hello); - Ok(()) - } + let supported_sigs = [SignatureScheme::Ed25519]; + let supported_hash = [ + HashFunction::Blake3, + HashFunction::Shake256, + HashFunction::Shake128, + HashFunction::SHA256, + ]; + let kkt_responder = KKTResponder::new( + responder_x25519_keypair, + resp_keys, + &supported_hash, + &supported_sigs, + &[protocol_version], + ) + .unwrap(); - // essentially make sure you can't accidentally trigger the handshake as the responder - #[tokio::test] - async fn preparing_client_hello_responder() -> anyhow::Result<()> { - let conn_init = MockIOStream::default(); - let mut conn_resp = conn_init.try_get_remote_handle(); + // SETUP END - let ciphersuite = Ciphersuite::new( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - HashLength::Default, - ); - let (_, resp) = mock_peers(); + let lp_peer_config = LpPeerConfig::new_client_to_entry(&mut rng, false); - // as initiator - let mut handshake_resp = PSQHandshakeState::new(&mut conn_resp, ciphersuite, resp); + // OneWay - MlKem + let (mut initiator, request) = KKTInitiator::generate_one_way_request( + &mut rng, + init.ciphersuite, + &responder_x25519_keypair.pk, + &dir_hash, + protocol_version, + Some(Vec::from(lp_peer_config.serialize())), + ) + .unwrap(); - // you can generate and send (valid) client hello as initiator - let sending_res = handshake_resp.send_client_hello().await; - assert!(sending_res.is_err()); - Ok(()) - } + let processed_req = kkt_responder + .process_request(request, LP_PEER_CONFIG_SIZE) + .unwrap(); - #[tokio::test] - async fn test_receive_client_hello_timestamp_too_skewed() -> anyhow::Result<()> { - let current_time = Duration::from_secs(10000); - MockClock::set_system_time(current_time); + let response = initiator + .process_response(processed_req.response, 0) + .unwrap(); + let encapsulation_key = response.encapsulation_key; - let too_old = current_time - DEFAULT_TIMESTAMP_TOLERANCE - Duration::from_secs(1); - let too_recent = current_time + DEFAULT_TIMESTAMP_TOLERANCE + Duration::from_secs(1); + let mut payload_buf_responder = vec![0u8; 4096]; + let mut payload_buf_initiator = vec![0u8; 4096]; - let ciphersuite = Ciphersuite::new( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - HashLength::Default, - ); + let initiator_ciphersuite = + initiator::build_psq_ciphersuite(&init, &resp_remote, &encapsulation_key).unwrap(); + let mut initiator = initiator::build_psq_principal( + rand09::rng(), + protocol_version, + initiator_ciphersuite, + ) + .unwrap(); - // TOO OLD - let mut conn_init = MockIOStream::default(); - let mut conn_resp = conn_init.try_get_remote_handle(); - let (init, resp) = mock_peers(); + let responder_ciphersuite = responder::build_psq_ciphersuite(&resp, kem).unwrap(); + let mut responder = responder::build_psq_principal( + rand09::rng(), + protocol_version, + responder_ciphersuite, + ) + .unwrap(); - let mut handshake_resp = PSQHandshakeState::new(&mut conn_resp, ciphersuite, resp); - let client_hello_too_old = init.build_client_hello_data(too_old.as_secs()); + // Send first message + let mut buf = vec![0u8; psq_msg1_size(kem)]; + let len_i = initiator.write_message(&[], &mut buf).unwrap(); + assert_eq!(len_i, buf.len()); - conn_init - .send_packet(client_hello_too_old.into_lp_packet(1), None) - .await?; - let err = handshake_resp.receive_client_hello().await.unwrap_err(); - assert!(err.to_string().contains("too old")); + // Read first message + let (_, _) = responder + .read_message(&buf, &mut payload_buf_responder) + .unwrap(); - // TOO RECENT - let mut conn_init = MockIOStream::default(); - let mut conn_resp = conn_init.try_get_remote_handle(); - let (init, resp) = mock_peers(); + // Get the authenticator out here, so we can deserialize the session later. + let Some(initiator_authenticator) = responder.initiator_authenticator() else { + panic!("No initiator authenticator found") + }; - let mut handshake_resp = PSQHandshakeState::new(&mut conn_resp, ciphersuite, resp); - let client_hello_too_recent = init.build_client_hello_data(too_recent.as_secs()); + // Respond + let mut buf = [0u8; PSQ_MSG2_SIZE]; + let len_r = responder.write_message(&[], &mut buf).unwrap(); + assert_eq!(len_r, buf.len()); - conn_init - .send_packet(client_hello_too_recent.into_lp_packet(1), None) - .await?; - let err = handshake_resp.receive_client_hello().await.unwrap_err(); + // Finalize on registration initiator + let (len_i_deserialized, _) = initiator + .read_message(&buf, &mut payload_buf_initiator) + .unwrap(); - assert!(err.to_string().contains("too future")); - Ok(()) + // We read the same amount of data. + assert_eq!(len_r, len_i_deserialized); + + // Ready for transport mode + assert!(initiator.is_handshake_finished()); + assert!(responder.is_handshake_finished()); + + let i_transport = initiator.into_session().unwrap(); + let r_transport = responder.into_session().unwrap(); + + // test serialization, deserialization + let mut session_storage = vec![0u8; 4096]; + i_transport + .serialize( + &mut session_storage, + SessionBinding { + initiator_authenticator: &Authenticator::Dh(init.x25519().pk), + responder_ecdh_pk: &responder_x25519_keypair.pk, + responder_pq_pk: Some(encapsulation_key.as_pq_encapsulation_key()), + }, + ) + .unwrap(); + let mut i_transport = Session::deserialize( + &session_storage, + SessionBinding { + initiator_authenticator: &Authenticator::Dh(init.x25519().pk), + responder_ecdh_pk: &responder_x25519_keypair.pk, + responder_pq_pk: Some(encapsulation_key.as_pq_encapsulation_key()), + }, + ) + .unwrap(); + + r_transport + .serialize( + &mut session_storage, + SessionBinding { + initiator_authenticator: &initiator_authenticator, + responder_ecdh_pk: &responder_x25519_keypair.pk, + responder_pq_pk: Some(encapsulation_key.as_pq_encapsulation_key()), + }, + ) + .unwrap(); + let mut r_transport = Session::deserialize( + &session_storage, + SessionBinding { + initiator_authenticator: &initiator_authenticator, + responder_ecdh_pk: &responder_x25519_keypair.pk, + responder_pq_pk: Some(encapsulation_key.as_pq_encapsulation_key()), + }, + ) + .unwrap(); + + let mut channel_i = i_transport.transport_channel().unwrap(); + let mut channel_r = r_transport.transport_channel().unwrap(); + + assert_eq!(channel_i.identifier(), channel_r.identifier()); + + let app_data_i = b"Derived session hey".as_slice(); + let app_data_r = b"Derived session ho".as_slice(); + + let ct_i = encrypt_data(app_data_i, &mut channel_i).unwrap(); + let pt_r = decrypt_data(&ct_i, &mut channel_r).unwrap(); + + assert_eq!(app_data_i, pt_r); + + let ct_r = encrypt_data(app_data_r, &mut channel_r).unwrap(); + let pt_i = decrypt_data(&ct_r, &mut channel_i).unwrap(); + + assert_eq!(app_data_r, pt_i); + } } } diff --git a/common/nym-lp/src/psq/responder.rs b/common/nym-lp/src/psq/responder.rs index 8882e1269f..1b85202602 100644 --- a/common/nym-lp/src/psq/responder.rs +++ b/common/nym-lp/src/psq/responder.rs @@ -1,461 +1,351 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::codec::OuterAeadKey; -use crate::message::{HandshakeData, KKTResponseData, MessageType}; -use crate::noise_protocol::NoiseProtocol; -use crate::peer::LpRemotePeer; -use crate::psk::psq_responder_process_message; -use crate::psq::helpers::{LpTransportHandshakeExt, current_timestamp}; -use crate::psq::{IntermediateHandshakeFailure, PSQHandshakeState}; -use crate::session::PqSharedSecret; -use crate::{ClientHelloData, LpError, LpMessage, LpSession}; -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, encrypt_kkt_frame}; -use nym_kkt::frame::KKTSessionId; -use nym_kkt::session::{responder_ingest_message, responder_process}; -use nym_lp_transport::traits::LpTransport; -use rand09::rng; -use std::time::Duration; +use crate::peer::LpLocalPeer; +use crate::peer_config::{LP_PEER_CONFIG_SIZE, LpPeerConfig}; +use crate::psq::handshake_message::{PSQMsg1, PSQMsg2}; +use crate::psq::helpers::kem_to_ciphersuite; +use crate::psq::{ + AAD_RESPONDER_V1, PSQ_MSG2_SIZE, PSQHandshakeState, ResponderData, SESSION_CONTEXT_V1, + handshake_message, psq_msg1_size, +}; +use crate::session::PersistentSessionBinding; +use crate::transport::traits::{HandshakeMessage, LpHandshakeChannel}; +use crate::{LpError, LpSession}; +use libcrux_psq::handshake::Responder; +use libcrux_psq::handshake::builders::{ + CiphersuiteBuilder, PrincipalBuilder, ResponderCiphersuite, +}; +use libcrux_psq::{Channel, IntoSession}; +use nym_kkt::context::KKTMode; +use nym_kkt::message::{KKTRequest, KKTResponse, ProcessedKKTRequest}; +use nym_kkt::responder::KKTResponder; +use nym_kkt_ciphersuite::KEM; +use rand09::SeedableRng; use tracing::debug; -pub const DEFAULT_TIMESTAMP_TOLERANCE: Duration = Duration::from_secs(30); - -// this will be removed anyway, so no point in doing anything more than a hardcoded placeholder -fn validate_client_hello_timestamp( - client_timestamp: u64, - tolerance: Duration, -) -> Result<(), LpError> { - let now = current_timestamp()?; - - let age = now.abs_diff(client_timestamp); - if age > tolerance.as_secs() { - let direction = if now >= client_timestamp { - "old" - } else { - "future" - }; - - return Err(LpError::kkt_psq_handshake(format!( - "ClientHello timestamp is too {direction} (age: {age}s, tolerance: {}s)", - tolerance.as_secs() - ))); - } - - Ok(()) +pub struct PSQHandshakeStateResponder<'a, S> { + pub(super) inner_state: PSQHandshakeState<'a, S>, + pub(super) responder_data: ResponderData, } -impl<'a, S> PSQHandshakeState<'a, S> +pub(crate) fn build_psq_principal( + rng: R, + version: u8, + ciphersuite: ResponderCiphersuite, +) -> Result, LpError> where - S: LpTransport + Unpin, + R: rand09::CryptoRng, { - pub(crate) fn encapsulated_kem_keys( - &self, - ) -> Result<(DecapsulationKey<'static>, EncapsulationKey<'static>), LpError> { - let kem_keys = self + let (ctx, aad) = match version { + 1 => (SESSION_CONTEXT_V1, AAD_RESPONDER_V1), + other => return Err(LpError::UnsupportedVersion { version: other }), + }; + + PrincipalBuilder::new(rng) + .context(ctx) + .outer_aad(aad) + .recent_keys_upper_bound(30) + .build_responder(ciphersuite) + .map_err(|inner| LpError::PSQResponderBuilderFailure { inner }) +} + +pub(crate) fn build_psq_ciphersuite( + peer: &LpLocalPeer, + kem: KEM, +) -> Result, LpError> { + let Some(kem_keys) = peer.kem_keypairs.as_ref() else { + return Err(LpError::ResponderWithMissingKEMKeys); + }; + + let psq_ciphersuite = kem_to_ciphersuite(kem); + let builder = CiphersuiteBuilder::new(psq_ciphersuite).longterm_x25519_keys(peer.x25519()); + + match kem { + KEM::MlKem768 => builder + .longterm_mlkem_encapsulation_key(kem_keys.ml_kem768_encapsulation_key()) + .longterm_mlkem_decapsulation_key(kem_keys.ml_kem768_decapsulation_key()), + KEM::McEliece => builder + .longterm_cmc_encapsulation_key(kem_keys.mc_eliece_encapsulation_key()) + .longterm_cmc_decapsulation_key(kem_keys.mc_eliece_decapsulation_key()), + } + .build_responder_ciphersuite() + .map_err(|inner| LpError::PSQResponderBuilderFailure { inner }) +} + +impl<'a, S> PSQHandshakeStateResponder<'a, S> +where + S: LpHandshakeChannel + Unpin, +{ + /// Attempt to receive a KKT request from a one-way client + async fn receive_one_way_kkt_request(&mut self) -> Result { + let packet_len = KKTRequest::size_excluding_payload( + KKTMode::OneWay, + self.inner_state.local_peer.ciphersuite.kem(), + ) + LP_PEER_CONFIG_SIZE; + + let req = self + .inner_state + .connection + .receive_handshake_message::(packet_len) + .await?; + + Ok(req.into()) + } + + /// Attempt to process the received KKT request + fn process_kkt_request(&self, kkt_request: KKTRequest) -> Result { + let kem_keys = &self + .inner_state .local_peer - .kem_psq + .kem_keypairs .as_ref() - .ok_or(LpError::ResponderWithMissingKEMKey)?; + .ok_or(LpError::ResponderWithMissingKEMKeys)?; - 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)) - } - - /// Attempt to receive and validate ClientHello - pub(crate) async fn receive_client_hello( - &mut self, - ) -> Result<(ClientHelloData, LpRemotePeer), LpError> { - let client_hello_packet = self.receive_non_error(None).await?; - let client_hello = match client_hello_packet.message { - LpMessage::ClientHello(client_hello) => client_hello, - other => { - return Err(LpError::unexpected_handshake_response( - other.typ(), - MessageType::ClientHello, - )); - } - }; - - validate_client_hello_timestamp( - client_hello.extract_timestamp(), - DEFAULT_TIMESTAMP_TOLERANCE, - )?; - - // TODO: somehow check for collision - - // set version and remote peer information - self.protocol_version = Some(client_hello_packet.header.protocol_version); - let remote_peer = LpRemotePeer::new( - client_hello.client_ed25519_public_key, - client_hello.client_lp_public_key, - ); - - Ok((client_hello, remote_peer)) - } - - /// Send client hello ACK - pub(crate) async fn send_client_hello_ack(&mut self, session_id: u32) -> Result<(), LpError> { - let protocol = self.protocol_version()?; - - let ack = self.next_packet(session_id, protocol, LpMessage::Ack); - self.connection.send_packet(ack, None).await?; - Ok(()) - } - - /// Attempt to receive and process a KKT request - pub(crate) async fn receive_kkt_request( - &mut self, - ) -> Result<(KKTContext, KKTSessionSecret, KKTSessionId), LpError> { - let kkt_request = match self.receive_non_error(None).await?.message { - LpMessage::KKTRequest(request) => request.0, - other => { - return Err(LpError::unexpected_handshake_response( - other.typ(), - MessageType::KKTRequest, - )); - } - }; - - let (session_secret, request_frame, remote_context) = - decrypt_initial_kkt_frame(self.local_peer.x25519.private_key(), &kkt_request)?; - let (context, _) = responder_ingest_message(&remote_context, None, None, &request_frame)?; - - Ok((context, session_secret, request_frame.session_id())) + let processed_req = KKTResponder::new( + &self.inner_state.local_peer.x25519, + kem_keys, + &self.responder_data.supported_hash_functions, + &self.responder_data.supported_signature_schemes, + &self.responder_data.supported_outer_protocol_versions, + )? + .process_request(kkt_request, LP_PEER_CONFIG_SIZE)?; + Ok(processed_req) } /// Attempt to send KKT response to the previously received request - pub(crate) async fn send_kkt_response( - &mut self, - session_id: u32, - (kkt_context, session_secret, kkt_session_id): (KKTContext, KKTSessionSecret, KKTSessionId), - encapsulation_key: &EncapsulationKey<'_>, - ) -> Result<(), LpError> { - let protocol = self.protocol_version()?; - - let response_frame = responder_process( - &kkt_context, - kkt_session_id, - self.local_peer.ed25519().private_key(), - encapsulation_key, - )?; - let encrypted_frame = encrypt_kkt_frame( - &mut rng(), - &session_secret, - &response_frame, - KKT_RESPONSE_AAD, - )?; - let lp_message = KKTResponseData::new(encrypted_frame).into(); - let lp_packet = self.next_packet(session_id, protocol, lp_message); - - self.connection.send_packet(lp_packet, None).await?; + async fn send_kkt_response(&mut self, response: KKTResponse, kem: KEM) -> Result<(), LpError> { + self.inner_state + .connection + .send_handshake_message::(response.into(), kem) + .await?; Ok(()) } /// Attempt to receive and process a PSQ msg1 request - pub(crate) async fn receive_psq_initiator_message( - &mut self, - remote_peer: &LpRemotePeer, - local_kem_keypair: (&DecapsulationKey<'_>, &EncapsulationKey<'_>), - salt: &[u8; 32], - session_id_bytes: &[u8; 4], - ) -> Result<(OuterAeadKey, NoiseProtocol, PqSharedSecret, Vec), LpError> { - let psq_msg1 = match self.receive_non_error(None).await?.message { - LpMessage::Handshake(response) => response.0, - other => { - return Err(LpError::unexpected_handshake_response( - other.typ(), - MessageType::Handshake, - )); - } - }; - - // Extract PSQ payload: [u16 psq_len][psq_payload][noise_msg] - if psq_msg1.len() < 2 { - return Err(LpError::kkt_psq_handshake("too short msg1 received")); - } - let handle_len = u16::from_le_bytes([psq_msg1[0], psq_msg1[1]]) as usize; - if psq_msg1.len() < 2 + handle_len { - return Err(LpError::kkt_psq_handshake("too short msg1 received")); - } - let psq_payload = &psq_msg1[2..2 + handle_len]; - let noise_payload = &psq_msg1[2 + handle_len..]; - - // Decapsulate PSK from PSQ payload using X25519 as DHKEM - let psq_responder = psq_responder_process_message( - self.local_peer.x25519.private_key(), - &remote_peer.x25519_public, - local_kem_keypair, - &remote_peer.ed25519_public, - psq_payload, - salt, - session_id_bytes, - )?; - - let psk = psq_responder.psk; - let psk_handle = psq_responder.psk_handle; - - // TEMP \/ - let outer_aead_key = OuterAeadKey::from_psk(&psk); - // TEMP /\ - - let mut noise_protocol = NoiseProtocol::build_new_responder( - self.local_peer.x25519().private_key().as_bytes(), - remote_peer.x25519_public.as_bytes(), - &psk, - )?; - noise_protocol.read_message(noise_payload)?; - - Ok(( - outer_aead_key, - noise_protocol, - PqSharedSecret::new(psq_responder.pq_shared_secret), - psk_handle, - )) - } - - /// Attempt to prepare and generate a responder PSQ msg2 - pub(crate) async fn send_psq_responder_message( - &mut self, - session_id: u32, - psk_handle: &[u8], - outer_aead_key: &OuterAeadKey, - noise_protocol: &mut NoiseProtocol, - ) -> Result<(), LpError> { - let protocol = self.protocol_version()?; - - let msg2 = noise_protocol - .get_bytes_to_send() - .ok_or_else(|| LpError::kkt_psq_handshake("failed to generate noise msg2"))??; - // Embed PSK handle in message: [u16 handle_len][handle_bytes][noise_msg] - let handle_len = psk_handle.len() as u16; - let mut combined = Vec::with_capacity(2 + psk_handle.len() + msg2.len()); - combined.extend_from_slice(&handle_len.to_le_bytes()); - combined.extend_from_slice(psk_handle); - combined.extend_from_slice(&msg2); - - let lp_message = HandshakeData::new(combined).into(); - let lp_packet = self.next_packet(session_id, protocol, lp_message); - self.connection - .send_packet(lp_packet, Some(outer_aead_key)) - .await?; - Ok(()) - } - - /// Attempt to receive and process final PSQ msg3 - pub(crate) async fn receive_final_psq_message( - &mut self, - outer_aead_key: &OuterAeadKey, - noise_protocol: &mut NoiseProtocol, - ) -> Result<(), LpError> { - let psq_msg3 = match self + async fn receive_psq_initiator_message(&mut self, kem: KEM) -> Result, LpError> { + let packet_len = psq_msg1_size(kem); + let msg: PSQMsg1 = self + .inner_state .connection - .receive_packet(Some(outer_aead_key)) - .await? - .message - { - LpMessage::Handshake(response) => response.0, - other => { - return Err(LpError::unexpected_handshake_response( - other.typ(), - MessageType::Handshake, - )); - } - }; - - noise_protocol.read_message(&psq_msg3)?; - if !noise_protocol.is_handshake_finished() { - return Err(LpError::kkt_psq_handshake( - "noise handshake not finished after msg3", - )); - } - Ok(()) - } - - /// Send final ACK to indicate finalisation of the handshake - pub(crate) async fn send_final_ack( - &mut self, - session_id: u32, - outer_aead_key: &OuterAeadKey, - ) -> Result<(), LpError> { - let protocol = self.protocol_version()?; - - let ack = self.next_packet(session_id, protocol, LpMessage::Ack); - self.connection - .send_packet(ack, Some(outer_aead_key)) + .receive_handshake_message(packet_len) .await?; - Ok(()) + Ok(msg.into_bytes()) } - async fn complete_as_responder_inner( - &mut self, - ) -> Result + pub async fn complete_handshake(self) -> Result where - S: LpTransport + Unpin, + S: LpHandshakeChannel + Unpin, { - // 1. receive and validate ClientHello - let (client_hello_data, remote_peer) = - self.receive_client_hello() - .await - .map_err(|source| IntermediateHandshakeFailure { - session_id: None, - protocol_version: self.protocol_version, - outer_aead_key: None, - source, - })?; - debug!("received client hello"); + let mut rng = rand09::rngs::StdRng::from_os_rng(); + self.complete_handshake_with_rng(&mut rng).await + } - let session_id = client_hello_data.receiver_index; - let session_id_bytes = session_id.to_le_bytes(); - let salt = client_hello_data.salt; - - // 2. send ack - debug!("sending client hello ACK"); - self.send_client_hello_ack(session_id) - .await - .map_err(|source| IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: None, - source, - })?; - - // 3. receive and process KKT request - let kkt_data = - self.receive_kkt_request() - .await - .map_err(|source| IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: None, - source, - })?; + pub async fn complete_handshake_with_rng(mut self, rng: &mut R) -> Result + where + S: LpHandshakeChannel + Unpin, + R: rand09::CryptoRng, + { + // 1. receive and process KKTRequest + let kkt_request = self.receive_one_way_kkt_request().await?; debug!("received KKT request"); - // TEMP: 'derive' KEM keys - let (dec_key, enc_key) = - self.encapsulated_kem_keys() - .map_err(|source| IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: None, - source, - })?; + let processed_req = self.process_kkt_request(kkt_request)?; + let kem = processed_req.requested_kem; - // 4. prepare and send KKT response + let lp_peer_config = LpPeerConfig::deserialize(&processed_req.request_payload)?; + + // 2. send back the KKTResponse debug!("sending KKT response"); - self.send_kkt_response(session_id, kkt_data, &enc_key) - .await - .map_err(|source| IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: None, - source, - })?; + self.send_kkt_response(processed_req.response, kem).await?; - // 5. receive and process PSQ msg1 - debug!("received PSQ msg1"); - let (outer_aead_key, mut noise_protocol, pq_shared_secret, psk_handle) = self - .receive_psq_initiator_message( - &remote_peer, - (&dec_key, &enc_key), - &salt, - &session_id_bytes, - ) - .await - .map_err(|source| IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: None, - source, - })?; + // 3. receive and process PSQ request + let raw_psq1 = self.receive_psq_initiator_message(kem).await?; + debug!("received PSQ handshake msg"); - // 6. prepare and send PSQ msg2 - debug!("sending PSQ msg2"); - if let Err(source) = self - .send_psq_responder_message( - session_id, - &psk_handle, - &outer_aead_key, - &mut noise_protocol, - ) - .await - { - return Err(IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: Some(outer_aead_key), - source, - }); + // construct the responder and process the message + let responder_ciphersuite = build_psq_ciphersuite(&self.inner_state.local_peer, kem)?; + let version = processed_req.outer_protocol_version; + let mut psq_responder = build_psq_principal(rng, version, responder_ciphersuite)?; + psq_responder.read_message(&raw_psq1, &mut [])?; + + let initiator_authenticator = psq_responder + .initiator_authenticator() + .ok_or(LpError::MissingInitiatorAuthenticator)?; + + // 4. send PSQ response + let conn = self.inner_state.connection; + + let mut buf = vec![0u8; PSQ_MSG2_SIZE]; + psq_responder.write_message(&[], &mut buf)?; + debug!("sending PSQ handshake msg"); + conn.send_handshake_message(PSQMsg2::new(buf), kem).await?; + + if !psq_responder.is_handshake_finished() { + return Err(LpError::kkt_psq_handshake( + "handshake not finished after receiving psq response", + )); } - // 7. receive and process PSQ msg3 - debug!("received PSQ msg3"); - if let Err(source) = self - .receive_final_psq_message(&outer_aead_key, &mut noise_protocol) - .await - { - return Err(IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: Some(outer_aead_key), - source, - }); - } + // SAFETY: we have completed the exchange so this key MUST HAVE been present + #[allow(clippy::unwrap_used)] + let kem_key = self + .inner_state + .local_peer + .kem_keypairs + .as_ref() + .unwrap() + .encapsulation_key(kem) + .unwrap(); - // 8. [optionally] send ACK to finalise - debug!("sending final ACK"); - if let Err(source) = self.send_final_ack(session_id, &outer_aead_key).await { - return Err(IntermediateHandshakeFailure { - session_id: Some(session_id), - protocol_version: self.protocol_version, - outer_aead_key: Some(outer_aead_key), - source, - }); - } + let receiver_index = + lp_peer_config.derive_receiver_index(&initiator_authenticator, &kem_key)?; - #[allow(clippy::expect_used)] - Ok(LpSession::new( - session_id, - self.protocol_version() - .expect("protocol version is known at this point"), - outer_aead_key, - self.local_peer.clone(), - remote_peer, - pq_shared_secret, - noise_protocol, - )) - } + let binding = PersistentSessionBinding { + initiator_authenticator, + responder_ecdh_pk: self.inner_state.local_peer.x25519().pk, + responder_pq_pk: Some(kem_key), + }; - pub async fn complete_as_responder(mut self) -> Result - where - S: LpTransport + Unpin, - { - match self.complete_as_responder_inner().await { - Ok(res) => Ok(res), - Err(err) => Err(self.try_send_error_packet(err).await), - } + let psq_session = psq_responder.into_session()?; + LpSession::new( + psq_session, + binding, + receiver_index, + processed_req.outer_protocol_version, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::codec::{decrypt_data, encrypt_data}; + use crate::peer::mock_peers; + use crate::peer_config::LpPeerConfig; + use crate::psq::initiator; + use nym_kkt::initiator::KKTInitiator; + use nym_kkt_ciphersuite::{Ciphersuite, IntoEnumIterator}; + use nym_test_utils::helpers::{ + DeterministicRng09Send, deterministic_rng_09, u64_seeded_rng_09, + }; + use nym_test_utils::mocks::async_read_write::MockIOStream; + use nym_test_utils::traits::{Leak, Timeboxed}; + + #[tokio::test] + async fn responder_test_plain() -> anyhow::Result<()> { + for kem in KEM::iter() { + let conn_init = MockIOStream::default(); + let conn_resp = conn_init.try_get_remote_handle(); + + // SETUP START: + // leak the connections (JUST FOR THE PURPOSE OF THIS TEST!) + // so they'd get 'static lifetime + let conn_init = conn_init.leak(); + let conn_resp = conn_resp.leak(); + + let (mut init, mut resp) = mock_peers(); + let resp_remote = resp.as_remote(); + + let ciphersuite = Ciphersuite::default().with_kem(kem); + init.ciphersuite = ciphersuite; + resp.ciphersuite = ciphersuite; + + let responder_data = ResponderData::default(); + let handshake_resp = + PSQHandshakeState::new(conn_resp, resp).as_responder(responder_data); + + let mut resp_rng = DeterministicRng09Send::new(u64_seeded_rng_09(2)); + let resp_fut = tokio::spawn(async move { + handshake_resp + .complete_handshake_with_rng(&mut resp_rng) + .timeboxed() + .await + }); + + // initiator: + + let mut rng = deterministic_rng_09(); + let dir_hash = resp_remote.expected_kem_key_hash(init.ciphersuite)?; + + let lp_peer_config = LpPeerConfig::new_client_to_entry(&mut rng, false); + + // OneWay - MlKem + let (mut initiator, request) = KKTInitiator::generate_one_way_request( + &mut rng, + init.ciphersuite, + &resp_remote.x25519_public, + &dir_hash, + 1, + Some(Vec::from(lp_peer_config.serialize())), + )?; + + // 1. send kkt request + conn_init + .send_handshake_message::(request.into(), kem) + .timeboxed() + .await??; + + // 2. receive KKT response + let response_len = KKTResponse::size_excluding_payload(kem); + let resp: handshake_message::KKTResponse = conn_init + .receive_handshake_message(response_len) + .timeboxed() + .await??; + let kkt_response = resp.into(); + + let response = initiator.process_response(kkt_response, 0)?; + let encapsulation_key = response.encapsulation_key; + + let initiator_ciphersuite = + initiator::build_psq_ciphersuite(&init, &resp_remote, &encapsulation_key)?; + let mut initiator = + initiator::build_psq_principal(rand09::rng(), 1, initiator_ciphersuite)?; + + // 3. send PSQ msg1 + // Send first message + let mut buf = vec![0u8; psq_msg1_size(kem)]; + let n = initiator.write_message(&[], &mut buf).unwrap(); + assert_eq!(n, buf.len()); + let msg = PSQMsg1::new(buf); + conn_init + .send_handshake_message(msg, kem) + .timeboxed() + .await??; + + // 4. receive PSQ msg2 + let msg: PSQMsg2 = conn_init + .receive_handshake_message(PSQ_MSG2_SIZE) + .timeboxed() + .await??; + initiator.read_message(&msg, &mut []).unwrap(); + + assert!(initiator.is_handshake_finished()); + + let mut session_resp = resp_fut.await???; + + let mut i_transport = initiator.into_session().unwrap(); + + // test serialization, deserialization + let mut channel_i = i_transport.transport_channel().unwrap(); + let channel_r = session_resp.active_transport(); + + assert_eq!(channel_i.identifier(), channel_r.identifier()); + + let app_data_i = b"Derived session hey".as_slice(); + let app_data_r = b"Derived session ho".as_slice(); + + let ct_i = encrypt_data(app_data_i, &mut channel_i)?; + let pt_r = decrypt_data(&ct_i, channel_r)?; + + assert_eq!(app_data_i, pt_r); + + let ct_r = encrypt_data(app_data_r, channel_r)?; + let pt_i = decrypt_data(&ct_r, &mut channel_i)?; + + assert_eq!(app_data_r, pt_i); + } + + Ok(()) } } diff --git a/common/nym-lp/src/replay/validator.rs b/common/nym-lp/src/replay/validator.rs index 3e7a0cfa5d..d3edb7b84b 100644 --- a/common/nym-lp/src/replay/validator.rs +++ b/common/nym-lp/src/replay/validator.rs @@ -38,6 +38,16 @@ const N_WORDS: usize = 16; /// Total number of bits in the bitmap const N_BITS: usize = WORD_SIZE * N_WORDS; +/// Current packet count statistics +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct PacketCount { + /// the next expected counter value + pub next: u64, + + /// the total number of received packets + pub received: u64, +} + /// Validator for receiving key counters to prevent replay attacks. /// /// This structure maintains a bitmap of received packets and validates @@ -205,11 +215,14 @@ impl ReceivingKeyCounterValidator { /// Returns the current packet count statistics. /// - /// Returns a tuple of `(next, receive_cnt)` where: + /// Returns a struct consisting of `(next, receive_cnt)` where: /// - `next` is the next expected counter value /// - `receive_cnt` is the total number of received packets - pub fn current_packet_cnt(&self) -> (u64, u64) { - (self.next, self.receive_cnt) + pub fn current_packet_cnt(&self) -> PacketCount { + PacketCount { + next: self.next, + received: self.receive_cnt, + } } #[inline(always)] @@ -481,7 +494,10 @@ mod tests { let mut validator = ReceivingKeyCounterValidator::default(); // Initial state - let (next, count) = validator.current_packet_cnt(); + let PacketCount { + next, + received: count, + } = validator.current_packet_cnt(); assert_eq!(next, 0); assert_eq!(count, 0); @@ -490,21 +506,30 @@ mod tests { assert!(validator.mark_did_receive_branchless(1).is_ok()); assert!(validator.mark_did_receive_branchless(2).is_ok()); - let (next, count) = validator.current_packet_cnt(); + let PacketCount { + next, + received: count, + } = validator.current_packet_cnt(); assert_eq!(next, 3); assert_eq!(count, 3); // After an out of order packet assert!(validator.mark_did_receive_branchless(10).is_ok()); - let (next, count) = validator.current_packet_cnt(); + let PacketCount { + next, + received: count, + } = validator.current_packet_cnt(); assert_eq!(next, 11); assert_eq!(count, 4); // After a packet from the past (within window) assert!(validator.mark_did_receive_branchless(5).is_ok()); - let (next, count) = validator.current_packet_cnt(); + let PacketCount { + next, + received: count, + } = validator.current_packet_cnt(); assert_eq!(next, 11); // Next doesn't change assert_eq!(count, 5); // Count increases } @@ -553,7 +578,7 @@ mod tests { assert!(validator.mark_did_receive_branchless(first_jump).is_ok()); // Verify next counter is updated - let (next, _) = validator.current_packet_cnt(); + let PacketCount { next, .. } = validator.current_packet_cnt(); assert_eq!(next, first_jump + 1); // Second large jump, even further ahead @@ -561,7 +586,7 @@ mod tests { assert!(validator.mark_did_receive_branchless(second_jump).is_ok()); // Verify next counter is updated again - let (next, _) = validator.current_packet_cnt(); + let PacketCount { next, .. } = validator.current_packet_cnt(); assert_eq!(next, second_jump + 1); // Test packets within the new window @@ -726,10 +751,10 @@ mod tests { // Check final state of the validator let final_state = validator.lock().unwrap(); - let (_next, receive_cnt) = final_state.current_packet_cnt(); + let count = final_state.current_packet_cnt(); // Verify that the received count matches our successful operations - assert_eq!(receive_cnt, total_successes as u64); + assert_eq!(count.received, total_successes as u64); } #[test] diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 52a336cfca..4ed5e0fb02 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -4,218 +4,177 @@ //! Session management for the Lewes Protocol. //! //! This module implements session management functionality, including replay protection -//! and Noise protocol state handling. -use crate::codec::OuterAeadKey; -use crate::message::EncryptedDataPayload; -// noiserm -use crate::noise_protocol::{NoiseError, NoiseProtocol, ReadResult}; -use crate::packet::LpHeader; +use crate::codec::{decrypt_lp_packet, encrypt_lp_packet}; +use crate::packet::{EncryptedLpPacket, LpHeader, LpMessage, LpPacket}; use crate::peer::{LpLocalPeer, LpRemotePeer}; -use crate::psk::derive_subsession_psk; -use crate::psq::PSQHandshakeState; -use crate::replay::ReceivingKeyCounterValidator; -use crate::{LpError, LpMessage, LpPacket}; -use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_kkt::ciphersuite::{Ciphersuite, HashFunction, HashLength, KEM, SignatureScheme}; -use nym_lp_transport::traits::LpTransport; -use parking_lot::Mutex; -use snow::Builder; -use zeroize::{Zeroize, ZeroizeOnDrop}; +use crate::peer_config::LpReceiverIndex; +use crate::psq::{ + InitiatorData, PSQHandshakeState, PSQHandshakeStateInitiator, PSQHandshakeStateResponder, + ResponderData, +}; +use crate::replay::validator::PacketCount; +use crate::transport::LpHandshakeChannel; +use crate::{LpError, replay::ReceivingKeyCounterValidator}; +use libcrux_psq::handshake::types::{Authenticator, DHPublicKey}; +use libcrux_psq::session::{Session, SessionBinding}; +use nym_kkt::keys::EncapsulationKey; +use std::fmt::{Debug, Formatter}; -/// PQ shared secret wrapper with automatic memory zeroization. -/// Ensures K_pq is cleared from memory when dropped. -#[derive(Clone, Zeroize, ZeroizeOnDrop)] -pub struct PqSharedSecret([u8; 32]); - -impl PqSharedSecret { - pub fn new(secret: [u8; 32]) -> Self { - Self(secret) - } - - pub fn as_bytes(&self) -> &[u8; 32] { - &self.0 - } -} - -impl std::fmt::Debug for PqSharedSecret { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PqSharedSecret") - .field("secret", &"") - .finish() - } -} +pub type SessionId = [u8; 32]; /// A session in the Lewes Protocol, handling connection state with Noise. /// /// Sessions manage connection state, including LP replay protection. /// Each session has a unique receiving index and sending index for connection identification. -#[derive(Debug)] pub struct LpSession { - /// Id of the established session - session_id: u32, + /// The underlying established session + psq_session: Session, + + /// The public key material bound to the underlying session. Used for serialisation. + session_binding: PersistentSessionBinding, + + /// The current active transport channel + // In the future it might get split between UDP and TCP transports + active_transport: libcrux_psq::session::Transport, + + /// Look-up index established during the initial KKT exchange + receiver_index: LpReceiverIndex, /// Negotiated protocol version from handshake. - /// Set during handshake completion from the ClientHello/ServerHello packet header. - /// Used for future version negotiation and compatibility checks. - version: u8, - - /// Outer AEAD key for packet encryption (derived from PSK after PSQ handshake). - outer_aead_key: OuterAeadKey, - - /// 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, - - // TODO: ALL BELOW maybe not needed after all? - /// Raw PQ shared secret (K_pq) from PSQ KEM encapsulation/decapsulation. - /// Stored after PSQ handshake completes for subsession PSK derivation. - pq_shared_secret: PqSharedSecret, - - /// Noise protocol state machine - noise_state: NoiseProtocol, + protocol_version: u8, /// Counter for outgoing packets sending_counter: u64, /// Validator for incoming packet counters to prevent replay attacks receiving_counter: ReceivingKeyCounterValidator, +} - /// Monotonically increasing counter for subsession indices. - /// Each subsession gets a unique index to ensure unique PSK derivation. - /// Uses u64 to make overflow practically impossible (~585k years at 1M/sec). - subsession_counter: u64, +/// Wraps public key material that is bound to a session. +#[derive(Clone)] +pub struct PersistentSessionBinding { + /// The initiator's authenticator value, i.e. a long-term DH public value or signature verification key. + pub initiator_authenticator: Authenticator, - /// True if this session has been demoted to read-only mode. - /// Demoted sessions can still receive/decrypt but cannot send/encrypt. - read_only: bool, + /// The responder's long term DH public value. + pub responder_ecdh_pk: DHPublicKey, - /// ID of the successor session that replaced this one. - /// Set when demote() is called. - successor_session_id: Option, + /// The responder's long term PQ-KEM public key (if any). + pub responder_pq_pk: Option, +} + +impl Debug for PersistentSessionBinding { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PersistentSessionBinding") + .field("initiator_authenticator", &"") + .field("responder_ecdh_pk", &self.responder_ecdh_pk) + .field("responder_pq_pk", &self.responder_pq_pk) + .finish() + } +} + +impl<'a> From<&'a PersistentSessionBinding> for SessionBinding<'a> { + fn from(value: &'a PersistentSessionBinding) -> Self { + SessionBinding { + initiator_authenticator: &value.initiator_authenticator, + responder_ecdh_pk: &value.responder_ecdh_pk, + responder_pq_pk: value + .responder_pq_pk + .as_ref() + .map(|k| k.as_pq_encapsulation_key()), + } + } +} + +impl Debug for LpSession { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LpSession") + .field("session_id", &self.psq_session.identifier()) + .field("session_binding", &self.session_binding) + .field("active_transport_id", &self.active_transport.identifier()) + .field("protocol_version", &self.protocol_version) + .field("sending_counter", &self.sending_counter) + .field("receiving_counter", &self.receiving_counter) + .finish() + } } impl LpSession { /// Creates a new session after completed KTT/PSQ exchange - /// - /// # Arguments - /// - /// * `session_id` - Session identifier - /// * `version` - Protocol version to attach in all `LpPacket`s - /// * `outer_aead_key` - Outer AEAD key for packet encryption - /// * `local_peer` - This side's LP peer's keys - /// * `remote_peer` - The remote's LP peer's keys - /// * `pq_shared_secret` - Raw PQ shared secret (K_pq) from PSQ KEM encapsulation/decapsulation. - /// * `noise_state` - Noise protocol state machine pub fn new( - session_id: u32, - version: u8, - outer_aead_key: OuterAeadKey, - local_peer: LpLocalPeer, - remote_peer: LpRemotePeer, - pq_shared_secret: PqSharedSecret, - noise_state: NoiseProtocol, - ) -> Self { - LpSession { - session_id, - version, - outer_aead_key, - local_peer, - remote_peer, - pq_shared_secret, - noise_state, + mut psq_session: Session, + session_binding: PersistentSessionBinding, + receiver_index: LpReceiverIndex, + protocol_version: u8, + ) -> Result { + // attempt to derive initial transport + let transport = psq_session + .transport_channel() + .map_err(|inner| LpError::TransportDerivationFailure { inner })?; + + Ok(LpSession { + psq_session, + session_binding, + active_transport: transport, + receiver_index, + protocol_version, sending_counter: 0, receiving_counter: Default::default(), - subsession_counter: 0, - read_only: false, - successor_session_id: None, - } - } - - /// Create an instance of `Ciphersuite` using hardcoded defaults. - /// This is a temporary workaround until values can be properly inferred - /// from reported version - pub fn default_ciphersuite() -> Ciphersuite { - Ciphersuite::new( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - HashLength::Default, - ) + }) } /// Helper function to create `PSQHandshakeState` for the handshake initiator - pub fn complete_as_initiator( + pub fn psq_handshake_initiator( connection: &'_ mut S, - ciphersuite: Ciphersuite, local_peer: LpLocalPeer, remote_peer: LpRemotePeer, remote_protocol_version: u8, - ) -> PSQHandshakeState<'_, S> + ) -> PSQHandshakeStateInitiator<'_, S> where - S: LpTransport + Unpin, + S: LpHandshakeChannel + Unpin, { - PSQHandshakeState::new(connection, ciphersuite, local_peer) - .with_protocol_version(remote_protocol_version) - .with_remote_peer(remote_peer) + PSQHandshakeState::new(connection, local_peer) + .as_initiator(InitiatorData::new(remote_protocol_version, remote_peer)) } /// Helper function to create `PSQHandshakeState` for the handshake responder pub fn psq_handshake_responder( connection: &'_ mut S, - ciphersuite: Ciphersuite, local_peer: LpLocalPeer, - ) -> PSQHandshakeState<'_, S> + ) -> PSQHandshakeStateResponder<'_, S> where - S: LpTransport + Unpin, + S: LpHandshakeChannel + Unpin, { - PSQHandshakeState::new(connection, ciphersuite, local_peer) + PSQHandshakeState::new(connection, local_peer).as_responder(ResponderData::default()) } - pub fn id(&self) -> u32 { - self.session_id + pub fn session_binding(&self) -> &PersistentSessionBinding { + &self.session_binding + } + + pub fn active_transport(&mut self) -> &mut libcrux_psq::session::Transport { + &mut self.active_transport + } + + pub fn session_identifier(&self) -> &[u8; 32] { + self.psq_session.identifier() + } + + pub fn receiver_index(&self) -> LpReceiverIndex { + self.receiver_index } /// Returns the negotiated protocol version from the handshake. /// /// Set during `LpSession` creation after sending / receiving `ClientHelloData` pub fn negotiated_version(&self) -> u8 { - self.version - } - - /// 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) -> 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) -> &x25519::PublicKey { - &self.remote_peer.x25519_public - } - - /// Returns the outer AEAD key for packet encryption/decryption. - pub fn outer_aead_key(&self) -> &OuterAeadKey { - &self.outer_aead_key + self.protocol_version } pub fn next_packet(&mut self, message: LpMessage) -> Result { let counter = self.next_counter(); - let header = LpHeader::new(self.id(), counter, self.version); + let header = LpHeader::new(self.receiver_index(), counter, self.protocol_version); let packet = LpPacket::new(header, message); Ok(packet) } @@ -274,508 +233,132 @@ impl LpSession { /// A tuple containing: /// * The next expected counter value for incoming packets /// * The total number of received packets - pub fn current_packet_cnt(&self) -> (u64, u64) { + pub fn current_packet_cnt(&self) -> PacketCount { self.receiving_counter.current_packet_cnt() } - /// Returns the PQ shared secret (K_pq). - /// - /// This is the raw KEM output from PSQ before Blake3 KDF combination. - /// Used for deriving subsession PSKs to maintain PQ protection. - pub fn pq_shared_secret(&self) -> &PqSharedSecret { - &self.pq_shared_secret - } - - /// Gets the next subsession index and increments the counter. - /// - /// Each subsession requires a unique index to ensure unique PSK derivation. - /// The index is monotonically increasing per session. - pub fn next_subsession_index(&mut self) -> u64 { - let next = self.subsession_counter; - self.subsession_counter += 1; - next - } - - /// Returns true if this session is in read-only mode. - /// - /// Read-only sessions have been demoted after a subsession was promoted. - /// They can still decrypt incoming messages but cannot encrypt outgoing ones. - pub fn is_read_only(&self) -> bool { - self.read_only - } - - /// Demotes this session to read-only mode after a subsession replaces it. - /// - /// After demotion: - /// - `encrypt_data()` will return `NoiseError::SessionReadOnly` - /// - `decrypt_data()` still works (to drain in-flight messages) - /// - Session should be cleaned up after TTL expires - /// - /// # Arguments - /// * `successor_idx` - The receiver index of the session that replaced this one - pub fn demote(&mut self, successor_idx: u32) { - self.successor_session_id = Some(successor_idx); - self.read_only = true; - } - - /// Returns the successor session ID if this session was demoted. - pub fn successor_session_id(&self) -> Option { - self.successor_session_id - } - - /// Encrypts application data payload using the established Noise transport session. + /// Encrypts a produced application using the established transport session + /// and produce an `EncryptedLpPacket` /// /// # Arguments /// - /// * `payload` - The application data to encrypt. + /// * `data` - plaintext data to encrypt /// /// # Returns /// - /// * `Ok(Vec)` containing the encrypted Noise message ciphertext. - /// * `Err(NoiseError)` if the session is not in transport mode or encryption fails. - pub fn encrypt_data(&mut self, payload: &[u8]) -> Result { - // Check if session is read-only (demoted) - if self.read_only { - return Err(NoiseError::SessionReadOnly); - } - - let payload = self.noise_state.write_message(payload)?; - Ok(LpMessage::EncryptedData(EncryptedDataPayload(payload))) + /// * `Ok(EncryptedLpPacket)` containing the encrypted message ciphertext. + /// * `Err(LpError)` if the session is not in transport mode or encryption fails. + pub(crate) fn encrypt_application_data( + &mut self, + data: LpMessage, + ) -> Result { + let packet = self.next_packet(data)?; + encrypt_lp_packet(packet, &mut self.active_transport) } - /// Decrypts an incoming Noise message containing application data. + /// Decrypts an incoming LpPacket /// /// # Arguments /// - /// * `noise_ciphertext` - The encrypted Noise message received from the peer. + /// * `ciphertext` - The encrypted packet /// /// # Returns /// - /// * `Ok(Vec)` containing the decrypted application data payload. - /// * `Err(NoiseError)` if the session is not in transport mode, decryption fails, or the message is not data. - pub fn decrypt_data(&mut self, noise_ciphertext: &LpMessage) -> Result, NoiseError> { - let payload = noise_ciphertext.payload(); - - match self.noise_state.read_message(payload)? { - ReadResult::DecryptedData(data) => Ok(data), - _ => Err(NoiseError::IncorrectStateError), - } - } - - /// Creates a new subsession using Noise KKpsk0 pattern. - /// - /// KKpsk0 reuses parent's static X25519 keys (both parties know each other from parent session). - /// PSK is derived from parent's PQ shared secret, preserving quantum resistance. - /// - /// # Arguments - /// * `subsession_index` - Unique index for this subsession (use `next_subsession_index()`) - /// * `is_initiator` - True if this side initiates the subsession handshake - /// - /// # Returns - /// `SubsessionHandshake` ready for KK1/KK2 message exchange - /// - /// # Errors - /// * Returns error if parent handshake not complete - /// * Returns error if PQ shared secret not available - pub fn create_subsession( - &self, - subsession_index: u64, - is_initiator: bool, - ) -> Result { - // Get PQ shared secret - let pq_secret = self.pq_shared_secret(); - - // Derive subsession PSK from parent's PQ shared secret - let subsession_psk = derive_subsession_psk(pq_secret.as_bytes(), subsession_index); - - // Build KKpsk0 handshake - // Pattern: Noise_KKpsk0_25519_ChaChaPoly_SHA256 - // Both parties already know each other's static keys from parent session - let pattern_name = "Noise_KKpsk0_25519_ChaChaPoly_SHA256"; - let params = pattern_name.parse()?; - - 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) - .remote_public_key(&remote_key_bytes) - .psk(0, &subsession_psk); // PSK at position 0 for KKpsk0 - - let handshake_state = if is_initiator { - builder.build_initiator().map_err(LpError::SnowKeyError)? - } else { - builder.build_responder().map_err(LpError::SnowKeyError)? - }; - - Ok(SubsessionHandshake { - index: subsession_index, - noise_state: Mutex::new(NoiseProtocol::new(handshake_state)), - is_initiator, - local_peer: self.local_peer.clone(), - remote_peer: self.remote_peer.clone(), - pq_shared_secret: self.pq_shared_secret.clone(), - subsession_psk, - negotiated_version: self.version, - }) - } -} - -/// Subsession created via Noise KKpsk0 handshake tunneled through parent session. -/// -/// Subsessions provide fresh session keys while inheriting PQ protection from parent's -/// ML-KEM shared secret. After handshake completes, the subsession can be promoted -/// to replace the parent session. -/// -/// # Lifecycle -/// 1. Parent calls `create_subsession()` to get `SubsessionHandshake` -/// 2. Initiator calls `prepare_message()` to get KK1 -/// 3. KK1 sent through parent session (encrypted tunnel) -/// 4. Responder calls `process_message(kk1)` to process KK1 -/// 5. Responder calls `prepare_message()` to get KK2 -/// 6. KK2 sent through parent session -/// 7. Initiator calls `process_message(kk2)` to complete handshake -/// 8. Both call `is_complete()` to verify -#[derive(Debug)] -pub struct SubsessionHandshake { - /// Subsession index (unique per parent session) - pub index: u64, - /// Noise KKpsk0 handshake state - noise_state: Mutex, - /// Is this side the initiator? - is_initiator: bool, - - // Key material inherited from parent session for into_session() conversion - /// 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 { - /// Prepares the next KK handshake message (KK1 or KK2 depending on role/state). - /// - /// # Returns - /// Noise handshake message bytes to send through parent session tunnel. - pub fn prepare_message(&self) -> Result, LpError> { - let mut noise_state = self.noise_state.lock(); - noise_state - .get_bytes_to_send() - .ok_or_else(|| LpError::Internal("Not our turn to send".into()))? - .map_err(LpError::NoiseError) - } - - /// Processes a received KK handshake message (KK1 or KK2). - /// - /// # Arguments - /// * `message` - Noise handshake message received through parent session tunnel. - /// - /// # Returns - /// Any payload embedded in the handshake message (usually empty for KK). - pub fn process_message(&self, message: &[u8]) -> Result, LpError> { - let mut noise_state = self.noise_state.lock(); - let result = noise_state - .read_message(message) - .map_err(LpError::NoiseError)?; - match result { - ReadResult::HandshakeComplete | ReadResult::NoOp => Ok(vec![]), - ReadResult::DecryptedData(data) => Ok(data), - } - } - - /// Checks if the handshake is complete (ready for transport mode). - pub fn is_complete(&self) -> bool { - self.noise_state.lock().is_handshake_finished() - } - - /// Returns whether this side is the initiator. - pub fn is_initiator(&self) -> bool { - self.is_initiator - } - - /// Returns the subsession index. - pub fn subsession_index(&self) -> u64 { - self.index - } - - /// Convert completed subsession handshake into a full LpSession. - /// - /// This consumes the SubsessionHandshake and creates a new LpSession - /// that can be used as a replacement for the parent session. - /// - /// # Arguments - /// * `receiver_index` - New receiver index for the promoted session - /// - /// # Errors - /// Returns error if handshake is not complete - pub fn into_session(self, receiver_index: u32) -> Result { - if !self.is_complete() { - return Err(LpError::Internal( - "Cannot convert incomplete subsession to session".to_string(), - )); - } - - // Extract the noise state (now in transport mode) - let noise_state = self.noise_state.into_inner(); - - // Derive outer AEAD key from the subsession PSK - let outer_key = OuterAeadKey::from_psk(&self.subsession_psk); - - Ok(LpSession { - // noiserm - session_id: receiver_index, - noise_state, - sending_counter: 0, - receiving_counter: ReceivingKeyCounterValidator::new(0), - local_peer: self.local_peer, - remote_peer: self.remote_peer, - outer_aead_key: outer_key, - pq_shared_secret: self.pq_shared_secret, - subsession_counter: 0, - read_only: false, - successor_session_id: None, - version: self.negotiated_version, - }) + /// * `Ok(LpPacket)` containing the decrypted application data payload. + /// * `Err(LpError)` if the session is not in transport mode, decryption fails, or the message is not data. + pub(crate) fn decrypt_packet( + &mut self, + packet: EncryptedLpPacket, + ) -> Result { + decrypt_lp_packet(packet, &mut self.active_transport) } } #[cfg(test)] mod tests { use super::*; - use crate::{SessionsMock, replay::ReplayError, sessions_for_tests}; - use rand::thread_rng; - - // Helper function to generate keypairs for tests - fn generate_x25519_keypair() -> x25519::KeyPair { - x25519::KeyPair::new(&mut thread_rng()) - } + use crate::{ReplayError, SessionsMock}; + use nym_kkt_ciphersuite::{IntoEnumIterator, KEM}; #[test] fn test_session_creation() { - let mut session = sessions_for_tests().0; + for kem in KEM::iter() { + let mut session = SessionsMock::mock_post_handshake(kem).responder; - // Initial counter should be zero - let counter = session.next_counter(); - assert_eq!(counter, 0); + // Initial counter should be zero + let counter = session.next_counter(); + assert_eq!(counter, 0); - // Counter should increment - let counter = session.next_counter(); - assert_eq!(counter, 1); + // Counter should increment + let counter = session.next_counter(); + assert_eq!(counter, 1); + } } - // NOTE: These tests are obsolete after removing optional KEM parameters. - // PSQ now always runs using X25519 keys internally converted to KEM format. - // The new tests at the end of this file (test_psq_*) cover PSQ integration. - /* - #[test] - fn test_session_creation_with_psq_state_initiator() { - // OLD API - REMOVED - } - - #[test] - fn test_session_creation_with_psq_state_responder() { - // OLD API - REMOVED - } - */ - #[test] fn test_replay_protection_sequential() { - let mut session = sessions_for_tests().1; + for kem in KEM::iter() { + let mut session = SessionsMock::mock_post_handshake(kem).responder; - // Sequential counters should be accepted - assert!(session.receiving_counter_quick_check(0).is_ok()); - assert!(session.receiving_counter_mark(0).is_ok()); + // Sequential counters should be accepted + assert!(session.receiving_counter_quick_check(0).is_ok()); + assert!(session.receiving_counter_mark(0).is_ok()); - assert!(session.receiving_counter_quick_check(1).is_ok()); - assert!(session.receiving_counter_mark(1).is_ok()); + assert!(session.receiving_counter_quick_check(1).is_ok()); + assert!(session.receiving_counter_mark(1).is_ok()); - // Duplicates should be rejected - assert!(session.receiving_counter_quick_check(0).is_err()); - let err = session.receiving_counter_mark(0).unwrap_err(); - match err { - LpError::Replay(replay_error) => { - assert!(matches!(replay_error, ReplayError::DuplicateCounter)); + // Duplicates should be rejected + assert!(session.receiving_counter_quick_check(0).is_err()); + let err = session.receiving_counter_mark(0).unwrap_err(); + match err { + LpError::Replay(replay_error) => { + assert!(matches!(replay_error, ReplayError::DuplicateCounter)); + } + _ => panic!("Expected replay error"), } - _ => panic!("Expected replay error"), } } #[test] fn test_replay_protection_out_of_order() { - let mut session = sessions_for_tests().1; + for kem in KEM::iter() { + let mut session = SessionsMock::mock_post_handshake(kem).responder; - // Receive packets in order - assert!(session.receiving_counter_mark(0).is_ok()); - assert!(session.receiving_counter_mark(1).is_ok()); - assert!(session.receiving_counter_mark(2).is_ok()); + // Receive packets in order + assert!(session.receiving_counter_mark(0).is_ok()); + assert!(session.receiving_counter_mark(1).is_ok()); + assert!(session.receiving_counter_mark(2).is_ok()); - // Skip ahead - assert!(session.receiving_counter_mark(10).is_ok()); + // Skip ahead + assert!(session.receiving_counter_mark(10).is_ok()); - // Can still receive out-of-order packets within window - assert!(session.receiving_counter_quick_check(5).is_ok()); - assert!(session.receiving_counter_mark(5).is_ok()); + // Can still receive out-of-order packets within window + assert!(session.receiving_counter_quick_check(5).is_ok()); + assert!(session.receiving_counter_mark(5).is_ok()); - // But duplicates are still rejected - assert!(session.receiving_counter_quick_check(5).is_err()); - assert!(session.receiving_counter_mark(5).is_err()); - } - - #[test] - fn test_packet_stats() { - let mut session = sessions_for_tests().1; - - // Initial stats - let (next, received) = session.current_packet_cnt(); - assert_eq!(next, 0); - assert_eq!(received, 0); - - // After receiving packets - assert!(session.receiving_counter_mark(0).is_ok()); - assert!(session.receiving_counter_mark(1).is_ok()); - - let (next, received) = session.current_packet_cnt(); - assert_eq!(next, 2); - assert_eq!(received, 2); - } - - /* - // These tests remain commented as they rely on the old mock crypto functions - #[test] - fn test_mock_crypto() { - let mut session = create_test_session(true); - let data = [1, 2, 3, 4, 5]; - let mut encrypted = [0; 5]; - let mut decrypted = [0; 5]; - - // Mock encrypt should copy the data - // let encrypted_len = session.encrypt_packet(&data, &mut encrypted).unwrap(); // Removed method - // assert_eq!(encrypted_len, 5); - // assert_eq!(encrypted, data); - - // Mock decrypt should copy the data - // let decrypted_len = session.decrypt_packet(&encrypted, &mut decrypted).unwrap(); // Removed method - // assert_eq!(decrypted_len, 5); - // assert_eq!(decrypted, data); - } - - #[test] - fn test_mock_crypto_buffer_too_small() { - let mut session = create_test_session(true); - let data = [1, 2, 3, 4, 5]; - let mut too_small = [0; 3]; - - // Should fail with buffer too small - // let result = session.encrypt_packet(&data, &mut too_small); // Removed method - // assert!(result.is_err()); - // match result.unwrap_err() { - // LpError::InsufficientBufferSize => {} // Error type might change - // _ => panic!("Expected InsufficientBufferSize error"), - // } - } - */ - - /// Test that X25519 keys are correctly converted to KEM format - #[test] - fn test_x25519_to_kem_conversion() { - use nym_kkt::ciphersuite::EncapsulationKey; - - 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(); - let libcrux_public_key = - libcrux_kem::PublicKey::decode(libcrux_kem::Algorithm::X25519, x25519_public_bytes) - .expect("X25519 public key should convert to libcrux PublicKey"); - - let _kem_key = EncapsulationKey::X25519(libcrux_public_key); - - // Verify we can convert X25519 private key to KEM format - let x25519_private_bytes = initiator_keys.private_key().to_bytes(); - let _libcrux_private_key = - libcrux_kem::PrivateKey::decode(libcrux_kem::Algorithm::X25519, &x25519_private_bytes) - .expect("X25519 private key should convert to libcrux PrivateKey"); - - // Successful conversion is sufficient - actual encapsulation is tested in psk.rs - // (libcrux_kem::PrivateKey is an enum with no len() method, conversion success is enough) - } - - #[test] - fn test_demote_sets_read_only() { - let sessions = SessionsMock::mock_post_handshake(12345); - let mut session = sessions.initiator; - - // Initially not read-only - assert!(!session.is_read_only()); - assert!(session.successor_session_id().is_none()); - - // Demote the session - session.demote(99999); - - // Now read-only with successor - assert!(session.is_read_only()); - assert_eq!(session.successor_session_id(), Some(99999)); - } - - #[test] - fn test_encrypt_fails_after_demotion() { - let receiver_index = 12345; - let sessions = SessionsMock::mock_post_handshake(receiver_index); - let mut initiator_session = sessions.initiator; - - // Encryption works before demotion - let plaintext = b"Hello before demotion"; - assert!(initiator_session.encrypt_data(plaintext).is_ok()); - - // Demote the session - initiator_session.demote(99999); - - // Encryption fails after demotion - let result = initiator_session.encrypt_data(plaintext); - assert!(result.is_err()); - match result.unwrap_err() { - NoiseError::SessionReadOnly => { - // Expected - } - e => panic!("Expected SessionReadOnly error, got: {:?}", e), + // But duplicates are still rejected + assert!(session.receiving_counter_quick_check(5).is_err()); + assert!(session.receiving_counter_mark(5).is_err()); } } #[test] - fn test_decrypt_works_after_demotion() { - // --- Setup Handshake --- - let receiver_index = 12345; - let sessions = SessionsMock::mock_post_handshake(receiver_index); - let mut initiator_session = sessions.initiator; - let mut responder_session = sessions.responder; + fn test_packet_stats() { + for kem in KEM::iter() { + let mut session = SessionsMock::mock_post_handshake(kem).responder; - // Responder encrypts a message - let plaintext = b"Message to demoted initiator"; - let ciphertext = responder_session - .encrypt_data(plaintext) - .expect("Encryption failed"); + // Initial stats + let packet_count = session.current_packet_cnt(); + assert_eq!(packet_count.next, 0); + assert_eq!(packet_count.received, 0); - // Demote the initiator session - initiator_session.demote(99999); - assert!(initiator_session.is_read_only()); + // After receiving packets + assert!(session.receiving_counter_mark(0).is_ok()); + assert!(session.receiving_counter_mark(1).is_ok()); - // Decryption still works on demoted session (drain in-flight) - let decrypted = initiator_session - .decrypt_data(&ciphertext) - .expect("Decryption should work on demoted session"); - assert_eq!(decrypted, plaintext); + let packet_count = session.current_packet_cnt(); + assert_eq!(packet_count.next, 2); + assert_eq!(packet_count.received, 2); + } } } diff --git a/common/nym-lp/src/session_integration/mod.rs b/common/nym-lp/src/session_integration/mod.rs index e72242a507..ae2387a745 100644 --- a/common/nym-lp/src/session_integration/mod.rs +++ b/common/nym-lp/src/session_integration/mod.rs @@ -1,731 +1,394 @@ #[cfg(test)] mod tests { - use crate::codec::{parse_lp_packet, serialize_lp_packet}; - use crate::{ - LpError, SessionsMock, - message::LpMessage, - packet::{LpHeader, LpPacket, TRAILER_LEN}, - session_manager::SessionManager, - }; - use bytes::BytesMut; + use crate::packet::{EncryptedLpPacket, LpMessage}; + use crate::state_machine::{LpAction, LpInput, LpStateBare}; + use crate::{LpError, SessionManager, SessionsMock}; + use nym_kkt_ciphersuite::{IntoEnumIterator, KEM}; - // Function to create a test packet - similar to how it's done in codec.rs tests - fn create_test_packet( - protocol_version: u8, - receiver_idx: u32, - counter: u64, - message: LpMessage, - ) -> LpPacket { - // Create the header - let header = LpHeader { - protocol_version, - reserved: [0u8; 3], // reserved - receiver_idx, - counter, - }; + // helpers to make tests smaller + trait ActionExtract { + fn ciphertext(self) -> EncryptedLpPacket; - // Create the trailer (zeros for now, in a real implementation this might be a MAC) - let trailer = [0u8; TRAILER_LEN]; - - // Create and return the packet directly - LpPacket { - header, - message, - trailer, - } + fn data(self) -> LpMessage; } - /// Tests the complete session flow including: - /// - Creation of sessions through session manager - /// - Packet encoding/decoding with the session - /// - Replay protection across the session - /// - Multiple sessions with unique indices - /// - Session removal and cleanup - #[test] - fn test_full_session_flow() { - // 1. Initialize session manager - let mut session_manager_1 = SessionManager::new(); - let mut session_manager_2 = SessionManager::new(); + impl ActionExtract for LpAction { + fn ciphertext(self) -> EncryptedLpPacket { + if let LpAction::SendPacket(packet) = self { + packet + } else { + panic!("invalid action"); + } + } - let receiver_index = 12345; - let sessions = SessionsMock::mock_post_handshake(receiver_index); - - // 2. Create sessions using the pre-built Noise states - let peer_a_sm = session_manager_1.create_session_state_machine(sessions.initiator); - let peer_b_sm = session_manager_2.create_session_state_machine(sessions.responder); - - // Verify session count - assert_eq!(session_manager_1.session_count(), 1); - assert_eq!(session_manager_2.session_count(), 1); - - // 3. Simulate Data Transfer (Post-Handshake) - println!("Starting data transfer simulation..."); - let plaintext_a_to_b = b"Hello from A!"; - - // A encrypts data - let ciphertext_a_to_b = session_manager_1 - .encrypt_data(peer_a_sm, plaintext_a_to_b) - .expect("A encrypt failed"); - - // A prepares packet - let counter_a = session_manager_1.next_counter(peer_a_sm).unwrap(); - let message_a_to_b = create_test_packet(1, receiver_index, counter_a, ciphertext_a_to_b); - let mut encoded_data_a_to_b = BytesMut::new(); - serialize_lp_packet(&message_a_to_b, &mut encoded_data_a_to_b, None) - .expect("A serialize data failed"); - - // B parses packet and checks replay - let decoded_packet_b = - parse_lp_packet(&encoded_data_a_to_b, None).expect("B parse data failed"); - assert_eq!(decoded_packet_b.header.counter, counter_a); - - // Check replay before decrypting - session_manager_2 - .receiving_counter_quick_check(peer_b_sm, decoded_packet_b.header.counter) - .expect("B data replay check failed (A->B)"); - - // B decrypts data - let decrypted_payload = session_manager_2 - .decrypt_data(peer_b_sm, &decoded_packet_b.message) - .expect("B decrypt failed"); - assert_eq!(decrypted_payload, plaintext_a_to_b); - // Mark counter only after successful decryption - session_manager_2 - .receiving_counter_mark(peer_b_sm, decoded_packet_b.header.counter) - .expect("B mark data counter failed"); - println!( - " A->B: Decrypted successfully: {:?}", - String::from_utf8_lossy(&decrypted_payload) - ); - - // B sends data to A - let plaintext_b_to_a = b"Hello from B!"; - let ciphertext_b_to_a = session_manager_2 - .encrypt_data(peer_b_sm, plaintext_b_to_a) - .expect("B encrypt failed"); - let counter_b = session_manager_2.next_counter(peer_b_sm).unwrap(); - let message_b_to_a = create_test_packet(1, receiver_index, counter_b, ciphertext_b_to_a); - let mut encoded_data_b_to_a = BytesMut::new(); - serialize_lp_packet(&message_b_to_a, &mut encoded_data_b_to_a, None) - .expect("B serialize data failed"); - - // A parses packet and checks replay - let decoded_packet_a = - parse_lp_packet(&encoded_data_b_to_a, None).expect("A parse data failed"); - assert_eq!(decoded_packet_a.header.counter, counter_b); - - // Check replay before decrypting - session_manager_1 - .receiving_counter_quick_check(peer_a_sm, decoded_packet_a.header.counter) - .expect("A data replay check failed (B->A)"); - - // A decrypts data - let decrypted_payload = session_manager_1 - .decrypt_data(peer_a_sm, &decoded_packet_a.message) - .expect("A decrypt failed"); - assert_eq!(decrypted_payload, plaintext_b_to_a); - // Mark counter only after successful decryption - session_manager_1 - .receiving_counter_mark(peer_a_sm, decoded_packet_a.header.counter) - .expect("A mark data counter failed"); - println!( - " B->A: Decrypted successfully: {:?}", - String::from_utf8_lossy(&decrypted_payload) - ); - - println!("Data transfer simulation completed."); - - // 4. Replay Protection Test (Data Packet) - println!("Testing data packet replay protection..."); - // Try to replay the last message from B to A - // Need to re-encode because decode consumes the buffer - let message_b_to_a_replay = create_test_packet( - 1, - receiver_index, - counter_b, - LpMessage::EncryptedData(crate::message::EncryptedDataPayload( - plaintext_b_to_a.to_vec(), - )), // Using plaintext here, but content doesn't matter for replay check - ); - let mut encoded_data_b_to_a_replay = BytesMut::new(); - serialize_lp_packet( - &message_b_to_a_replay, - &mut encoded_data_b_to_a_replay, - None, - ) - .expect("B serialize replay failed"); - - let parsed_replay_packet = - parse_lp_packet(&encoded_data_b_to_a_replay, None).expect("A parse replay failed"); - let replay_result = session_manager_1 - .receiving_counter_quick_check(peer_a_sm, parsed_replay_packet.header.counter); - assert!(replay_result.is_err(), "Data replay should be prevented"); - assert!( - matches!(replay_result.unwrap_err(), LpError::Replay(_)), - "Should be a replay protection error for data packet" - ); - println!("Data packet replay protection test passed."); - - // 5. Test out-of-order packet reception (send counter N+1 before counter N) - println!("Testing out-of-order data packet reception..."); - let counter_a_next = session_manager_1.next_counter(peer_a_sm).unwrap(); // Should be counter_a + 1 - let counter_a_skip = session_manager_1.next_counter(peer_a_sm).unwrap(); // Should be counter_a + 2 - - // Prepare data for counter_a_skip (N+1) - let plaintext_skip = b"Out of order message"; - let ciphertext_skip = session_manager_1 - .encrypt_data(peer_a_sm, plaintext_skip) - .expect("A encrypt skip failed"); - - let message_a_to_b_skip = create_test_packet( - 1, // protocol version - receiver_index, - counter_a_skip, // Send N+1 first - ciphertext_skip, - ); - - // Encode the skip message - let mut encoded_skip = BytesMut::new(); - serialize_lp_packet(&message_a_to_b_skip, &mut encoded_skip, None) - .expect("Failed to serialize skip message"); - - // B parses skip message and checks replay - let decoded_packet_skip = - parse_lp_packet(&encoded_skip, None).expect("B parse skip failed"); - session_manager_2 - .receiving_counter_quick_check(peer_b_sm, decoded_packet_skip.header.counter) - .expect("B replay check skip failed"); - assert_eq!(decoded_packet_skip.header.counter, counter_a_skip); - - // B decrypts skip message - let decrypted_payload = session_manager_2 - .decrypt_data(peer_b_sm, &decoded_packet_skip.message) - .expect("B decrypt skip failed"); - assert_eq!(decrypted_payload, plaintext_skip); - // Mark counter N+1 - session_manager_2 - .receiving_counter_mark(peer_b_sm, decoded_packet_skip.header.counter) - .expect("B mark skip counter failed"); - println!( - " A->B (Counter {}): Decrypted successfully: {:?}", - counter_a_skip, - String::from_utf8_lossy(&decrypted_payload) - ); - - // 6. Now send the skipped counter N message (should still work) - println!("Testing delayed data packet reception..."); - // Prepare data for counter_a_next (N) - let plaintext_delayed = b"Delayed message"; - let ciphertext_delayed = session_manager_1 - .encrypt_data(peer_a_sm, plaintext_delayed) - .expect("A encrypt delayed failed"); - - let message_a_to_b_delayed = create_test_packet( - 1, // protocol version - receiver_index, - counter_a_next, // counter N (delayed packet) - ciphertext_delayed, - ); - - // Encode the delayed message - let mut encoded_delayed = BytesMut::new(); - serialize_lp_packet(&message_a_to_b_delayed, &mut encoded_delayed, None) - .expect("Failed to serialize delayed message"); - - // Make a copy for replay test later - let encoded_delayed_copy = encoded_delayed.clone(); - - // B parses delayed message and checks replay - let decoded_packet_delayed = - parse_lp_packet(&encoded_delayed, None).expect("B parse delayed failed"); - session_manager_2 - .receiving_counter_quick_check(peer_b_sm, decoded_packet_delayed.header.counter) - .expect("B replay check delayed failed"); - assert_eq!(decoded_packet_delayed.header.counter, counter_a_next); - - // B decrypts delayed message - let decrypted_payload = session_manager_2 - .decrypt_data(peer_b_sm, &decoded_packet_delayed.message) - .expect("B decrypt delayed failed"); - assert_eq!(decrypted_payload, plaintext_delayed); - // Mark counter N - session_manager_2 - .receiving_counter_mark(peer_b_sm, decoded_packet_delayed.header.counter) - .expect("B mark delayed counter failed"); - println!( - " A->B (Counter {}): Decrypted successfully: {:?}", - counter_a_next, - String::from_utf8_lossy(&decrypted_payload) - ); - - println!("Delayed data packet reception test passed."); - - // 7. Try to replay message with counter N (should fail) - println!("Testing replay of delayed packet..."); - let parsed_delayed_replay = - parse_lp_packet(&encoded_delayed_copy, None).expect("Parse delayed replay failed"); - let result = session_manager_2 - .receiving_counter_quick_check(peer_b_sm, parsed_delayed_replay.header.counter); - assert!(result.is_err(), "Replay attack should be prevented"); - assert!( - matches!(result, Err(LpError::Replay(_))), - "Should be a replay protection error" - ); - - // 8. Session removal - assert!(session_manager_1.remove_state_machine(receiver_index)); - assert_eq!(session_manager_1.session_count(), 0); - - // Verify the session is gone - let session = session_manager_1.state_machine_exists(receiver_index); - assert!(!session, "Session should be removed"); - - // But the other session still exists - let session = session_manager_2.state_machine_exists(receiver_index); - assert!(session, "Session still exists in the other manager"); + fn data(self) -> LpMessage { + if let LpAction::DeliverData(data) = self { + data + } else { + panic!("invalid action"); + } + } } /// Tests simultaneous bidirectional communication between sessions #[test] fn test_bidirectional_communication() { - // 1. Initialize session manager - let mut session_manager_1 = SessionManager::new(); - let mut session_manager_2 = SessionManager::new(); + for kem in KEM::iter() { + // 1. Initialize session manager + let mut session_manager_1 = SessionManager::new(); + let mut session_manager_2 = SessionManager::new(); + let sessions = SessionsMock::mock_post_handshake(kem); - let receiver_index = 12345; - let sessions = SessionsMock::mock_post_handshake(receiver_index); + // 2. Create sessions using the pre-built Noise states + let peer_a_sm = session_manager_1 + .create_session_state_machine(sessions.initiator) + .unwrap(); + let peer_b_sm = session_manager_2 + .create_session_state_machine(sessions.responder) + .unwrap(); - // 2. Create sessions using the pre-built Noise states - let peer_a_sm = session_manager_1.create_session_state_machine(sessions.initiator); - let peer_b_sm = session_manager_2.create_session_state_machine(sessions.responder); + // 3. Send multiple encrypted messages both ways + const NUM_MESSAGES: u64 = 5; + for i in 0..NUM_MESSAGES { + println!("Bidirectional test: Round {i}"); + // --- A sends to B --- + let plaintext_a = format!("A->B Message {i}").into_bytes(); + let ciphertext_a = session_manager_1 + .send_data(peer_a_sm, LpMessage::new_opaque(plaintext_a.clone())) + .unwrap() + .ciphertext(); - // Counters after handshake - let mut counter_a = 0; // Next counter for A to send - let mut counter_b = 0; // Next counter for B to send + // B parses and checks replay + let decrypted_payload = session_manager_2 + .receive_packet(peer_b_sm, ciphertext_a) + .unwrap() + .unwrap() + .data(); + assert_eq!(decrypted_payload.content, plaintext_a); - // 3. Send multiple encrypted messages both ways - const NUM_MESSAGES: u64 = 5; - for i in 0..NUM_MESSAGES { - println!("Bidirectional test: Round {}", i); - // --- A sends to B --- - let plaintext_a = format!("A->B Message {}", i).into_bytes(); - let ciphertext_a = session_manager_1 - .encrypt_data(peer_a_sm, &plaintext_a) - .expect("A encrypt failed"); - let current_counter_a = counter_a; - counter_a += 1; + // --- B sends to A --- + let plaintext_b = format!("B->A Message {i}").into_bytes(); + let ciphertext_b = session_manager_2 + .send_data(peer_b_sm, LpMessage::new_opaque(plaintext_b.clone())) + .unwrap() + .ciphertext(); - let message_a = create_test_packet(1, receiver_index, current_counter_a, ciphertext_a); - let mut encoded_a = BytesMut::new(); - serialize_lp_packet(&message_a, &mut encoded_a, None).expect("A serialize failed"); + // B parses and checks replay + let decrypted_payload = session_manager_1 + .receive_packet(peer_a_sm, ciphertext_b) + .unwrap() + .unwrap() + .data(); + assert_eq!(decrypted_payload.content, plaintext_b); + } - // B parses and checks replay - let decoded_packet_b = parse_lp_packet(&encoded_a, None).expect("B parse failed"); - session_manager_2 - .receiving_counter_quick_check(peer_b_sm, decoded_packet_b.header.counter) - .expect("B replay check failed (A->B)"); - assert_eq!(decoded_packet_b.header.counter, current_counter_a); - let decrypted_payload = session_manager_2 - .decrypt_data(peer_b_sm, &decoded_packet_b.message) - .expect("B decrypt failed"); - assert_eq!(decrypted_payload, plaintext_a); - session_manager_2 - .receiving_counter_mark(peer_b_sm, current_counter_a) - .expect("B mark counter failed"); + // 5. Verify counter stats + // Note: current_packet_cnt() returns (next_expected_receive_counter, total_received) + let count_a = session_manager_1.current_packet_cnt(peer_a_sm).unwrap(); + let count_b = session_manager_2.current_packet_cnt(peer_b_sm).unwrap(); - // --- B sends to A --- - let plaintext_b = format!("B->A Message {}", i).into_bytes(); - let ciphertext_b = session_manager_2 - .encrypt_data(peer_b_sm, &plaintext_b) - .expect("B encrypt failed"); - let current_counter_b = counter_b; - counter_b += 1; + // Peer A sent handshake(0), handshake(1) + 5 data packets = 7 total. Next send counter = 7. + // Peer A received handshake(0) + 5 data packets = 6 total. Next expected recv counter = 6. + assert_eq!( + count_a.received, NUM_MESSAGES, + "Peer A total received count mismatch" + ); // Received 5 data + assert_eq!( + count_a.next, NUM_MESSAGES, + "Peer A next expected receive counter mismatch" + ); // Expected counter for msg from B - let message_b = create_test_packet(1, receiver_index, current_counter_b, ciphertext_b); - let mut encoded_b = BytesMut::new(); - serialize_lp_packet(&message_b, &mut encoded_b, None).expect("B serialize failed"); + // Peer B sent handshake(0) + 5 data packets = 6 total. Next send counter = 6. + // Peer B received handshake(0), handshake(1) + 5 data packets = 7 total. Next expected recv counter = 7. + assert_eq!( + count_b.received, NUM_MESSAGES, + "Peer B total received count mismatch" + ); // Received 5 data + assert_eq!( + count_b.next, NUM_MESSAGES, + "Peer B next expected receive counter mismatch" + ); // Expected counter for msg from A - // A parses and checks replay - let decoded_packet_a = parse_lp_packet(&encoded_b, None).expect("A parse failed"); - session_manager_1 - .receiving_counter_quick_check(peer_a_sm, decoded_packet_a.header.counter) - .expect("A replay check failed (B->A)"); - assert_eq!(decoded_packet_a.header.counter, current_counter_b); - let decrypted_payload = session_manager_1 - .decrypt_data(peer_a_sm, &decoded_packet_a.message) - .expect("A decrypt failed"); - assert_eq!(decrypted_payload, plaintext_b); - session_manager_1 - .receiving_counter_mark(peer_a_sm, current_counter_b) - .expect("A mark counter failed"); + println!("Bidirectional test completed."); } - - // 5. Verify counter stats - // Note: current_packet_cnt() returns (next_expected_receive_counter, total_received) - let (next_recv_a, total_recv_a) = session_manager_1.current_packet_cnt(peer_a_sm).unwrap(); - let (next_recv_b, total_recv_b) = session_manager_2.current_packet_cnt(peer_b_sm).unwrap(); - - // Peer A sent handshake(0), handshake(1) + 5 data packets = 7 total. Next send counter = 7. - // Peer A received handshake(0) + 5 data packets = 6 total. Next expected recv counter = 6. - assert_eq!( - counter_a, NUM_MESSAGES, - "Peer A final send counter mismatch" - ); - assert_eq!( - total_recv_a, NUM_MESSAGES, - "Peer A total received count mismatch" - ); // Received 5 data - assert_eq!( - next_recv_a, NUM_MESSAGES, - "Peer A next expected receive counter mismatch" - ); // Expected counter for msg from B - - // Peer B sent handshake(0) + 5 data packets = 6 total. Next send counter = 6. - // Peer B received handshake(0), handshake(1) + 5 data packets = 7 total. Next expected recv counter = 7. - assert_eq!( - counter_b, NUM_MESSAGES, - "Peer B final send counter mismatch" - ); - assert_eq!( - total_recv_b, NUM_MESSAGES, - "Peer B total received count mismatch" - ); // Received 5 data - assert_eq!( - next_recv_b, NUM_MESSAGES, - "Peer B next expected receive counter mismatch" - ); // Expected counter for msg from A - - println!("Bidirectional test completed."); } /// Tests error handling in session flow #[test] fn test_session_error_handling() { - // 1. Initialize session manager - let mut session_manager = SessionManager::new(); + for kem in KEM::iter() { + // 1. Initialize session manager + let mut session_manager = SessionManager::new(); - let receiver_index = 123; - let session1 = SessionsMock::mock_post_handshake(receiver_index).initiator; - let session2 = SessionsMock::mock_post_handshake(124).initiator; + let sessions = SessionsMock::mock_post_handshake(kem); + let session_id = sessions.initiator.receiver_index(); - // 2. Create a session (using real noise state) - let _session = session_manager.create_session_state_machine(session1); + let non_existent = 123; + // sanity check in case of the 1 in 2^256 + assert_ne!(session_id, non_existent); - // 3. Try to get a non-existent session - let result = session_manager.state_machine_exists(999); - assert!(!result, "Non-existent session should return None"); + let session1 = sessions.initiator; + let session2 = sessions.responder; - // 4. Try to remove a non-existent session - let result = session_manager.remove_state_machine(999); - assert!( - !result, - "Remove session should not remove a non-existent session" - ); + // 2. Create a session (using real noise state) + let _session = session_manager.create_session_state_machine(session1); - // 5. Create and immediately remove a session - let _temp_session = session_manager.create_session_state_machine(session2); + // 3. Try to get a non-existent session + let result = session_manager.state_machine_exists(non_existent); + assert!(!result, "Non-existent session should return None"); - assert!( - session_manager.remove_state_machine(124), - "Should remove the session" - ); + // 4. Try to remove a non-existent session + let result = session_manager.remove_state_machine(non_existent); + assert!( + !result, + "Remove session should not remove a non-existent session" + ); - // 6. Create a codec and test error cases - // let mut codec = LPCodec::new(session); + // 5. Create and immediately remove a session + let _temp_session = session_manager.create_session_state_machine(session2); - // 7. Create an invalid message type packet - let mut buf = BytesMut::new(); - - // Add header - buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved - buf.extend_from_slice(&receiver_index.to_le_bytes()); // Sender index - buf.extend_from_slice(&0u64.to_le_bytes()); // Counter - - // Add invalid message type - buf.extend_from_slice(&0xFFFFu16.to_le_bytes()); - - // Add some dummy data - buf.extend_from_slice(&[0u8; 80]); - - // Add trailer - buf.extend_from_slice(&[0u8; TRAILER_LEN]); - - // Try to parse the invalid message type - let result = parse_lp_packet(&buf, None); - assert!(result.is_err(), "Decoding invalid message type should fail"); - - // Add assertion for the specific error type - assert!(matches!( - result.unwrap_err(), - LpError::InvalidMessageType(0xFFFF) - )); - - // 8. Test partial packet decoding - let partial_packet = &buf[0..10]; // Too short to be a valid packet - let partial_bytes = BytesMut::from(partial_packet); - - let result = parse_lp_packet(&partial_bytes, None); - assert!(result.is_err(), "Parsing partial packet should fail"); - assert!(matches!( - result.unwrap_err(), - LpError::InsufficientBufferSize - )); + assert!( + session_manager.remove_state_machine(session_id), + "Should remove the session" + ); + } } - // Remove unused imports if SessionManager methods are no longer direct dependencies - // use crate::noise_protocol::{create_noise_state, create_noise_state_responder}; - use crate::state_machine::LpData; - use crate::state_machine::{LpAction, LpInput, LpStateBare}; - // Use Bytes for SendData input - - // Keep helper function for creating test packets if needed, - // but LpAction::SendPacket should provide the packets now. - // fn create_test_packet(...) -> LpPacket { ... } /// Tests the complete session flow using ONLY the process_input interface: /// - Creation of sessions through session manager - /// - Handshake driven by StartHandshake, ReceivePacket inputs /// - Data transfer driven by SendData, ReceivePacket inputs /// - Actions like SendPacket, DeliverData handled from output /// - Implicit replay protection via state machine logic /// - Closing driven by Close input #[test] - fn test_full_session_flow_with_process_input() { + fn test_full_session_flow() { // 1. Initialize session managers let mut session_manager_1 = SessionManager::new(); let mut session_manager_2 = SessionManager::new(); - let receiver_index = 12345; - let sessions = SessionsMock::mock_post_handshake(receiver_index); + for kem in KEM::iter() { + let sessions = SessionsMock::mock_post_handshake(kem); + let session_id = sessions.responder.receiver_index(); - // 2. Create sessions state machines - session_manager_1.create_session_state_machine(sessions.initiator); - session_manager_2.create_session_state_machine(sessions.responder); + // 2. Create sessions state machines + session_manager_1 + .create_session_state_machine(sessions.initiator) + .unwrap(); + session_manager_2 + .create_session_state_machine(sessions.responder) + .unwrap(); - assert_eq!(session_manager_1.session_count(), 1); - assert_eq!(session_manager_2.session_count(), 1); - assert!(session_manager_1.state_machine_exists(receiver_index)); - assert!(session_manager_2.state_machine_exists(receiver_index)); + assert_eq!(session_manager_1.session_count(), 1); + assert_eq!(session_manager_2.session_count(), 1); + assert!(session_manager_1.state_machine_exists(session_id)); + assert!(session_manager_2.state_machine_exists(session_id)); - // Verify initial states are Transport - assert_eq!( - session_manager_1.get_state(receiver_index).unwrap(), - LpStateBare::Transport - ); - assert_eq!( - session_manager_2.get_state(receiver_index).unwrap(), - LpStateBare::Transport - ); - - // --- 3. Simulate Data Transfer via process_input --- - println!("Starting data transfer simulation 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.clone())) - .expect("A SendData should produce action") - .expect("A SendData failed"); - - let data_packet_a = if let LpAction::SendPacket(packet) = action_a_send { - packet - } else { - panic!("A SendData did not produce SendPacket"); - }; - - // Simulate network - let mut buf_data_a = BytesMut::new(); - serialize_lp_packet(&data_packet_a, &mut buf_data_a, None).unwrap(); - let parsed_data_a = parse_lp_packet(&buf_data_a, None).unwrap(); - - // B receives - println!(" B receives from A"); - let action_b_recv = session_manager_2 - .process_input(receiver_index, LpInput::ReceivePacket(parsed_data_a)) - .expect("B ReceivePacket (data) should produce action") - .expect("B ReceivePacket (data) failed"); - - if let LpAction::DeliverData(data) = action_b_recv { - assert_eq!(data, plaintext_a_to_b, "Decrypted data mismatch A->B"); - println!( - " B successfully decrypted: {:?}", - String::from_utf8_lossy(&data.content) + // Verify initial states are Transport + assert_eq!( + session_manager_1.get_state(session_id).unwrap(), + LpStateBare::Transport ); - } else { - panic!("B ReceivePacket did not produce DeliverData"); - } - - // --- 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.clone())) - .expect("B SendData should produce action") - .expect("B SendData failed"); - - let data_packet_b = if let LpAction::SendPacket(packet) = action_b_send { - packet - } else { - panic!("B SendData did not produce SendPacket"); - }; - // Keep a copy for replay test - let data_packet_b_replay = data_packet_b.clone(); - - // Simulate network - let mut buf_data_b = BytesMut::new(); - serialize_lp_packet(&data_packet_b, &mut buf_data_b, None).unwrap(); - let parsed_data_b = parse_lp_packet(&buf_data_b, None).unwrap(); - - // A receives - println!(" A receives from B"); - let action_a_recv = session_manager_1 - .process_input(receiver_index, LpInput::ReceivePacket(parsed_data_b)) - .expect("A ReceivePacket (data) should produce action") - .expect("A ReceivePacket (data) failed"); - - if let LpAction::DeliverData(data) = action_a_recv { - assert_eq!(data, plaintext_b_to_a, "Decrypted data mismatch B->A"); - println!( - " A successfully decrypted: {:?}", - String::from_utf8_lossy(&data.content) + assert_eq!( + session_manager_2.get_state(session_id).unwrap(), + LpStateBare::Transport ); - } else { - panic!("A ReceivePacket did not produce DeliverData"); + + // --- 3. Simulate Data Transfer via process_input --- + println!("Starting data transfer simulation via process_input..."); + let plaintext_a_to_b = + LpMessage::new_opaque(b"Hello from A via process_input!".to_vec()); + let plaintext_b_to_a = + LpMessage::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(session_id, LpInput::SendData(plaintext_a_to_b.clone())) + .expect("A SendData should produce action") + .expect("A SendData failed"); + + let data_packet_a = action_a_send.ciphertext(); + + // B receives + println!(" B receives from A"); + let action_b_recv = session_manager_2 + .process_input(session_id, LpInput::ReceivePacket(data_packet_a)) + .expect("B ReceivePacket (data) should produce action") + .expect("B ReceivePacket (data) failed"); + + if let LpAction::DeliverData(data) = action_b_recv { + assert_eq!(data, plaintext_a_to_b, "Decrypted data mismatch A->B"); + println!( + " B successfully decrypted: {:?}", + String::from_utf8_lossy(&data.content) + ); + } else { + panic!("B ReceivePacket did not produce DeliverData"); + } + + // --- B sends to A --- + println!(" B sends to A"); + let action_b_send = session_manager_2 + .process_input(session_id, LpInput::SendData(plaintext_b_to_a.clone())) + .expect("B SendData should produce action") + .expect("B SendData failed"); + + let data_packet_b = action_b_send.ciphertext(); + + // Keep a copy for replay test + let data_packet_b_replay = data_packet_b.clone(); + + // A receives + println!(" A receives from B"); + let action_a_recv = session_manager_1 + .process_input(session_id, LpInput::ReceivePacket(data_packet_b)) + .expect("A ReceivePacket (data) should produce action") + .expect("A ReceivePacket (data) failed"); + + if let LpAction::DeliverData(data) = action_a_recv { + assert_eq!(data, plaintext_b_to_a, "Decrypted data mismatch B->A"); + println!( + " A successfully decrypted: {:?}", + String::from_utf8_lossy(&data.content) + ); + } else { + panic!("A ReceivePacket did not produce DeliverData"); + } + println!("Data transfer simulation completed."); + + // --- 4. Replay Protection Test --- + println!("Testing data packet replay protection via process_input..."); + let replay_result = session_manager_1 + .process_input(session_id, LpInput::ReceivePacket(data_packet_b_replay)); // Use cloned packet + + assert!(replay_result.is_err(), "Replay should produce Err(...)"); + let error = replay_result.err().unwrap(); + assert!( + matches!(error, LpError::Replay(_)), + "Expected Replay error, got {:?}", + error + ); + println!("Data packet replay protection test passed."); + + // --- 5. Out-of-Order Test --- + println!("Testing out-of-order reception via process_input..."); + + // A prepares N+1 then N + let data_n_plus_1 = LpMessage::new_opaque(b"Message N+1".to_vec()); + let data_n = LpMessage::new_opaque(b"Message N".to_vec()); + + let action_send_n1 = session_manager_1 + .process_input(session_id, LpInput::SendData(data_n_plus_1.clone())) + .unwrap() + .unwrap(); + let packet_n1 = match action_send_n1 { + LpAction::SendPacket(p) => p, + _ => panic!("Expected SendPacket"), + }; + + let action_send_n = session_manager_1 + .process_input(session_id, LpInput::SendData(data_n.clone())) + .unwrap() + .unwrap(); + let packet_n = match action_send_n { + LpAction::SendPacket(p) => p, + _ => panic!("Expected SendPacket"), + }; + let packet_n_replay = packet_n.clone(); // For replay test + + // B receives N+1 first + println!(" B receives N+1"); + let action_recv_n1 = session_manager_2 + .process_input(session_id, LpInput::ReceivePacket(packet_n1)) + .unwrap() + .unwrap(); + match action_recv_n1 { + LpAction::DeliverData(d) => assert_eq!(d, data_n_plus_1, "Data N+1 mismatch"), + _ => panic!("Expected DeliverData for N+1"), + } + + // B receives N second (should work) + println!(" B receives N"); + let action_recv_n = session_manager_2 + .process_input(session_id, LpInput::ReceivePacket(packet_n)) + .unwrap() + .unwrap(); + match action_recv_n { + LpAction::DeliverData(d) => assert_eq!(d, data_n, "Data N mismatch"), + _ => panic!("Expected DeliverData for N"), + } + + // B tries to replay N (should fail) + println!(" B tries to replay N"); + let replay_n_result = session_manager_2 + .process_input(session_id, LpInput::ReceivePacket(packet_n_replay)); + assert!(replay_n_result.is_err(), "Replay N should produce Err"); + assert!( + matches!(replay_n_result.err().unwrap(), LpError::Replay(_)), + "Expected Replay error for N" + ); + println!("Out-of-order test passed."); + + // --- 6. Close Test --- + println!("Testing close via process_input..."); + + // A closes + let action_a_close = session_manager_1 + .process_input(session_id, LpInput::Close) + .expect("A Close should produce action") + .expect("A Close failed"); + assert!(matches!(action_a_close, LpAction::ConnectionClosed)); + assert_eq!( + session_manager_1.get_state(session_id).unwrap(), + LpStateBare::Closed + ); + + // Further actions on A fail + let send_after_close_a = session_manager_1.process_input( + session_id, + LpInput::SendData(LpMessage::new_opaque(b"fail".to_vec())), + ); + assert!(send_after_close_a.is_err()); + assert!(matches!( + send_after_close_a.err().unwrap(), + LpError::LpSessionClosed + )); + + // B closes + let action_b_close = session_manager_2 + .process_input(session_id, LpInput::Close) + .expect("B Close should produce action") + .expect("B Close failed"); + assert!(matches!(action_b_close, LpAction::ConnectionClosed)); + assert_eq!( + session_manager_2.get_state(session_id).unwrap(), + LpStateBare::Closed + ); + + // Further actions on B fail + let send_after_close_b = session_manager_2.process_input( + session_id, + LpInput::SendData(LpMessage::new_opaque(b"fail".to_vec())), + ); + assert!(send_after_close_b.is_err()); + assert!(matches!( + send_after_close_b.err().unwrap(), + LpError::LpSessionClosed + )); + println!("Close test passed."); + + // --- 7. Session Removal --- + assert!(session_manager_1.remove_state_machine(session_id)); + assert_eq!(session_manager_1.session_count(), 0); + assert!(!session_manager_1.state_machine_exists(session_id)); + + // B's session manager still has it until removed + assert!(session_manager_2.state_machine_exists(session_id)); + assert!(session_manager_2.remove_state_machine(session_id)); + assert_eq!(session_manager_2.session_count(), 0); + assert!(!session_manager_2.state_machine_exists(session_id)); + println!("Session removal test passed."); } - println!("Data transfer simulation completed."); - - // --- 4. Replay Protection Test --- - println!("Testing data packet replay protection via process_input..."); - let replay_result = session_manager_1 - .process_input(receiver_index, LpInput::ReceivePacket(data_packet_b_replay)); // Use cloned packet - - assert!(replay_result.is_err(), "Replay should produce Err(...)"); - let error = replay_result.err().unwrap(); - assert!( - matches!(error, LpError::Replay(_)), - "Expected Replay error, got {:?}", - error - ); - println!("Data packet replay protection test passed."); - - // --- 5. Out-of-Order Test --- - println!("Testing out-of-order reception via process_input..."); - - // A prepares N+1 then 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.clone())) - .unwrap() - .unwrap(); - let packet_n1 = match action_send_n1 { - LpAction::SendPacket(p) => p, - _ => panic!("Expected SendPacket"), - }; - - let action_send_n = session_manager_1 - .process_input(receiver_index, LpInput::SendData(data_n.clone())) - .unwrap() - .unwrap(); - let packet_n = match action_send_n { - LpAction::SendPacket(p) => p, - _ => panic!("Expected SendPacket"), - }; - let packet_n_replay = packet_n.clone(); // For replay test - - // B receives N+1 first - println!(" B receives N+1"); - let action_recv_n1 = session_manager_2 - .process_input(receiver_index, LpInput::ReceivePacket(packet_n1)) - .unwrap() - .unwrap(); - match action_recv_n1 { - LpAction::DeliverData(d) => assert_eq!(d, data_n_plus_1, "Data N+1 mismatch"), - _ => panic!("Expected DeliverData for N+1"), - } - - // B receives N second (should work) - println!(" B receives N"); - let action_recv_n = session_manager_2 - .process_input(receiver_index, LpInput::ReceivePacket(packet_n)) - .unwrap() - .unwrap(); - match action_recv_n { - LpAction::DeliverData(d) => assert_eq!(d, data_n, "Data N mismatch"), - _ => panic!("Expected DeliverData for N"), - } - - // B tries to replay N (should fail) - println!(" B tries to replay N"); - let replay_n_result = session_manager_2 - .process_input(receiver_index, LpInput::ReceivePacket(packet_n_replay)); - assert!(replay_n_result.is_err(), "Replay N should produce Err"); - assert!( - matches!(replay_n_result.err().unwrap(), LpError::Replay(_)), - "Expected Replay error for N" - ); - println!("Out-of-order test passed."); - - // --- 6. Close Test --- - println!("Testing close via process_input..."); - - // A closes - let action_a_close = session_manager_1 - .process_input(receiver_index, LpInput::Close) - .expect("A Close should produce action") - .expect("A Close failed"); - assert!(matches!(action_a_close, LpAction::ConnectionClosed)); - assert_eq!( - session_manager_1.get_state(receiver_index).unwrap(), - LpStateBare::Closed - ); - - // Further actions on A fail - 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(), - LpError::LpSessionClosed - )); - - // B closes - let action_b_close = session_manager_2 - .process_input(receiver_index, LpInput::Close) - .expect("B Close should produce action") - .expect("B Close failed"); - assert!(matches!(action_b_close, LpAction::ConnectionClosed)); - assert_eq!( - session_manager_2.get_state(receiver_index).unwrap(), - LpStateBare::Closed - ); - - // Further actions on B fail - 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(), - LpError::LpSessionClosed - )); - println!("Close test passed."); - - // --- 7. Session Removal --- - assert!(session_manager_1.remove_state_machine(receiver_index)); - assert_eq!(session_manager_1.session_count(), 0); - assert!(!session_manager_1.state_machine_exists(receiver_index)); - - // B's session manager still has it until removed - assert!(session_manager_2.state_machine_exists(receiver_index)); - assert!(session_manager_2.remove_state_machine(receiver_index)); - assert_eq!(session_manager_2.session_count(), 0); - assert!(!session_manager_2.state_machine_exists(receiver_index)); - println!("Session removal test passed."); } // ... other tests ... } diff --git a/common/nym-lp/src/session_manager.rs b/common/nym-lp/src/session_manager.rs index 9ddcb91d3f..30e1cc7921 100644 --- a/common/nym-lp/src/session_manager.rs +++ b/common/nym-lp/src/session_manager.rs @@ -6,17 +6,20 @@ //! This module implements session lifecycle management functionality, handling //! creation, retrieval, and storage of sessions. +use crate::packet::{EncryptedLpPacket, LpMessage}; +use crate::peer_config::LpReceiverIndex; use crate::state_machine::{LpAction, LpInput, LpStateBare}; -use crate::{LpError, LpMessage, LpSession, LpStateMachine}; +use crate::{LpError, LpSession, LpStateMachine}; use std::collections::HashMap; +pub use crate::replay::validator::PacketCount; + /// Manages the lifecycle of Lewes Protocol sessions. /// -/// The SessionManager is responsible for creating, storing, and retrieving sessions, -/// ensuring proper thread-safety for concurrent access. +/// The SessionManager is responsible for creating, storing, and retrieving sessions pub struct SessionManager { /// Manages state machines directly, keyed by lp_id - state_machines: HashMap, + state_machines: HashMap, } impl Default for SessionManager { @@ -35,62 +38,47 @@ impl SessionManager { pub fn process_input( &mut self, - lp_id: u32, + lp_id: LpReceiverIndex, input: LpInput, ) -> Result, LpError> { self.with_state_machine_mut(lp_id, |sm| sm.process_input(input).transpose())? } - pub fn closed(&self, lp_id: u32) -> Result { + pub fn send_data( + &mut self, + lp_id: LpReceiverIndex, + data: LpMessage, + ) -> Result { + self.process_input(lp_id, LpInput::SendData(data))? + .ok_or(LpError::NotInTransport) + } + + pub fn receive_packet( + &mut self, + lp_id: LpReceiverIndex, + packet: EncryptedLpPacket, + ) -> Result, LpError> { + self.process_input(lp_id, LpInput::ReceivePacket(packet)) + } + + pub fn closed(&self, lp_id: LpReceiverIndex) -> Result { Ok(self.get_state(lp_id)? == LpStateBare::Closed) } - pub fn transport(&self, lp_id: u32) -> Result { + pub fn transport(&self, lp_id: LpReceiverIndex) -> Result { Ok(self.get_state(lp_id)? == LpStateBare::Transport) } #[cfg(test)] - fn get_state_machine_id(&self, lp_id: u32) -> Result { - self.with_state_machine(lp_id, |sm| sm.id())? + fn get_state_machine_id(&self, lp_id: LpReceiverIndex) -> Result { + self.with_state_machine(lp_id, |sm| sm.receiver_index())? } - pub fn get_state(&self, lp_id: u32) -> Result { + pub fn get_state(&self, lp_id: LpReceiverIndex) -> Result { self.with_state_machine(lp_id, |sm| Ok(sm.bare_state()))? } - pub fn receiving_counter_quick_check(&self, lp_id: u32, counter: u64) -> Result<(), LpError> { - self.with_state_machine(lp_id, |sm| { - sm.session()?.receiving_counter_quick_check(counter) - })? - } - - pub fn receiving_counter_mark(&mut self, lp_id: u32, counter: u64) -> Result<(), LpError> { - self.with_state_machine_mut(lp_id, |sm| { - sm.session_mut()?.receiving_counter_mark(counter) - })? - } - - pub fn next_counter(&mut self, lp_id: u32) -> Result { - self.with_state_machine_mut(lp_id, |sm| Ok(sm.session_mut()?.next_counter()))? - } - - pub fn decrypt_data(&mut self, lp_id: u32, message: &LpMessage) -> Result, LpError> { - self.with_state_machine_mut(lp_id, |sm| { - sm.session_mut()? - .decrypt_data(message) - .map_err(LpError::NoiseError) - })? - } - - pub fn encrypt_data(&mut self, lp_id: u32, message: &[u8]) -> Result { - self.with_state_machine_mut(lp_id, |sm| { - sm.session_mut()? - .encrypt_data(message) - .map_err(LpError::NoiseError) - })? - } - - pub fn current_packet_cnt(&self, lp_id: u32) -> Result<(u64, u64), LpError> { + pub fn current_packet_cnt(&self, lp_id: LpReceiverIndex) -> Result { self.with_state_machine(lp_id, |sm| Ok(sm.session()?.current_packet_cnt()))? } @@ -98,43 +86,54 @@ impl SessionManager { self.state_machines.len() } - pub fn state_machine_exists(&self, lp_id: u32) -> bool { + pub fn state_machine_exists(&self, lp_id: LpReceiverIndex) -> bool { self.state_machines.contains_key(&lp_id) } - pub fn with_state_machine(&self, lp_id: u32, f: F) -> Result + pub fn with_state_machine(&self, lp_id: LpReceiverIndex, f: F) -> Result where F: FnOnce(&LpStateMachine) -> R, { if let Some(sm) = self.state_machines.get(&lp_id) { Ok(f(sm)) } else { - Err(LpError::StateMachineNotFound { lp_id }) + Err(LpError::StateMachineNotFound(lp_id)) } - // self.state_machines.get(&lp_id).map(|sm_ref| f(&*sm_ref)) // Lock held only during closure execution } // For mutable access (like running process_input) - pub fn with_state_machine_mut(&mut self, lp_id: u32, f: F) -> Result + pub fn with_state_machine_mut( + &mut self, + lp_id: LpReceiverIndex, + f: F, + ) -> Result where F: FnOnce(&mut LpStateMachine) -> R, // Closure takes mutable ref { if let Some(sm) = self.state_machines.get_mut(&lp_id) { Ok(f(sm)) } else { - Err(LpError::StateMachineNotFound { lp_id }) + Err(LpError::StateMachineNotFound(lp_id)) } } - pub fn create_session_state_machine(&mut self, lp_session: LpSession) -> u32 { - let receiver_index = lp_session.id(); + pub fn create_session_state_machine( + &mut self, + lp_session: LpSession, + ) -> Result { + let session_id = lp_session.receiver_index(); + + if self.state_machines.contains_key(&session_id) { + return Err(LpError::DuplicateSessionId(session_id)); + } + let sm = LpStateMachine::new(lp_session); - self.state_machines.insert(receiver_index, sm); - receiver_index + self.state_machines.insert(session_id, sm); + Ok(session_id) } /// Method to remove a state machine - pub fn remove_state_machine(&mut self, lp_id: u32) -> bool { + pub fn remove_state_machine(&mut self, lp_id: LpReceiverIndex) -> bool { let removed = self.state_machines.remove(&lp_id); removed.is_some() @@ -145,21 +144,21 @@ impl SessionManager { mod tests { use super::*; use crate::{SessionsMock, mock_session_for_test}; + use nym_kkt_ciphersuite::{IntoEnumIterator, KEM}; #[test] fn test_session_manager_get() { let mut manager = SessionManager::new(); - let local_session = mock_session_for_test(); - let id = local_session.id(); + let id = local_session.receiver_index(); - let sm_1_id = manager.create_session_state_machine(local_session); + let sm_1_id = manager.create_session_state_machine(local_session).unwrap(); assert_eq!(sm_1_id, id); let retrieved = manager.state_machine_exists(id); assert!(retrieved); - let not_found = manager.state_machine_exists(99); + let not_found = manager.state_machine_exists(123); assert!(!not_found); } @@ -167,8 +166,7 @@ mod tests { fn test_session_manager_remove() { let mut manager = SessionManager::new(); let local_session = mock_session_for_test(); - - let sm_1_id = manager.create_session_state_machine(local_session); + let sm_1_id = manager.create_session_state_machine(local_session).unwrap(); let removed = manager.remove_state_machine(sm_1_id); assert!(removed); @@ -180,24 +178,26 @@ mod tests { #[test] fn test_multiple_sessions() { - let mut manager = SessionManager::new(); - let session1 = SessionsMock::mock_post_handshake(123).initiator; - let session2 = SessionsMock::mock_post_handshake(124).initiator; - let session3 = SessionsMock::mock_post_handshake(125).initiator; + for kem in KEM::iter() { + let mut manager = SessionManager::new(); + let session1 = SessionsMock::mock_seeded_post_handshake(123, kem).initiator; + let session2 = SessionsMock::mock_seeded_post_handshake(124, kem).initiator; + let session3 = SessionsMock::mock_seeded_post_handshake(125, kem).initiator; - let sm_1 = manager.create_session_state_machine(session1); - let sm_2 = manager.create_session_state_machine(session2); - let sm_3 = manager.create_session_state_machine(session3); + let sm_1 = manager.create_session_state_machine(session1).unwrap(); + let sm_2 = manager.create_session_state_machine(session2).unwrap(); + let sm_3 = manager.create_session_state_machine(session3).unwrap(); - assert_eq!(manager.session_count(), 3); + assert_eq!(manager.session_count(), 3); - let retrieved1 = manager.get_state_machine_id(sm_1).unwrap(); - let retrieved2 = manager.get_state_machine_id(sm_2).unwrap(); - let retrieved3 = manager.get_state_machine_id(sm_3).unwrap(); + let retrieved1 = manager.get_state_machine_id(sm_1).unwrap(); + let retrieved2 = manager.get_state_machine_id(sm_2).unwrap(); + let retrieved3 = manager.get_state_machine_id(sm_3).unwrap(); - assert_eq!(retrieved1, sm_1); - assert_eq!(retrieved2, sm_2); - assert_eq!(retrieved3, sm_3); + assert_eq!(retrieved1, sm_1); + assert_eq!(retrieved2, sm_2); + assert_eq!(retrieved3, sm_3); + } } #[test] @@ -206,7 +206,7 @@ mod tests { let sesion = mock_session_for_test(); - let sm = manager.create_session_state_machine(sesion); + let sm = manager.create_session_state_machine(sesion).unwrap(); assert_eq!(manager.session_count(), 1); let retrieved = manager.get_state_machine_id(sm); diff --git a/common/nym-lp/src/state_machine.rs b/common/nym-lp/src/state_machine.rs index 7892fbf017..442bff94b7 100644 --- a/common/nym-lp/src/state_machine.rs +++ b/common/nym-lp/src/state_machine.rs @@ -2,48 +2,31 @@ // SPDX-License-Identifier: Apache-2.0 //! Lewes Protocol State Machine for managing connection lifecycle. -//! -//! LP protocol flow (KKT → PSQ → Noise): -//! 1. KKTExchange: Client requests gateway's KEM public key (signed for MITM protection) -//! 2. Handshaking: Noise XKpsk3 with PSQ-derived PSK embedded in handshake messages -//! - PSQ ciphertext piggybacked on ClientHello (no extra round-trip) -//! - PSK = Blake3(ECDH || PSQ_secret || salt) provides hybrid classical+PQ security -//! 3. Transport: ChaCha20-Poly1305 authenticated encryption with derived keys -//! //! State machine ensures protocol steps execute in correct order. Invalid transitions //! return LpError, preventing protocol violations. -use crate::{ - LpError, - message::{LpMessage, SubsessionKK1Data, SubsessionKK2Data, SubsessionReadyData}, - noise_protocol::NoiseError, - packet::LpPacket, - session::{LpSession, SubsessionHandshake}, -}; -use bytes::{Buf, Bytes}; -use num_enum::{IntoPrimitive, TryFromPrimitive}; +use crate::packet::EncryptedLpPacket; +use crate::packet::message::LpMessage; +use crate::peer_config::LpReceiverIndex; +use crate::session::SessionId; +use crate::{LpError, session::LpSession}; use std::mem; -use tracing::debug; + +#[derive(Debug)] +pub struct LpTransportState { + /// The underlying session in the transport state + session: Box, +} /// Represents the possible states of the Lewes Protocol connection. #[derive(Debug, Default)] pub enum LpState { /// Handshake complete, ready for data transport. - Transport { session: Box }, - - /// Performing subsession KK handshake while parent remains active. - /// Parent can still send/receive; subsession messages tunneled through parent. - SubsessionHandshaking { - session: Box, - subsession: Box, - }, - - /// Parent session demoted after subsession promoted. - /// Can only receive (drain in-flight), cannot send. - ReadOnlyTransport { session: Box }, + Transport(LpTransportState), /// An error occurred, or the connection was intentionally closed. Closed { reason: String }, + /// Processing an input event. #[default] Processing, @@ -52,8 +35,6 @@ pub enum LpState { #[derive(Debug, Clone, PartialEq, Eq)] pub enum LpStateBare { Transport, - SubsessionHandshaking, - ReadOnlyTransport, Closed, Processing, } @@ -62,8 +43,6 @@ impl From<&LpState> for LpStateBare { fn from(state: &LpState) -> Self { match state { LpState::Transport { .. } => LpStateBare::Transport, - LpState::SubsessionHandshaking { .. } => LpStateBare::SubsessionHandshaking, - LpState::ReadOnlyTransport { .. } => LpStateBare::ReadOnlyTransport, LpState::Closed { .. } => LpStateBare::Closed, LpState::Processing => LpStateBare::Processing, } @@ -74,109 +53,27 @@ impl From<&LpState> for LpStateBare { #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum LpInput { - /// Received an LP Packet from the network. - ReceivePacket(LpPacket), + /// Received an encrypted LP Packet from the network. + ReceivePacket(EncryptedLpPacket), + /// Application wants to send data (only valid in Transport state). - SendData(LpData), + SendData(LpMessage), + /// Close the connection. Close, - /// Initiate a subsession handshake (only valid in Transport state). - /// Creates SubsessionHandshake and sends KK1 message. - InitiateSubsession, } /// Represents actions the state machine requests the environment to perform. #[derive(Debug)] pub enum LpAction { /// Send an LP Packet over the network. - SendPacket(LpPacket), + SendPacket(EncryptedLpPacket), + /// Deliver decrypted application data received from the peer. - DeliverData(LpData), + DeliverData(LpMessage), + /// Inform the environment that the connection is closed. ConnectionClosed, - /// Subsession KK handshake initiated by this side. - /// Contains the KK1 packet to send and the subsession index for tracking. - SubsessionInitiated { - packet: LpPacket, - subsession_index: u64, - }, - /// Subsession handshake complete, ready for promotion. - /// Contains the packet to send (Some for initiator with SubsessionReady, None for responder), - /// the completed SubsessionHandshake for into_session(), and the new receiver_index. - SubsessionComplete { - packet: Option, - subsession: Box, - new_receiver_index: u32, - }, -} - -/// 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. @@ -191,9 +88,7 @@ impl LpStateMachine { pub fn session_mut(&mut self) -> Result<&mut LpSession, LpError> { match &mut self.state { - LpState::Transport { session } - | LpState::SubsessionHandshaking { session, .. } - | LpState::ReadOnlyTransport { session } => Ok(session), + LpState::Transport(transport) => Ok(&mut transport.session), LpState::Closed { .. } => Err(LpError::LpSessionClosed), LpState::Processing => Err(LpError::LpSessionProcessing), } @@ -201,9 +96,7 @@ impl LpStateMachine { pub fn session(&self) -> Result<&LpSession, LpError> { match &self.state { - LpState::Transport { session } - | LpState::SubsessionHandshaking { session, .. } - | LpState::ReadOnlyTransport { session } => Ok(session), + LpState::Transport(transport) => Ok(&transport.session), LpState::Closed { .. } => Err(LpError::LpSessionClosed), LpState::Processing => Err(LpError::LpSessionProcessing), } @@ -214,50 +107,93 @@ impl LpStateMachine { /// ownership of the session to the caller. pub fn into_session(self) -> Result { match self.state { - LpState::Transport { session } - | LpState::SubsessionHandshaking { session, .. } - | LpState::ReadOnlyTransport { session } => Ok(*session), + LpState::Transport(transport) => Ok(*transport.session), LpState::Closed { .. } => Err(LpError::LpSessionClosed), LpState::Processing => Err(LpError::LpSessionProcessing), } } - pub fn id(&self) -> Result { - Ok(self.session()?.id()) + pub fn session_identifier(&self) -> Result { + Ok(*self.session()?.session_identifier()) + } + + pub fn receiver_index(&self) -> Result { + Ok(self.session()?.receiver_index()) } /// Creates a new state machine in `Transport` state post-KKT/PSQ handshake pub fn new(session: LpSession) -> Self { LpStateMachine { - state: LpState::Transport { + state: LpState::Transport(LpTransportState { session: Box::new(session), - }, + }), } } - /// Creates a state machine in Transport state from a completed subsession handshake. - /// - /// This is used when a subsession (rekeying) completes and we need a new state machine - /// for the promoted session that can handle further subsession initiations (chained rekeying). - /// - /// # Arguments - /// - /// * `subsession` - The completed subsession handshake - /// * `receiver_index` - The new session's receiver index - /// - /// # Errors - /// - /// Returns error if the subsession handshake is not complete. - pub fn from_subsession( - subsession: SubsessionHandshake, - receiver_index: u32, - ) -> Result { - let session = subsession.into_session(receiver_index)?; - Ok(LpStateMachine { - state: LpState::Transport { - session: Box::new(session), - }, - }) + fn process_input_transport( + &mut self, + mut state: LpTransportState, + input: LpInput, + ) -> (LpState, Option>) { + let session = &mut state.session; + match input { + LpInput::ReceivePacket(packet) => { + // Check if packet lp_id matches our session + if packet.outer_header().receiver_idx != session.receiver_index() { + let result_action = Some(Err(LpError::UnknownSessionId( + packet.outer_header().receiver_idx, + ))); + return (LpState::Transport(state), result_action); + } + + let ctr = packet.outer_header().counter; + + // 1. Check replay protection + if let Err(e) = session.receiving_counter_quick_check(ctr) { + return (LpState::Transport(state), Some(Err(e))); + } + + // 2. decrypt the packet and attempt to deliver data + let packet = match session.decrypt_packet(packet) { + Ok(packet) => packet, + Err(e) => return (LpState::Transport(state), Some(Err(e))), + }; + + // 3. Mark counter as received + if let Err(e) = session.receiving_counter_mark(ctr) { + return (LpState::Transport(state), Some(Err(e))); + } + + // 4. deliver the message + let message = packet.message; + let result_action = Some(Ok(LpAction::DeliverData(message))); + (LpState::Transport(state), result_action) + } + LpInput::SendData(data) => { + // Encrypt and send application data + let result_action = match self.prepare_data_packet(session, data) { + Ok(packet) => Some(Ok(LpAction::SendPacket(packet))), + Err(e) => { + // If prepare fails, should we close? Let's report error and stay Transport for now. + // Alternative: transition to Closed state. + Some(Err(e)) + } + }; + // Remain in transport state + (LpState::Transport(state), result_action) + } + + // --- Close Transition --- + LpInput::Close => { + // Transition to Closed state + ( + LpState::Closed { + reason: "Closed by user".to_string(), + }, + Some(Ok(LpAction::ConnectionClosed)), + ) + } + } } /// Processes an input event and returns a list of actions to perform. @@ -270,606 +206,10 @@ impl LpStateMachine { // 2. Match on the owned current_state. Each arm calculates and returns the NEXT state. let next_state = match (current_state, input) { // --- Transport State --- - (LpState::Transport { mut session }, LpInput::ReceivePacket(packet)) => { - // 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 - 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 { mut session }, LpInput::SendData(data)) => { - // Encrypt and send application data - match self.prepare_data_packet(&mut 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. - // Alternative: transition to Closed state. - result_action = Some(Err(e.into())); - } - } - // Remain in transport state - LpState::Transport { session } - } - - // --- Transport + InitiateSubsession → SubsessionHandshaking --- - (LpState::Transport { mut session }, LpInput::InitiateSubsession) => { - // Get next subsession index - let subsession_index = session.next_subsession_index(); - - // Create subsession handshake (this side is initiator) - match session.create_subsession(subsession_index, true) { - Ok(subsession) => { - // Prepare KK1 message - match subsession.prepare_message() { - Ok(kk1_payload) => { - let kk1_msg = LpMessage::SubsessionKK1(SubsessionKK1Data { - payload: kk1_payload, - }); - match session.next_packet(kk1_msg) { - Ok(packet) => { - // Emit SubsessionInitiated with packet and index - result_action = Some(Ok(LpAction::SubsessionInitiated { - packet, - subsession_index, - })); - 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 } - } - } - } - - // --- SubsessionHandshaking State --- - ( - LpState::SubsessionHandshaking { - mut session, - subsession, - }, - LpInput::ReceivePacket(packet), - ) => { - // Check if packet receiver_idx matches our session - if packet.header.receiver_idx() != session.id() { - result_action = - Some(Err(LpError::UnknownSessionId(packet.header.receiver_idx()))); - LpState::SubsessionHandshaking { - session, - subsession, - } - } else { - match &packet.message { - LpMessage::SubsessionKK1(kk1_data) if !subsession.is_initiator() => { - // Responder processes KK1, prepares KK2 - // Responder stays in SubsessionHandshaking after sending KK2, - // waiting for SubsessionReady from initiator before completing - match subsession.process_message(&kk1_data.payload) { - Ok(_) => { - 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, - } - } - 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 } - } - } - } - LpMessage::SubsessionKK1(kk1_data) if subsession.is_initiator() => { - // Simultaneous initiation race detected. - // Both sides called InitiateSubsession and sent KK1 to each other. - // Use X25519 public key comparison as deterministic tie-breaker. - // Lower key loses and becomes responder. - let local_key = session.local_x25519_public(); - let remote_key = session.remote_x25519_public(); - - if local_key.as_bytes() < remote_key.as_bytes() { - // We LOSE - become responder - // Use the same index as our initiator subsession, which should - // match the winner's index if subsession counters are in sync. - // This works because both sides independently picked the same index when - // they initiated simultaneously (both counters were at the same value). - let subsession_index = subsession.index; - match session.create_subsession(subsession_index, false) { - Ok(new_subsession) => { - match new_subsession.process_message(&kk1_data.payload) { - Ok(_) => { - match new_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, - ))); - // Replace old initiator subsession with new responder subsession - LpState::SubsessionHandshaking { - session, - subsession: Box::new( - 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 } - } - } - } else { - // We WIN - stay initiator, notify peer they lost - // Send SubsessionAbort to explicitly tell peer to become responder - let abort_msg = LpMessage::SubsessionAbort; - match session.next_packet(abort_msg) { - Ok(abort_packet) => { - result_action = - Some(Ok(LpAction::SendPacket(abort_packet))); - LpState::SubsessionHandshaking { - session, - subsession, - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - } - LpMessage::SubsessionKK2(kk2_data) if subsession.is_initiator() => { - // Initiator processes KK2, completes handshake - // Initiator emits SubsessionComplete with SubsessionReady packet - // and the subsession for caller to promote via into_session() - match subsession.process_message(&kk2_data.payload) { - Ok(_) if subsession.is_complete() => { - // Generate new receiver_index for subsession - let new_receiver_index: u32 = rand::random(); - session.demote(new_receiver_index); - - // Send SubsessionReady with new index - let ready_msg = - LpMessage::SubsessionReady(SubsessionReadyData { - receiver_index: new_receiver_index, - }); - match session.next_packet(ready_msg) { - Ok(ready_packet) => { - result_action = - Some(Ok(LpAction::SubsessionComplete { - packet: Some(ready_packet), - subsession, - new_receiver_index, - })); - LpState::ReadOnlyTransport { session } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - Ok(_) => { - // Handshake not complete yet, shouldn't happen for KK - let err = LpError::Internal( - "Subsession handshake incomplete after KK2".to_string(), - ); - let reason = err.to_string(); - result_action = Some(Err(err)); - LpState::Closed { reason } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - LpMessage::EncryptedData(_) => { - // Parent still processes normal traffic during subsession handshake - // Same as Transport state handling - if let Err(e) = - session.receiving_counter_quick_check(packet.header.counter) - { - result_action = Some(Err(e)); - LpState::SubsessionHandshaking { - session, - subsession, - } - } 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::SubsessionHandshaking { - session, - subsession, - } - } else { - 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) => { - let reason = e.to_string(); - result_action = Some(Err(e.into())); - LpState::Closed { reason } - } - } - } - } - LpMessage::SubsessionReady(ready_data) if !subsession.is_initiator() => { - // Responder receives SubsessionReady from initiator - // Responder completes handshake here, uses initiator's receiver_index - // The subsession handshake should already be complete (after KK2) - if subsession.is_complete() { - let new_receiver_index = ready_data.receiver_index; - session.demote(new_receiver_index); - result_action = Some(Ok(LpAction::SubsessionComplete { - packet: None, // Responder has no packet to send - subsession, - new_receiver_index, - })); - LpState::ReadOnlyTransport { session } - } else { - // Shouldn't happen - handshake should be complete after KK2 - let err = LpError::Internal( - "Received SubsessionReady but handshake not complete" - .to_string(), - ); - let reason = err.to_string(); - result_action = Some(Err(err)); - LpState::Closed { reason } - } - } - LpMessage::SubsessionAbort if subsession.is_initiator() => { - // We received abort from peer - we lost the simultaneous initiation race. - // Peer has higher X25519 key and is staying as initiator. - // Discard our initiator subsession and return to Transport to receive peer's KK1. - // Peer's KK1 should already be in flight or queued. - result_action = None; - LpState::Transport { session } - } - LpMessage::SubsessionAbort if !subsession.is_initiator() => { - // Race was already resolved via KK1 - this abort is stale. - // We already became responder when we received KK1 and detected local < remote. - // The winner's abort message arrived after we processed their KK1. - // Silently ignore it - we're in the correct state. - result_action = None; - LpState::SubsessionHandshaking { - session, - subsession, - } - } - _ => { - // Wrong message type for subsession handshake - let err = LpError::InvalidStateTransition { - state: "SubsessionHandshaking".to_string(), - input: format!("Unexpected message type: {:?}", packet.message), - }; - let reason = err.to_string(); - result_action = Some(Err(err)); - LpState::Closed { reason } - } - } - } - } - - // Parent can still send data during subsession handshake - ( - LpState::SubsessionHandshaking { - mut session, - subsession, - }, - LpInput::SendData(data), - ) => { - match self.prepare_data_packet(&mut session, data) { - Ok(packet) => result_action = Some(Ok(LpAction::SendPacket(packet))), - Err(e) => { - result_action = Some(Err(e.into())); - } - } - LpState::SubsessionHandshaking { - session, - subsession, - } - } - - // Reject other inputs during subsession handshake - ( - LpState::SubsessionHandshaking { - session, - subsession, - }, - LpInput::InitiateSubsession, - ) => { - result_action = Some(Err(LpError::InvalidStateTransition { - state: "SubsessionHandshaking".to_string(), - input: "InitiateSubsession".to_string(), - })); - LpState::SubsessionHandshaking { - session, - subsession, - } - } - - // --- ReadOnlyTransport State --- - (LpState::ReadOnlyTransport { mut session }, LpInput::ReceivePacket(packet)) => { - // Can still receive and decrypt, but state stays ReadOnlyTransport - if packet.header.receiver_idx() != session.id() { - 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 { - 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 } - } - } - } - } - - // Reject SendData in read-only mode - (LpState::ReadOnlyTransport { session }, LpInput::SendData(_)) => { - result_action = Some(Err(LpError::NoiseError(NoiseError::SessionReadOnly))); - LpState::ReadOnlyTransport { session } - } - - // Reject other inputs in read-only mode - (LpState::ReadOnlyTransport { session }, LpInput::InitiateSubsession) => { - result_action = Some(Err(LpError::InvalidStateTransition { - state: "ReadOnlyTransport".to_string(), - input: "InitiateSubsession".to_string(), - })); - LpState::ReadOnlyTransport { session } - } - - // --- Close Transition (applies to ReadyToHandshake, KKTExchange, Handshaking, Transport, SubsessionHandshaking, ReadOnlyTransport) --- - ( - LpState::Transport { .. } - | LpState::SubsessionHandshaking { .. } - | LpState::ReadOnlyTransport { .. }, - LpInput::Close, - ) => { - result_action = Some(Ok(LpAction::ConnectionClosed)); - // Transition to Closed state - LpState::Closed { - reason: "Closed by user".to_string(), - } + (LpState::Transport(transport), input) => { + let (next_state, action) = self.process_input_transport(transport, input); + result_action = action; + next_state } // Ignore Close if already Closed (closed_state @ LpState::Closed { .. }, LpInput::Close) => { @@ -877,11 +217,6 @@ impl LpStateMachine { // Return the original closed state closed_state } - // Ignore StartHandshake if Closed - // (closed_state @ LpState::Closed { .. }, LpInput::StartHandshake) => { - // result_action = Some(Err(LpError::LpSessionClosed)); - // closed_state - // } // Ignore ReceivePacket if Closed (closed_state @ LpState::Closed { .. }, LpInput::ReceivePacket(_)) => { result_action = Some(Err(LpError::LpSessionClosed)); @@ -900,19 +235,6 @@ impl LpStateMachine { 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 } - } }; // 3. Put the calculated next state back into the machine. @@ -926,12 +248,9 @@ impl LpStateMachine { fn prepare_data_packet( &self, session: &mut LpSession, - data: LpData, - ) -> Result { - 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? + data: LpMessage, + ) -> Result { + session.encrypt_application_data(data) } } @@ -939,238 +258,95 @@ impl LpStateMachine { mod tests { use super::*; use crate::SessionsMock; + use nym_kkt_ciphersuite::{IntoEnumIterator, KEM}; #[test] fn test_state_machine_init() { - let mock_sessions = SessionsMock::mock_post_handshake(123); + for kem in KEM::iter() { + let mock_sessions = SessionsMock::mock_post_handshake(kem); - let initiator_sm = LpStateMachine::new(mock_sessions.initiator); - assert!(matches!(initiator_sm.state, LpState::Transport { .. })); - let init_session = initiator_sm.session().unwrap(); + let initiator_sm = LpStateMachine::new(mock_sessions.initiator); + assert!(matches!(initiator_sm.state, LpState::Transport { .. })); + let init_session = initiator_sm.session().unwrap(); - let responder_sm = LpStateMachine::new(mock_sessions.responder); - assert!(matches!(responder_sm.state, LpState::Transport { .. })); - let resp_session = responder_sm.session().unwrap(); + let responder_sm = LpStateMachine::new(mock_sessions.responder); + assert!(matches!(responder_sm.state, LpState::Transport { .. })); + let resp_session = responder_sm.session().unwrap(); - // Check both state machines use the same receiver_index - assert_eq!(init_session.id(), resp_session.id()); + // Check both state machines use the same receiver_index + assert_eq!(init_session.receiver_index(), resp_session.receiver_index()); + } } #[test] fn test_state_machine_simplified_flow() { - let receiver_index: u32 = 123; - let mock_sessions = SessionsMock::mock_post_handshake(123); + for kem in KEM::iter() { + let mock_sessions = SessionsMock::mock_post_handshake(kem); + let receiver_index = mock_sessions.responder.receiver_index(); - // Create state machines (already in Transport) - let mut initiator = LpStateMachine::new(mock_sessions.initiator); - let mut responder = LpStateMachine::new(mock_sessions.responder); + // Create state machines (already in Transport) + let mut initiator = LpStateMachine::new(mock_sessions.initiator); + let mut responder = LpStateMachine::new(mock_sessions.responder); - assert_eq!(initiator.id().unwrap(), responder.id().unwrap()); - - // --- Transport Phase --- - println!("--- Step 1: Initiator sends data ---"); - 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 { - panic!("Initiator should send data packet"); - }; - assert_eq!(data_packet_1.header.receiver_idx(), receiver_index); - - println!("--- Step 2: Responder receives data ---"); - let resp_actions_5 = responder.process_input(LpInput::ReceivePacket(data_packet_1)); - let resp_data_1 = if let Some(Ok(LpAction::DeliverData(data))) = resp_actions_5 { - data - } else { - panic!("Responder should deliver data"); - }; - assert_eq!(resp_data_1, data_to_send_1); - - println!("--- Step 3: Responder sends data ---"); - 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 { - panic!("Responder should send data packet"); - }; - assert_eq!(data_packet_2.header.receiver_idx(), receiver_index); - - println!("--- Step 4: 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, data_to_send_2); - } else { - panic!("Initiator should deliver data"); - } - - // --- Close --- - println!("--- Step 5: Initiator closes ---"); - let init_actions_6 = initiator.process_input(LpInput::Close); - assert!(matches!( - init_actions_6, - Some(Ok(LpAction::ConnectionClosed)) - )); - assert!(matches!(initiator.state, LpState::Closed { .. })); - - println!("--- Step 6: Responder closes ---"); - let resp_actions_7 = responder.process_input(LpInput::Close); - assert!(matches!( - resp_actions_7, - Some(Ok(LpAction::ConnectionClosed)) - )); - assert!(matches!(responder.state, LpState::Closed { .. })); - } - - /// 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) { - let sessions = SessionsMock::mock_post_handshake(12345); - ( - LpStateMachine::new(sessions.initiator), - LpStateMachine::new(sessions.responder), - ) - } - - #[test] - fn test_simultaneous_subsession_initiation() { - // Test for simultaneous subsession initiation race condition. - // Both sides call InitiateSubsession at the same time, sending KK1 to each other. - // The tie-breaker uses X25519 public key comparison: lower key becomes responder. - - let (mut alice, mut bob) = setup_transport_sessions(); - - // Get X25519 public keys to determine expected winner - let alice_x25519 = alice.session().unwrap().local_x25519_public(); - let bob_x25519 = bob.session().unwrap().local_x25519_public(); - - // Determine who should win (higher key stays initiator) - let alice_wins = alice_x25519.as_bytes() > bob_x25519.as_bytes(); - - // --- Both sides initiate subsession simultaneously --- - // Alice initiates subsession - let alice_kk1_packet = if let Some(Ok(LpAction::SubsessionInitiated { packet, .. })) = - alice.process_input(LpInput::InitiateSubsession) - { - packet - } else { - panic!("Alice should initiate subsession with KK1"); - }; - assert!(matches!(alice.state, LpState::SubsessionHandshaking { .. })); - - // Bob initiates subsession (simultaneously) - let bob_kk1_packet = if let Some(Ok(LpAction::SubsessionInitiated { packet, .. })) = - bob.process_input(LpInput::InitiateSubsession) - { - packet - } else { - panic!("Bob should initiate subsession with KK1"); - }; - assert!(matches!(bob.state, LpState::SubsessionHandshaking { .. })); - - // --- Cross-delivery of KK1 packets (race resolution) --- - // Alice receives Bob's KK1 - let alice_response = alice.process_input(LpInput::ReceivePacket(bob_kk1_packet)); - - // Bob receives Alice's KK1 - let bob_response = bob.process_input(LpInput::ReceivePacket(alice_kk1_packet)); - - // --- Verify tie-breaker worked correctly --- - if alice_wins { - // Alice has higher key - she stays initiator, sends SubsessionAbort - assert!( - matches!(alice_response, Some(Ok(LpAction::SendPacket(_)))), - "Alice (winner) should send SubsessionAbort" - ); - assert!( - matches!(alice.state, LpState::SubsessionHandshaking { .. }), - "Alice should still be SubsessionHandshaking as initiator" + assert_eq!( + initiator.session_identifier().unwrap(), + responder.session_identifier().unwrap() ); - // Bob has lower key - he becomes responder, sends KK2 - let bob_kk2_packet = if let Some(Ok(LpAction::SendPacket(p))) = bob_response { - p + // --- Transport Phase --- + println!("--- Step 1: Initiator sends data ---"); + let data_to_send_1 = LpMessage::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 { - panic!("Bob (loser) should send KK2 as new responder"); + panic!("Initiator should send data packet"); }; - assert!( - matches!(bob.state, LpState::SubsessionHandshaking { .. }), - "Bob should be SubsessionHandshaking as responder" - ); + assert_eq!(data_packet_1.outer_header().receiver_idx, receiver_index); - // Complete the handshake: Alice receives KK2 - let alice_completion = alice.process_input(LpInput::ReceivePacket(bob_kk2_packet)); - match alice_completion { - Some(Ok(LpAction::SubsessionComplete { - packet: Some(ready_packet), - .. - })) => { - assert!( - matches!(alice.state, LpState::ReadOnlyTransport { .. }), - "Alice should be ReadOnlyTransport after SubsessionComplete" - ); - - // Bob receives SubsessionReady - let bob_final = bob.process_input(LpInput::ReceivePacket(ready_packet)); - assert!( - matches!(bob_final, Some(Ok(LpAction::SubsessionComplete { .. }))), - "Bob should complete with SubsessionComplete" - ); - assert!( - matches!(bob.state, LpState::ReadOnlyTransport { .. }), - "Bob should be ReadOnlyTransport" - ); - } - other => panic!("Alice should complete subsession, got: {:?}", other), - } - } else { - // Bob has higher key - he stays initiator, sends SubsessionAbort - assert!( - matches!(bob_response, Some(Ok(LpAction::SendPacket(_)))), - "Bob (winner) should send SubsessionAbort" - ); - assert!( - matches!(bob.state, LpState::SubsessionHandshaking { .. }), - "Bob should still be SubsessionHandshaking as initiator" - ); - - // Alice has lower key - she becomes responder, sends KK2 - let alice_kk2_packet = if let Some(Ok(LpAction::SendPacket(p))) = alice_response { - p + println!("--- Step 2: Responder receives data ---"); + let resp_actions_5 = responder.process_input(LpInput::ReceivePacket(data_packet_1)); + let resp_data_1 = if let Some(Ok(LpAction::DeliverData(data))) = resp_actions_5 { + data } else { - panic!("Alice (loser) should send KK2 as new responder"); + panic!("Responder should deliver data"); }; - assert!( - matches!(alice.state, LpState::SubsessionHandshaking { .. }), - "Alice should be SubsessionHandshaking as responder" - ); + assert_eq!(resp_data_1, data_to_send_1); - // Complete the handshake: Bob receives KK2 - let bob_completion = bob.process_input(LpInput::ReceivePacket(alice_kk2_packet)); - match bob_completion { - Some(Ok(LpAction::SubsessionComplete { - packet: Some(ready_packet), - .. - })) => { - assert!( - matches!(bob.state, LpState::ReadOnlyTransport { .. }), - "Bob should be ReadOnlyTransport after SubsessionComplete" - ); + println!("--- Step 3: Responder sends data ---"); + let data_to_send_2 = LpMessage::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 { + panic!("Responder should send data packet"); + }; + assert_eq!(data_packet_2.outer_header().receiver_idx, receiver_index); - // Alice receives SubsessionReady - let alice_final = alice.process_input(LpInput::ReceivePacket(ready_packet)); - assert!( - matches!(alice_final, Some(Ok(LpAction::SubsessionComplete { .. }))), - "Alice should complete with SubsessionComplete" - ); - assert!( - matches!(alice.state, LpState::ReadOnlyTransport { .. }), - "Alice should be ReadOnlyTransport" - ); - } - other => panic!("Bob should complete subsession, got: {:?}", other), + println!("--- Step 4: 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, data_to_send_2); + } else { + panic!("Initiator should deliver data"); } + + // --- Close --- + println!("--- Step 5: Initiator closes ---"); + let init_actions_6 = initiator.process_input(LpInput::Close); + assert!(matches!( + init_actions_6, + Some(Ok(LpAction::ConnectionClosed)) + )); + assert!(matches!(initiator.state, LpState::Closed { .. })); + + println!("--- Step 6: Responder closes ---"); + let resp_actions_7 = responder.process_input(LpInput::Close); + assert!(matches!( + resp_actions_7, + Some(Ok(LpAction::ConnectionClosed)) + )); + assert!(matches!(responder.state, LpState::Closed { .. })); } } } diff --git a/common/nym-lp/src/transport/error.rs b/common/nym-lp/src/transport/error.rs new file mode 100644 index 0000000000..ccfee827ce --- /dev/null +++ b/common/nym-lp/src/transport/error.rs @@ -0,0 +1,55 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum LpTransportError { + #[error("the encoded packet is too long ({size} bytes)")] + PacketTooBig { size: usize }, + + #[error("the encoded packet is too small ({size} bytes) to encode valid data")] + PacketTooSmall { size: usize }, + + #[error("failed to establish connection with the remote host: {0}")] + ConnectionFailure(String), + + #[error("failed to configure the established connection: {0}")] + ConnectionConfigFailure(String), + + #[error("connection got closed before finishing the operation")] + ConnectionClosed, + + #[error("the received packet was malformed: {0}")] + MalformedPacket(String), + + #[error("failed to send bytes across the channel: {0}")] + TransportSendFailure(String), + + #[error("failed to receive bytes across the channel: {0}")] + TransportReceiveFailure(String), +} + +impl LpTransportError { + pub fn connection_failure(error: impl Into) -> Self { + LpTransportError::ConnectionFailure(error.into()) + } + + pub fn connection_config(error: impl Into) -> Self { + LpTransportError::ConnectionConfigFailure(error.into()) + } + + pub fn send_failure(error: std::io::Error) -> Self { + if error.kind() == std::io::ErrorKind::UnexpectedEof { + return LpTransportError::ConnectionClosed; + } + LpTransportError::TransportSendFailure(error.to_string()) + } + + pub fn receive_failure(error: std::io::Error) -> Self { + if error.kind() == std::io::ErrorKind::UnexpectedEof { + return LpTransportError::ConnectionClosed; + } + LpTransportError::TransportReceiveFailure(error.to_string()) + } +} diff --git a/common/nym-lp-transport/src/lib.rs b/common/nym-lp/src/transport/mod.rs similarity index 52% rename from common/nym-lp-transport/src/lib.rs rename to common/nym-lp/src/transport/mod.rs index 3f62a665ed..8770aad406 100644 --- a/common/nym-lp-transport/src/lib.rs +++ b/common/nym-lp/src/transport/mod.rs @@ -1,4 +1,9 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +pub mod error; pub mod traits; + +pub use error::LpTransportError; + +pub use traits::{LpHandshakeChannel, LpTransportChannel}; diff --git a/common/nym-lp/src/transport/traits.rs b/common/nym-lp/src/transport/traits.rs new file mode 100644 index 0000000000..f3c39af0af --- /dev/null +++ b/common/nym-lp/src/transport/traits.rs @@ -0,0 +1,302 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::packet::{EncryptedLpPacket, OuterHeader}; +use crate::transport::error::LpTransportError; +use nym_kkt::context::KKTMode; +use nym_kkt_ciphersuite::KEM; +use std::net::SocketAddr; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use tokio::net::TcpStream; +use tracing::debug; + +#[cfg(any(feature = "mock", test))] +use nym_test_utils::mocks::async_read_write::MockIOStream; + +pub const MAX_TRANSPORT_PACKET_SIZE: usize = 65536; // 64KB max +pub const MAX_HANDSHAKE_PACKET_SIZE: usize = 524287; // 524'160 for mceliece key + a bit of overhead for safety + +/// Simple trait allowing sending bytes across. +/// It is not concerned with encryption. It is up to the caller. +// only used in internal code (and tests) +#[allow(async_fn_in_trait)] +pub trait LpHandshakeChannel: Sized { + /// Write all provided data and immediately flush the buffer + async fn write_all_and_flush(&mut self, data: &[u8]) -> Result<(), LpTransportError>; + + /// Wrapper around `ReadExact` to return the `Vec` of `n` bytes directly + async fn read_n_bytes(&mut self, n: usize) -> Result, LpTransportError>; + + /// Send the provided handshake message on the connection + async fn send_handshake_message( + &mut self, + message: M, + _: KEM, + ) -> Result<(), LpTransportError> { + self.write_all_and_flush(&message.into_bytes()).await + } + + /// Attempt to receive a handshake message of the provided type from the stream + async fn receive_handshake_message( + &mut self, + expected_size: usize, + ) -> Result { + let bytes = self.read_n_bytes(expected_size).await?; + M::try_from_bytes(bytes) + } +} + +pub trait HandshakeMessage: Sized { + /// Convert this message into bytes + fn into_bytes(self) -> Vec; + + /// Attempt to recover this message from the byte stream + fn try_from_bytes(bytes: Vec) -> Result; + + /// Expected size of this message based on the provided parameters + fn expected_size(mode: KKTMode, expected_kem: KEM, payload_size: usize) -> usize; + + /// Expected size of the response from the remote party. + /// `None` if this is the final (PSQ msg2) message of the exchange + fn response_size(&self, expected_kem: KEM) -> Option; +} + +async fn write_all_and_flush_async_write( + writer: &mut W, + data: &[u8], +) -> Result<(), LpTransportError> +where + W: AsyncWrite + Unpin, +{ + writer + .write_all(data) + .await + .map_err(LpTransportError::send_failure)?; + writer.flush().await.map_err(LpTransportError::send_failure) +} + +async fn read_n_bytes_async_read(reader: &mut R, n: usize) -> Result, LpTransportError> +where + R: AsyncRead + Unpin, +{ + let mut buf = vec![0u8; n]; + if n > MAX_HANDSHAKE_PACKET_SIZE { + return Err(LpTransportError::PacketTooBig { size: n }); + } + reader + .read_exact(&mut buf) + .await + .map_err(LpTransportError::receive_failure)?; + Ok(buf) +} + +// only used in internal code (and tests) +#[allow(async_fn_in_trait)] +pub trait LpTransportChannel: Sized { + async fn connect(endpoint: SocketAddr) -> Result; + + fn set_no_delay(&mut self, nodelay: bool) -> Result<(), LpTransportError>; + + /// Sends a serialised and encrypted LP packet over the data stream with length-prefixed framing. + /// + /// Format: 4-byte big-endian u32 length + packet bytes + /// + /// # Arguments + /// * `packet` - The encrypted LP packet to send + /// + /// # Errors + /// Returns an error on network transmission fails. + async fn send_length_prefixed_transport_packet( + &mut self, + packet: &EncryptedLpPacket, + ) -> Result<(), LpTransportError>; + + /// Receives an LP packet from a TCP stream with length-prefixed framing without additional parsing + /// + /// Format: 4-byte big-endian u32 length + packet bytes + /// + /// # Errors + /// Returns an error on network transmission fails. + async fn receive_length_prefixed_transport_bytes( + &mut self, + ) -> Result, LpTransportError>; + + /// 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_length_prefixed_transport_packet( + &mut self, + ) -> Result { + let mut bytes = self.receive_length_prefixed_transport_bytes().await?; + + if bytes.len() < OuterHeader::SIZE { + return Err(LpTransportError::PacketTooSmall { size: bytes.len() }); + } + + // split it into the outer header and ciphertext + let ciphertext = bytes.split_off(OuterHeader::SIZE); + + // SAFETY: we just checked we have at least OuterHeader::SIZE bytes + #[allow(clippy::unwrap_used)] + let outer_header = OuterHeader::parse(&bytes).unwrap(); + + tracing::trace!( + "Received LP packet ({} bytes + 4 byte length-prefix)", + bytes.len() + ); + Ok(EncryptedLpPacket::new(outer_header, ciphertext)) + } +} + +async fn send_serialised_packet_async_write( + writer: &mut W, + packet: &EncryptedLpPacket, +) -> Result<(), LpTransportError> +where + W: AsyncWrite + Unpin, +{ + // Send 4-byte length prefix (u32 big-endian) + let len = packet.encoded_length() as u32; + writer + .write_all(&len.to_le_bytes()) + .await + .inspect_err(|e| debug!("Failed to send packet length: {e}")) + .map_err(LpTransportError::send_failure)?; + + // TODO: benchmark whether it'd be faster to concatenate all slices slices and + // use a single `write_all` call + + // Send the outer header + writer + .write_all(&packet.outer_header().to_bytes()) + .await + .inspect_err(|e| debug!("Failed to send packet data: {e}")) + .map_err(LpTransportError::send_failure)?; + + // Send the actual packet data + writer + .write_all(packet.ciphertext()) + .await + .inspect_err(|e| debug!("Failed to send packet data: {e}")) + .map_err(LpTransportError::send_failure)?; + + // Flush to ensure data is sent immediately + writer + .flush() + .await + .inspect_err(|e| debug!("Failed to flush stream: {e}")) + .map_err(LpTransportError::send_failure)?; + + tracing::trace!( + "Sent LP packet ({} bytes + 4 byte length-prefix)", + packet.encoded_length() + ); + Ok(()) +} + +async fn receive_length_prefixed_bytes_async_read( + reader: &mut R, +) -> Result, LpTransportError> +where + R: AsyncRead + Unpin, +{ + // Read 4-byte length prefix (u32 big-endian) + let mut len_buf = [0u8; 4]; + reader + .read_exact(&mut len_buf) + .await + .inspect_err(|e| debug!("Failed to read packet length: {e}")) + .map_err(LpTransportError::receive_failure)?; + + let size = u32::from_le_bytes(len_buf) as usize; + + // Sanity check to prevent huge allocations + if size > MAX_TRANSPORT_PACKET_SIZE { + return Err(LpTransportError::PacketTooBig { size }); + } + + // Read the actual packet data + let mut packet_buf = vec![0u8; size]; + reader + .read_exact(&mut packet_buf) + .await + .inspect_err(|e| debug!("Failed to read packet data: {e}")) + .map_err(LpTransportError::receive_failure)?; + + Ok(packet_buf) +} + +impl LpTransportChannel for TcpStream { + async fn connect(endpoint: SocketAddr) -> Result { + TcpStream::connect(endpoint) + .await + .map_err(|err| LpTransportError::connection_failure(err.to_string())) + } + + fn set_no_delay(&mut self, nodelay: bool) -> Result<(), LpTransportError> { + // Set TCP_NODELAY for low latency + self.set_nodelay(nodelay) + .map_err(|err| LpTransportError::connection_config(err.to_string())) + } + + async fn send_length_prefixed_transport_packet( + &mut self, + packet: &EncryptedLpPacket, + ) -> Result<(), LpTransportError> { + send_serialised_packet_async_write(self, packet).await + } + + async fn receive_length_prefixed_transport_bytes( + &mut self, + ) -> Result, LpTransportError> { + receive_length_prefixed_bytes_async_read(self).await + } +} + +#[cfg(any(feature = "mock", test))] +impl LpTransportChannel for MockIOStream { + async fn connect(_endpoint: SocketAddr) -> Result { + Ok(MockIOStream::default()) + } + + fn set_no_delay(&mut self, _nodelay: bool) -> Result<(), LpTransportError> { + Ok(()) + } + + async fn send_length_prefixed_transport_packet( + &mut self, + packet: &EncryptedLpPacket, + ) -> Result<(), LpTransportError> { + send_serialised_packet_async_write(self, packet).await + } + + async fn receive_length_prefixed_transport_bytes( + &mut self, + ) -> Result, LpTransportError> { + receive_length_prefixed_bytes_async_read(self).await + } +} + +#[cfg(any(feature = "mock", test))] +impl LpHandshakeChannel for MockIOStream { + async fn write_all_and_flush(&mut self, data: &[u8]) -> Result<(), LpTransportError> { + write_all_and_flush_async_write(self, data).await + } + + async fn read_n_bytes(&mut self, n: usize) -> Result, LpTransportError> { + read_n_bytes_async_read(self, n).await + } +} + +impl LpHandshakeChannel for TcpStream { + async fn write_all_and_flush(&mut self, data: &[u8]) -> Result<(), LpTransportError> { + write_all_and_flush_async_write(self, data).await + } + + async fn read_n_bytes(&mut self, n: usize) -> Result, LpTransportError> { + read_n_bytes_async_read(self, n).await + } +} diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index f297de143d..8e271ab1ea 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -5,13 +5,14 @@ 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_kkt_ciphersuite::{Ciphersuite, KEM, KEMKeyDigests}; use nym_sphinx::addressing::Recipient; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; pub use lp_messages::*; +use nym_crypto::asymmetric::x25519::DHPublicKey; pub use serialisation::BincodeError; mod lp_messages; @@ -56,9 +57,11 @@ pub struct WireguardConfiguration { #[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, + pub expected_kem_key_hashes: BTreeMap, + pub x25519: DHPublicKey, + + // to be inferred from node's version + pub ciphersuite: Ciphersuite, /// Supported protocol version of the remote gateway. /// Included in case we have to downgrade our version. diff --git a/common/store-cipher/Cargo.toml b/common/store-cipher/Cargo.toml index 124d5b1869..a2066a5247 100644 --- a/common/store-cipher/Cargo.toml +++ b/common/store-cipher/Cargo.toml @@ -21,7 +21,7 @@ thiserror = { workspace = true } zeroize = { workspace = true, features = ["zeroize_derive"] } [target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies] -getrandom = { version = "0.2", features = ["js"] } +getrandom = { workspace = true, features = ["js"] } [features] default = [] diff --git a/common/test-utils/Cargo.toml b/common/test-utils/Cargo.toml index ab509eb8f9..ead0cff923 100644 --- a/common/test-utils/Cargo.toml +++ b/common/test-utils/Cargo.toml @@ -15,6 +15,7 @@ description = "Helpers, traits, and mock definitions for tests" anyhow = { workspace = true } futures = { workspace = true } rand_chacha = { workspace = true } +rand_chacha09 = { workspace = true } tokio = { workspace = true, features = ["sync", "time", "rt"] } tracing = { workspace = true } diff --git a/common/test-utils/src/helpers.rs b/common/test-utils/src/helpers.rs index e96494dfc0..17ef674c93 100644 --- a/common/test-utils/src/helpers.rs +++ b/common/test-utils/src/helpers.rs @@ -1,19 +1,29 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +// fine in test code +#![allow(clippy::unwrap_used)] + use crate::traits::Timeboxed; 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 rand_chacha09::rand_core::SeedableRng as SeedableRng09; use std::future::Future; +use std::sync::{Arc, Mutex}; use tokio::task::JoinHandle; use tokio::time::error::Elapsed; +// 'current' rand crate pub use rand_chacha::ChaCha20Rng as DeterministicRng; pub use rand_chacha::rand_core::{CryptoRng, RngCore}; +// rand09 compat +pub use rand_chacha09::ChaChaRng as DeterministicRng09; +pub use rand_chacha09::rand_core::{CryptoRng as CryptoRng09, RngCore as RngCore09}; + pub fn leak(val: T) -> &'static mut T { Box::leak(Box::new(val)) } @@ -26,6 +36,35 @@ where tokio::spawn(async move { fut.timeboxed().await }) } +pub struct DeterministicRng09Send(Arc>); + +impl DeterministicRng09Send { + pub fn new(deterministic_rng09: DeterministicRng09) -> Self { + Self(Arc::new(Mutex::new(deterministic_rng09))) + } +} + +impl CryptoRng09 for DeterministicRng09Send {} + +// unwraps are perfectly fine in test code +impl RngCore09 for DeterministicRng09Send { + fn next_u32(&mut self) -> u32 { + self.0.lock().unwrap().next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.0.lock().unwrap().next_u64() + } + + fn fill_bytes(&mut self, dst: &mut [u8]) { + self.0.lock().unwrap().fill_bytes(dst) + } +} + +pub fn deterministic_rng_09() -> DeterministicRng09 { + seeded_rng_09([42u8; 32]) +} + pub fn deterministic_rng() -> DeterministicRng { seeded_rng([42u8; 32]) } @@ -34,10 +73,18 @@ pub fn seeded_rng(seed: [u8; 32]) -> DeterministicRng { DeterministicRng::from_seed(seed) } +pub fn seeded_rng_09(seed: [u8; 32]) -> DeterministicRng09 { + DeterministicRng09::from_seed(seed) +} + pub fn u64_seeded_rng(seed: u64) -> DeterministicRng { DeterministicRng::seed_from_u64(seed) } +pub fn u64_seeded_rng_09(seed: u64) -> DeterministicRng09 { + DeterministicRng09::seed_from_u64(seed) +} + // test logger to use during debugging #[allow(clippy::unwrap_used)] pub fn setup_test_logger() { diff --git a/common/wasm/client-core/.cargo/config.toml b/common/wasm/client-core/.cargo/config.toml index bdbe517658..e9c3438b6e 100644 --- a/common/wasm/client-core/.cargo/config.toml +++ b/common/wasm/client-core/.cargo/config.toml @@ -1,3 +1,4 @@ [build] target = "wasm32-unknown-unknown" target_arch = "wasm32" +rustflags = ["--cfg=getrandom_backend=\"wasm_js\""] \ No newline at end of file diff --git a/common/wasm/storage/.cargo/config.toml b/common/wasm/storage/.cargo/config.toml index bdbe517658..e9c3438b6e 100644 --- a/common/wasm/storage/.cargo/config.toml +++ b/common/wasm/storage/.cargo/config.toml @@ -1,3 +1,4 @@ [build] target = "wasm32-unknown-unknown" target_arch = "wasm32" +rustflags = ["--cfg=getrandom_backend=\"wasm_js\""] \ No newline at end of file diff --git a/common/wasm/storage/Cargo.toml b/common/wasm/storage/Cargo.toml index 884a7b89b6..7c07592da2 100644 --- a/common/wasm/storage/Cargo.toml +++ b/common/wasm/storage/Cargo.toml @@ -13,8 +13,6 @@ documentation.workspace = true [dependencies] async-trait = { workspace = true } -getrandom = { workspace = true, features = ["js"] } -js-sys = { workspace = true } wasm-bindgen = { workspace = true } serde = { workspace = true, features = ["derive"] } serde-wasm-bindgen = { workspace = true } diff --git a/common/wasm/utils/.cargo/config.toml b/common/wasm/utils/.cargo/config.toml index bdbe517658..e9c3438b6e 100644 --- a/common/wasm/utils/.cargo/config.toml +++ b/common/wasm/utils/.cargo/config.toml @@ -1,3 +1,4 @@ [build] target = "wasm32-unknown-unknown" target_arch = "wasm32" +rustflags = ["--cfg=getrandom_backend=\"wasm_js\""] \ No newline at end of file diff --git a/common/wireguard/src/peer_controller/mod.rs b/common/wireguard/src/peer_controller/mod.rs index 9aae772a8f..19f936b93b 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 +pub use crate::ip_pool::IpPair; use crate::ip_pool::allocated_ip_pair; use crate::{ IpPool, @@ -23,6 +24,7 @@ use nym_credentials_interface::CredentialSpendingData; use nym_gateway_requests::models::CredentialSpendingRequest; use nym_gateway_storage::traits::BandwidthGatewayStorage; use nym_node_metrics::NymNodeMetrics; +use nym_node_metrics::prometheus_wrapper::{PROMETHEUS_METRICS, PrometheusMetric}; use nym_wireguard_types::{ DEFAULT_IP_CLEANUP_INTERVAL, DEFAULT_IP_STALE_AGE, DEFAULT_PEER_TIMEOUT_CHECK, }; @@ -35,8 +37,6 @@ use tokio::sync::{RwLock, mpsc}; use tokio_stream::{StreamExt, wrappers::IntervalStream}; use tracing::{debug, error, info, trace}; -pub use crate::ip_pool::IpPair; - #[cfg(feature = "mock")] pub mod mock; @@ -261,6 +261,10 @@ impl PeerController { } async fn handle_add_request(&mut self, peer: &Peer) -> Result<()> { + // observation will get automatically added once dropped + let _metric_timer = + PROMETHEUS_METRICS.start_timer(PrometheusMetric::WireguardDefguardPeerCreation); + nym_metrics::inc!("wg_peer_addition_attempts"); // confirm ip allocation so that it wouldn't be released for as long as the peer exists diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 5468e50112..84a73def30 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1137,9 +1137,9 @@ checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libm" diff --git a/documentation/README.md b/documentation/README.md index b27ac7733d..7b59737970 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -56,6 +56,39 @@ pnpm run build ## CI/CD - **Link checking**: Runs on every push to `documentation/docs/` via `.github/workflows/ci-docs-linkcheck.yml` +## SEO & Structured Data +### Frontmatter +Every `.mdx` page supports frontmatter fields that control meta tags, Open Graph, and JSON-LD schema: +```yaml +--- +title: "Page Title for Search Engines" +description: "Unique meta description for this page." +schemaType: "TechArticle" # TechArticle (default), HowTo, or FAQPage +section: "Operators" # Operators, Developers, Network, APIs +lastUpdated: "2026-02-11" # Feeds dateModified schema +breadcrumbLabel: "Custom Label" # Optional, overrides URL slug in breadcrumbs +--- +``` + +### Sitemap +```bash +npx next-sitemap +``` +Outputs `sitemap.xml` and `robots.txt` to `/public`. + +### Environment Variable +Set in production: +``` +NEXT_PUBLIC_SITE_URL=https://nymtech.net/docs +``` + +### Schema Types +| Type | Use When | +|------|----------| +| TechArticle | Reference docs, config guides, overviews (default) | +| HowTo | Step-by-step install/setup guides | +| FAQPage | Question-answer pages | + ## Licensing and copyright information This is a monorepo and components that make up Nym as a system are licensed individually, so for accurate information, please check individual files. diff --git a/documentation/docs/code-examples/sdk/typescript/mixfetch-example-code.mdx b/documentation/docs/code-examples/sdk/typescript/mixfetch-example-code.mdx index 85eb29277f..e6b003c6fb 100644 --- a/documentation/docs/code-examples/sdk/typescript/mixfetch-example-code.mdx +++ b/documentation/docs/code-examples/sdk/typescript/mixfetch-example-code.mdx @@ -1,86 +1,273 @@ -```tsx copy filename="mixFetchExample.tsx" -import React, { useState } from "react"; +```tsx +import React, { useState, useRef, useEffect } from "react"; import CircularProgress from "@mui/material/CircularProgress"; import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; import Typography from "@mui/material/Typography"; import Box from "@mui/material/Box"; -import { mixFetch } from "@nymproject/mix-fetch-full-fat"; +import { mixFetch, createMixFetch } from "@nymproject/mix-fetch-full-fat"; import Stack from "@mui/material/Stack"; import Paper from "@mui/material/Paper"; import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat"; -const defaultUrl = "https://nymtech.net/.wellknown/network-requester/exit-policy.txt"; +const defaultUrl = + "https://nymtech.net/.wellknown/network-requester/exit-policy.txt"; const args = { mode: "unsafe-ignore-cors" }; - const mixFetchOptions: SetupMixFetchOps = { - preferredGateway: "2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW", // with WSS - // preferredNetworkRequester: - // "CTDxrcXgrZHWyCWnuCgjpJPghQUcEVz1HkhUr5mGdFnT.3UAww1YWNyVNYNWFQL1LaHYouQtDiXBGK5GiDZgpXkTK@2RFtU5BwxvJJXagAWAEuaPgb5ZVPRoy2542TT93Edw6v", + clientId: "docs-mixfetch-demo", // explicit ID + preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", mixFetchOverride: { requestTimeoutMs: 60_000, }, forceTls: true, // force WSS }; +// Log entry type for the visible log panel +type LogLevel = "info" | "error" | "send" | "receive"; +type LogEntry = { timestamp: string; message: string; level: LogLevel }; + +const logColors: Record = { + info: "gray", + error: "red", + send: "blue", + receive: "green", +}; + +const logLabels: Record = { + info: "INFO", + error: "ERROR", + send: "SEND", + receive: "RECV", +}; + export const MixFetch = () => { + // MixFetch initialization state + const [status, setStatus] = useState<"idle" | "starting" | "ready" | "error">("idle"); + const [errorMsg, setErrorMsg] = useState(null); + + // Log panel state + const [logs, setLogs] = useState([]); + const logEndRef = useRef(null); + + // Single fetch state const [url, setUrl] = useState(defaultUrl); const [html, setHtml] = useState(); const [busy, setBusy] = useState(false); + // Concurrent fetch state + const [concurrentResults, setConcurrentResults] = useState([]); + const [concurrentBusy, setConcurrentBusy] = useState(false); + + // Auto-scroll log panel to bottom + useEffect(() => { + logEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [logs]); + + // Helper to add a timestamped log entry + const addLog = (message: string, level: LogLevel) => { + const timestamp = new Date().toISOString().substring(11, 23); + setLogs((prev) => [...prev, { timestamp, message, level }]); + }; + + // Initialize MixFetch explicitly via createMixFetch + const handleStart = async () => { + try { + setStatus("starting"); + setErrorMsg(null); + addLog("Starting MixFetch...", "info"); + await createMixFetch(mixFetchOptions); + setStatus("ready"); + addLog("MixFetch is ready!", "info"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + setStatus("error"); + setErrorMsg(msg); + addLog(`Error: ${msg}`, "error"); + } + }; + + // Single URL fetch — reuses the existing MixFetch singleton const handleFetch = async () => { try { setBusy(true); setHtml(undefined); + addLog(`Sending request to ${url}...`, "send"); const response = await mixFetch(url, args, mixFetchOptions); - console.log(response); const resHtml = await response.text(); setHtml(resHtml); + addLog(`Response received (${resHtml.length} bytes)`, "receive"); } catch (err) { - console.log(err); + const msg = err instanceof Error ? err.message : String(err); + addLog(`Fetch error: ${msg}`, "error"); } finally { setBusy(false); } }; + // Send 5 concurrent requests to different URLs on the same domain + const handleConcurrentFetch = async () => { + const baseUrl = "https://jsonplaceholder.typicode.com/posts/"; + const count = 5; + try { + setConcurrentBusy(true); + setConcurrentResults([]); + addLog( + `Starting ${count} concurrent requests to ${baseUrl}1-${count}...`, + "send", + ); + // Fire off all requests concurrently using Promise.all + const requests = Array.from({ length: count }, (_, i) => { + const targetUrl = `${baseUrl}${i + 1}`; + return mixFetch(targetUrl, args, mixFetchOptions) + .then((res) => res.json()) + .then((json: { id: number; title: string }) => { + const entry = `[${json.id}] ${json.title}`; + addLog(entry, "receive"); + return entry; + }); + }); + const results = await Promise.all(requests); + setConcurrentResults(results); + addLog(`All ${count} concurrent requests completed!`, "info"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + addLog(`Concurrent fetch error: ${msg}`, "error"); + } finally { + setConcurrentBusy(false); + } + }; + + const isReady = status === "ready"; + + const statusText = { + idle: "Not started", + starting: "Starting...", + ready: "Ready", + error: `Error: ${errorMsg}`, + }; + const statusColor = { + idle: "gray", + starting: "orange", + ready: "green", + error: "red", + }; + return (
- - setUrl(e.target.value)} - /> - - + {/* Start MixFetch */} + + + + {status === "starting" && } + + {statusText[status]} + + + - {busy && ( - - - - )} - {html && ( - <> - - Response + {/* Fetch controls — disabled until MixFetch is ready */} + + {/* Single fetch */} + + setUrl(e.target.value)} + /> + + + {busy && ( + + - - - {html} - + )} + {html && ( + <> + + Response + + + + {html} + + + + )} + + {/* Concurrent fetch */} + + Concurrent Requests + + + + + {concurrentBusy && ( + + + + )} + {concurrentResults.length > 0 && ( + + {concurrentResults.map((result, i) => ( + + {result} + + ))} - + )} + + + {/* Log Panel */} + {logs.length > 0 && ( + + Log + {logs.map((entry, i) => ( + + {entry.timestamp} [{logLabels[entry.level]}] {entry.message} + + ))} +
+ )}
); diff --git a/documentation/docs/components/landing-page.tsx b/documentation/docs/components/landing-page.tsx index a3e274994a..699e704263 100644 --- a/documentation/docs/components/landing-page.tsx +++ b/documentation/docs/components/landing-page.tsx @@ -51,8 +51,8 @@ export const LandingPage = () => { }; return ( - - + + {/* Nym Docs @@ -62,70 +62,54 @@ export const LandingPage = () => { using blinded, re-randomizable, decentralized credentials. Our goal is to allow developers to build new applications, or upgrade existing apps, with privacy features unavailable in other systems. - - + */} + {squares.map((square, index) => ( - + + + {square.text} + + + + {square.description} + + {square.text} - - - {square.text} - - - - {square.description} - - - - Open - - diff --git a/documentation/docs/components/mix-fetch.tsx b/documentation/docs/components/mix-fetch.tsx index a7b51c3b9d..a754ba7f62 100644 --- a/documentation/docs/components/mix-fetch.tsx +++ b/documentation/docs/components/mix-fetch.tsx @@ -1,10 +1,10 @@ -import React, { useState } from "react"; +import React, { useState, useRef, useEffect } from "react"; import CircularProgress from "@mui/material/CircularProgress"; import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; import Typography from "@mui/material/Typography"; import Box from "@mui/material/Box"; -import { mixFetch } from "@nymproject/mix-fetch-full-fat"; +import { mixFetch, createMixFetch } from "@nymproject/mix-fetch-full-fat"; import Stack from "@mui/material/Stack"; import Paper from "@mui/material/Paper"; import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat"; @@ -12,8 +12,8 @@ import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat"; const defaultUrl = "https://nymtech.net/.wellknown/network-requester/exit-policy.txt"; const args = { mode: "unsafe-ignore-cors" }; - const mixFetchOptions: SetupMixFetchOps = { + clientId: "docs-mixfetch-demo", // explicit ID preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", mixFetchOverride: { requestTimeoutMs: 60_000, @@ -21,64 +21,260 @@ const mixFetchOptions: SetupMixFetchOps = { forceTls: true, // force WSS }; +// Log entry type for the visible log panel +type LogLevel = "info" | "error" | "send" | "receive"; +type LogEntry = { timestamp: string; message: string; level: LogLevel }; + +// Color map for log levels +const logColors: Record = { + info: "gray", + error: "red", + send: "blue", + receive: "green", +}; + +// Label map for log levels +const logLabels: Record = { + info: "INFO", + error: "ERROR", + send: "SEND", + receive: "RECV", +}; + export const MixFetch = () => { + // MixFetch initialization state + const [status, setStatus] = useState<"idle" | "starting" | "ready" | "error">( + "idle" + ); + const [errorMsg, setErrorMsg] = useState(null); + + // Log panel state + const [logs, setLogs] = useState([]); + + // Single fetch state const [url, setUrl] = useState(defaultUrl); const [html, setHtml] = useState(); const [busy, setBusy] = useState(false); + // Concurrent fetch state + const [concurrentResults, setConcurrentResults] = useState([]); + const [concurrentBusy, setConcurrentBusy] = useState(false); + + // Auto-scroll within the log panel when new entries are added (without scrolling the page) + const logContainerRef = useRef(null); + useEffect(() => { + if (logContainerRef.current) { + logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; + } + }, [logs]); + + // Helper to add a timestamped log entry + const addLog = (message: string, level: LogLevel) => { + const timestamp = new Date().toISOString().substring(11, 23); // HH:MM:SS.mmm + setLogs((prev) => [...prev, { timestamp, message, level }]); + }; + + // Initialize MixFetch explicitly via createMixFetch + const handleStart = async () => { + try { + setStatus("starting"); + setErrorMsg(null); + addLog("Starting MixFetch...", "info"); + await createMixFetch(mixFetchOptions); + setStatus("ready"); + addLog("MixFetch is ready!", "info"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + setStatus("error"); + setErrorMsg(msg); + addLog(`Error: ${msg}`, "error"); + } + }; + + // Single URL fetch — mixFetch reuses the existing singleton const handleFetch = async () => { try { setBusy(true); setHtml(undefined); + addLog(`Sending request to ${url}...`, "send"); const response = await mixFetch(url, args, mixFetchOptions); - console.log(response); const resHtml = await response.text(); setHtml(resHtml); + addLog(`Response received (${resHtml.length} bytes)`, "receive"); } catch (err) { - console.log(err); + const msg = err instanceof Error ? err.message : String(err); + addLog(`Fetch error: ${msg}`, "error"); } finally { setBusy(false); } }; + // Send 5 concurrent requests to different URLs on the same domain + const handleConcurrentFetch = async () => { + const baseUrl = "https://jsonplaceholder.typicode.com/posts/"; + const count = 5; + try { + setConcurrentBusy(true); + setConcurrentResults([]); + addLog( + `Starting ${count} concurrent requests to ${baseUrl}1-${count}...`, + "send" + ); + // Fire off all requests concurrently using Promise.all + const requests = Array.from({ length: count }, (_, i) => { + const targetUrl = `${baseUrl}${i + 1}`; + return mixFetch(targetUrl, args, mixFetchOptions) + .then((res) => res.json()) + .then((json: { id: number; title: string }) => { + const entry = `[${json.id}] ${json.title}`; + addLog(entry, "receive"); + return entry; + }); + }); + const results = await Promise.all(requests); + setConcurrentResults(results); + addLog(`All ${count} concurrent requests completed!`, "info"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + addLog(`Concurrent fetch error: ${msg}`, "error"); + } finally { + setConcurrentBusy(false); + } + }; + + // Are fetch controls enabled? + const isReady = status === "ready"; + + // Status text + color for the startup indicator + const statusText: Record = { + idle: "Not started", + starting: "Starting...", + ready: "Ready", + error: `Error: ${errorMsg}`, + }; + const statusColor: Record = { + idle: "gray", + starting: "orange", + ready: "green", + error: "red", + }; + return (
- - setUrl(e.target.value)} - /> - - + {/* --- Start MixFetch Section --- */} + + + + {status === "starting" && } + + {statusText[status]} + + + - {busy && ( - - - - )} - {html && ( - <> - - Response + {/* --- Fetch Controls (disabled until ready) --- */} + + {/* Single fetch */} + + setUrl(e.target.value)} + /> + + + {busy && ( + + - - - {html} - + )} + {html && ( + <> + + Response + + + + {html} + + + + )} + + {/* Concurrent fetch demo */} + + Concurrent Requests + + + + + {concurrentBusy && ( + + + + )} + {concurrentResults.length > 0 && ( + + {concurrentResults.map((result, i) => ( + + {result} + + ))} - + )} + + + {/* --- Log Panel --- */} + {logs.length > 0 && ( + + Log + {logs.map((entry, i) => ( + + {entry.timestamp} [{logLabels[entry.level]}] {entry.message} + + ))} + )}
); 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 00cd13c087..7b90ea43ab 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.87% +0.90% 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 6b54162743..0df94bdc04 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 @@ -33.087 +31.932 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 1aaa64f171..32d8432bb0 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 @@ -Wednesday, February 11th 2026, 11:35:05 UTC +Tuesday, February 24th 2026, 10:08:37 UTC diff --git a/documentation/docs/components/outputs/command-outputs/node-api-check-help.md b/documentation/docs/components/outputs/command-outputs/node-api-check-help.md index 875b2921ae..d41e42995c 100644 --- a/documentation/docs/components/outputs/command-outputs/node-api-check-help.md +++ b/documentation/docs/components/outputs/command-outputs/node-api-check-help.md @@ -11,7 +11,7 @@ positional arguments: version_count (v, version) Sum of nodes in given version(s) -options: +optional arguments: -h, --help show this help message and exit -V, --version show program's version number and exit ``` diff --git a/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md b/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md index 999f3d4a6f..b96adaebe5 100644 --- a/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md +++ b/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md @@ -6,7 +6,7 @@ usage: Nym-node API check query_stats [-h] [--no_routing_history] positional arguments: id supply nym-node identity key -options: +optional arguments: -h, --help show this help message and exit --no_routing_history Display node stats without routing history --no_verloc_metrics Display node stats without verloc metrics diff --git a/documentation/docs/next-sitemap.config.js b/documentation/docs/next-sitemap.config.js new file mode 100644 index 0000000000..c676d09f91 --- /dev/null +++ b/documentation/docs/next-sitemap.config.js @@ -0,0 +1,28 @@ +/** @type {import('next-sitemap').IConfig} */ +module.exports = { + siteUrl: 'https://nymtech.net/docs', + generateRobotsTxt: true, + outDir: './public', + exclude: ['/api/*', '/docs/_*', '/404'], + robotsTxtOptions: { + policies: [ + { userAgent: '*', allow: '/' }, + { userAgent: '*', disallow: ['/api/', '/_next/'] }, + ], + additionalSitemaps: [ + 'https://nymtech.net/docs/sitemap-docs.xml', + ], + }, + transform: async (config, path) => ({ + loc: path, + changefreq: path.includes('/changelog') + ? 'weekly' + : path.includes('/docs/operators') || path.includes('/docs/developers') + ? 'monthly' + : 'yearly', + priority: path === '/docs' ? 1.0 + : path.includes('/operators/nodes') || path.includes('/developers') ? 0.8 + : 0.6, + lastmod: new Date().toISOString(), + }), +} \ No newline at end of file diff --git a/documentation/docs/package.json b/documentation/docs/package.json index 63e3132a88..e7e69e1ff6 100644 --- a/documentation/docs/package.json +++ b/documentation/docs/package.json @@ -38,7 +38,7 @@ "@nextui-org/accordion": "^2.0.40", "@nextui-org/react": "^2.4.8", "@nymproject/contract-clients": ">=1.2.4-rc.2 || ^1", - "@nymproject/mix-fetch-full-fat": ">=1.5.1-rc.0 || ^1.4.1", + "@nymproject/mix-fetch-full-fat": "^1.4.2", "@nymproject/sdk-full-fat": ">=1.5.1-rc.0 || ^1.4.1", "@redocly/cli": "^1.25.15", "@types/mdx": "^2.0.13", @@ -61,6 +61,7 @@ "copy-webpack-plugin": "^11.0.0", "eslint": "8.46.0", "eslint-config-next": "13.4.13", + "next-sitemap": "4.2.3", "raw-loader": "^4.0.2", "typescript": "^5.9.3" }, diff --git a/documentation/docs/pages/_app.tsx b/documentation/docs/pages/_app.tsx index b3fbeaf252..f76d9a47ef 100644 --- a/documentation/docs/pages/_app.tsx +++ b/documentation/docs/pages/_app.tsx @@ -10,7 +10,11 @@ const MyApp: React.FC = ({ Component, pageProps }) => { palette: { mode: 'dark', primary: { - main: '#e67300', + main: '#85E89D', + }, + background: { + default: '#242B2D', + paper: '#2A3235', }, }, }), diff --git a/documentation/docs/pages/apis/introduction.mdx b/documentation/docs/pages/apis/introduction.mdx index c2c521ba8c..176ed71489 100644 --- a/documentation/docs/pages/apis/introduction.mdx +++ b/documentation/docs/pages/apis/introduction.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym API Reference: Network Infrastructure" +description: "Interactive API documentation for Nym network infrastructure. Query node status, network topology, blockchain state & mixnet performance programmatically." +schemaType: "TechArticle" +section: "APIs" +lastUpdated: "2026-02-01" +--- + # Introduction This site contains interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet. diff --git a/documentation/docs/pages/developers/chain.md b/documentation/docs/pages/developers/chain.md index 58eed5ecaf..aa77836f61 100644 --- a/documentation/docs/pages/developers/chain.md +++ b/documentation/docs/pages/developers/chain.md @@ -1,3 +1,11 @@ +--- +title: "Nyx Blockchain & Nym Smart Contracts" +description: "Developer guide for interacting with the Nyx blockchain via Cosmos SDK. Covers CLI wallet setup, Cosmos Registry, Ledger Live, and RPC node deployment." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-02-11" +--- + # Interacting with Nyx Chain and Smart Contracts There are two options for interacting with the blockchain to send tokens or interact with deployed smart contracts: diff --git a/documentation/docs/pages/developers/chain/rpc-node.md b/documentation/docs/pages/developers/chain/rpc-node.md index ddd8afc538..091edcd56e 100644 --- a/documentation/docs/pages/developers/chain/rpc-node.md +++ b/documentation/docs/pages/developers/chain/rpc-node.md @@ -1,3 +1,11 @@ +--- +title: "Run a Nyx RPC Node for the Nym Network" +description: "Set up and run a dedicated RPC node for the Nyx blockchain. Query network state, serve chain data, and interact with Nym smart contracts programmatically." +schemaType: "HowTo" +section: "Developers" +lastUpdated: "2026-02-01" +--- + # RPC Nodes RPC Nodes (which might otherwise be referred to as 'Lite Nodes' or just 'Full Nodes') differ from Validators in that they hold a copy of the Nyx blockchain, but do **not** participate in consensus / block-production. diff --git a/documentation/docs/pages/developers/index.mdx b/documentation/docs/pages/developers/index.mdx index f1cdd88680..a6e1d197f1 100644 --- a/documentation/docs/pages/developers/index.mdx +++ b/documentation/docs/pages/developers/index.mdx @@ -1,2 +1,10 @@ +--- +title: "Nym Developer Portal: SDKs & Tools" +description: "Developer documentation for building privacy-enhanced applications on the Nym mixnet. Covers Rust SDK, TypeScript SDK, blockchain interaction & CLI tools." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-02-01" +--- + # Introduction Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs. diff --git a/documentation/docs/pages/developers/nymvpncli.mdx b/documentation/docs/pages/developers/nymvpncli.mdx index b3e915d854..660c7fd8e6 100644 --- a/documentation/docs/pages/developers/nymvpncli.mdx +++ b/documentation/docs/pages/developers/nymvpncli.mdx @@ -1,3 +1,11 @@ +--- +title: "NymVPN CLI: Run NymVPN from the Command Line" +description: "Install and run NymVPN from the terminal on Linux, macOS, and Windows. Requires Rust and Go. Includes mnemonic generation and testnet configuration." +schemaType: "HowTo" +section: "Developers" +lastUpdated: "2026-02-11" +--- + import { Callout } from 'nextra/components' # Nym VPN CLI diff --git a/documentation/docs/pages/developers/rust.mdx b/documentation/docs/pages/developers/rust.mdx index d39707bb44..08c93ccc12 100644 --- a/documentation/docs/pages/developers/rust.mdx +++ b/documentation/docs/pages/developers/rust.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym Rust SDK: Privacy Apps for the Mixnet" +description: "Rust SDK reference for building privacy applications on the Nym mixnet. Covers TcpProxy, Mixnet module, Client Pool, FFI bindings, and code examples." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-02-01" +--- + # Introduction The Rust SDK allows exposes a few different modules, some more plug and play than others. Each of which handles exposes a Nym Client, which handles finding and using a route for packets through the Mixnet, encryption, and cover traffic, all under the hood. diff --git a/documentation/docs/pages/developers/rust/mixnet.mdx b/documentation/docs/pages/developers/rust/mixnet.mdx index 0524619874..2fc45fee11 100644 --- a/documentation/docs/pages/developers/rust/mixnet.mdx +++ b/documentation/docs/pages/developers/rust/mixnet.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym Rust SDK: Mixnet Messaging Module" +description: "Use the Nym Rust SDK Mixnet module to send messages through the mixnet. Covers builder patterns, custom topologies, SOCKS proxy, and anonymous replies." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-02-01" +--- + # Mixnet Module import { Callout } from 'nextra/components'; diff --git a/documentation/docs/pages/developers/rust/tcpproxy.mdx b/documentation/docs/pages/developers/rust/tcpproxy.mdx index d34d4f0a37..e8fb886374 100644 --- a/documentation/docs/pages/developers/rust/tcpproxy.mdx +++ b/documentation/docs/pages/developers/rust/tcpproxy.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym TcpProxy: Route TCP via the Mixnet" +description: "Route TCP traffic through the Nym mixnet using the TcpProxy Rust module. Covers architecture, single and multi-connection patterns, and troubleshooting." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-02-11" +--- + # TcpProxy Module import { Callout } from 'nextra/components'; diff --git a/documentation/docs/pages/developers/tools.mdx b/documentation/docs/pages/developers/tools.mdx index 9f0299c333..ecfec9bd48 100644 --- a/documentation/docs/pages/developers/tools.mdx +++ b/documentation/docs/pages/developers/tools.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym Developer Tools: CLI, Echo & TcpProxy" +description: "Overview of Nym developer tools including nym-cli for blockchain interaction, echo server for traffic testing, and standalone TcpProxy binary downloads." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-02-01" +--- + # Tools There are a few tools available to developers for chain interaction: the `nym-cli` tool, which operates as an easier-to-use wrapper around `nyxd`, to allow operators to script interactions with their infrastructure (and those who prefer CLI tools). diff --git a/documentation/docs/pages/developers/tools/_meta.json b/documentation/docs/pages/developers/tools/_meta.json index 8aa37f3c3b..d2efea2a75 100644 --- a/documentation/docs/pages/developers/tools/_meta.json +++ b/documentation/docs/pages/developers/tools/_meta.json @@ -1,5 +1,6 @@ { "nym-cli": "Nym-cli", + "diagnostic-tool": "Diagnostic Tool", "echo-server": "Echo Server", "standalone-tcpproxy": "TcpProxy Binaries (Standalone)" } diff --git a/documentation/docs/pages/developers/tools/diagnostic-tool.mdx b/documentation/docs/pages/developers/tools/diagnostic-tool.mdx new file mode 100644 index 0000000000..dec7b67cf2 --- /dev/null +++ b/documentation/docs/pages/developers/tools/diagnostic-tool.mdx @@ -0,0 +1,134 @@ +import { Steps } from 'nextra/components'; + +# Diagnostic Tool + +The Diagnostic Tool is a standalone binary designed to perform various network tests, including DNS, HTTP, and gateway connectivity tests. This tool helps diagnose connectivity issues and provides insights into network performance. + +It’s also possible to run it within the daemon with the same CLI interface. + +## Download Binary + +To get `nym-diagnostic` follow these steps: + +###### 1. Download `nym-vpn-core` +- Navigate to [github.com/nymtech/nym-vpn-client/releases](https://github.com/nymtech/nym-vpn-client/releases) +- Find latest `nym-vpn-core-` +- Download version for your system + +###### 2. Install or extract and make executable + +- If you downloaded `.deb` installer, install it with this command: +```sh +sudo dpkg -i +``` + +- If you downloaded `.tar.gz`, in terminal you can extract the file with +```sh +tar -xvf +``` + +- Navigate inside the directory and make executable: +```sh +cd nym-vpn-core- +chmod +x ./* +``` + + +## CLI Usage + +The Diagnostic Tool can be executed from the command line interface (CLI). Below are the usage instructions and options available. Read in the chapter [*Tests Performed*](#tests-performed) about the purpose and outcome of these commands. + +### Command Syntax + +```sh +./nym-diagnostic [command] [options] +./nym-vpnc diagnostic [command] [options] +``` + +#### `run` command arguments + +The most useful command is `run`, here are the options for that command: + +```sh +-h, --help Display help information and exit. +--skip-dns Skip the DNS tests +--skip-http Skip the HTTP tests +--gateway Run the gateway connectivity test on the given gateway. Skip those tests if not provided +-v, --verbose Enable verbose output for detailed logging. +``` + +#### `register` command arguments + +Command `register` requires valid credential. Here are the options for that command: + +```sh +--gateway Register to the given gateway +--storage-path Path to the directory containing the credentials database. If it is not valid registration will be skipped. +--skip-wireguard Skip Wireguard tests +``` + +### Command Examples + + +- Run all tests on a gateway: +```sh +./nym-diagnostic run --gateway +``` + +- Run the DNS tests only: +```sh +./nym-diagnostic run --skip-http +``` + +- Register to a gateway: +```sh +sudo ./nym-diagnostic register --gateway --storage-path /var/lib/nym-vpnd/mainnet +# sudo is required to read the database +``` + +- You can also run DNS and HTTP tests from `nym-vpnc` (installation [here](/developers/nymvpncli)): +```sh +./nym-vpnc diagnostic run​ +``` + + +## Tests Performed + +The Diagnostic Tool runs the following tests: + + +### 1. DNS Test + +- **Purpose**: To check the resolution DNS availability. +- **Process**: We try to resolve all the domain names present in a given nym network environment with different DNS configurations +- **Output**: Displays the resolved IP address and the time taken for the resolution. + + +### 2. HTTP Test + +- **Purpose**: To verify the accessibility of the NymVPN API. +- **Process**: The tool query the `health` endpoint as well as the `nodes/described` endpoint. +- **Output**: Displays the response of the `health` endpoint, the time skew and the number of nodes in the network (sanity check) + +### 3. Gateway Test + +- **Purpose**: To check the connectivity to a given gateway. +- **Process**: The tool fetches information about the gateway, then establishes a TCP connection, upgrades it to WS and sends a request +- **Output**: Display the gateway reported information, the status of the connections and the WS response. + +### 4. Registration Test + +- **Purpose:** To check the correctness of the registration process. +- **Process:** The tool tries to build a mixnet client to the provided gateway and then tries to register to the entry authenticator +- **Output:** Display the status of the different steps +- **Caveat:** This test requires a credential to be spent, which is why it is available as a separate command only + +### 5. Wireguard Test + +- **Purpose:** To check the soundness of a wireguard connection +- **Process:** The tool uses the registration data from the previous step to establish a wireguard connection and ping an IP. +- **Output:** Display the ping RTTs and any error that might have happened + +## Reports + +Reports are logged in a JSON format and also returned by the commands for a future use \ No newline at end of file diff --git a/documentation/docs/pages/developers/tools/nym-cli.md b/documentation/docs/pages/developers/tools/nym-cli.md index e2be8ea3ef..e8f4f56903 100644 --- a/documentation/docs/pages/developers/tools/nym-cli.md +++ b/documentation/docs/pages/developers/tools/nym-cli.md @@ -1,3 +1,11 @@ +--- +title: "Nym CLI: Mixnet & Blockchain Commands" +description: "Use nym-cli to interact with the Nym mixnet and Nyx blockchain. Manage nodes, delegate stake, and query network state from the command line." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-02-11" +--- + # Nym-CLI This is a CLI tool for interacting with: diff --git a/documentation/docs/pages/developers/typescript.mdx b/documentation/docs/pages/developers/typescript.mdx index 3cf683f0f4..18929c5f1b 100644 --- a/documentation/docs/pages/developers/typescript.mdx +++ b/documentation/docs/pages/developers/typescript.mdx @@ -1,3 +1,10 @@ +--- +title: "Nym TypeScript SDK: Privacy for Web Apps" +description: "TypeScript SDK for integrating web apps with the Nym mixnet. Covers mixFetch, Mixnet Client, Smart Contracts, and Cosmos Kit with live playground examples." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-02-11" +--- # Introduction diff --git a/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx b/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx index 4bc35df9a1..5f12f9ae12 100644 --- a/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx +++ b/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx @@ -13,7 +13,7 @@ Sounds great, are there any catches? Well, there are a few (for now): - For now, `mixfetch` doesn't work with SURBS, although this will change in the future. -- For now, `mixFetch` cannot deal with concurrent requests with the same base URL. +- `mixFetch` supports concurrent requests (up to 10) to either different URLs on the same domain or different domains. Duplicate requests to the exact same URL will be deduplicated. Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so. @@ -33,6 +33,7 @@ curl -X 'GET' \ import type { SetupMixFetchOps } from '@nymproject/mix-fetch'; const mixFetchOptions: SetupMixFetchOps = { + clientId: "my-mixfetch-client", // explicit ID to avoid stale default IndexedDB storage preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", // with WSS mixFetchOverride: { requestTimeoutMs: 60_000, @@ -78,7 +79,7 @@ import { mixFetch } from '@nymproject/mix-fetch-full-fat'; ##### Example: using the `mixFetch` client: -`Get` and `Post` outputs will be observable from your console. +`Get`, `Post`, and `Concurrent` outputs will be observable from your console. MixFetch auto-initializes on the first request. Individual concurrent results are logged as they arrive. ```ts import './App.css'; @@ -86,6 +87,7 @@ import { mixFetch, SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat'; import React from 'react'; const mixFetchOptions: SetupMixFetchOps = { + clientId: "my-mixfetch-client", // explicit ID to avoid stale default IndexedDB storage preferredGateway: '23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb', // with WSS preferredNetworkRequester: 'HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb', @@ -103,7 +105,7 @@ export function HttpGET() { const response = await mixFetch('https://nym.com/favicon.svg', { mode: 'unsafe-ignore-cors' }, mixFetchOptions); const text = await response.text(); console.log('response was', text); - setHtml(html); + setHtml(text); } return ( @@ -146,11 +148,63 @@ export function HttpPOST() { ); } +// Send 5 concurrent requests to different URLs on the same domain using Promise.all +export function HttpConcurrent() { + const [results, setResults] = React.useState([]); + const [busy, setBusy] = React.useState(false); + + async function fetchConcurrent() { + const baseUrl = 'https://jsonplaceholder.typicode.com/posts/'; + const count = 5; + setBusy(true); + setResults([]); + console.log(`Starting ${count} concurrent requests to ${baseUrl}1-${count}...`); + + try { + // Fire off all requests at once with Promise.all + const requests = Array.from({ length: count }, (_, i) => { + const url = `${baseUrl}${i + 1}`; + return mixFetch(url, { mode: 'unsafe-ignore-cors' }, mixFetchOptions) + .then((res) => res.json()) + .then((json: { id: number; title: string }) => { + const entry = `[${json.id}] ${json.title}`; + console.log(entry); + return entry; + }); + }); + + const allResults = await Promise.all(requests); + setResults(allResults); + console.log('All concurrent requests completed!', allResults); + } catch (err) { + console.error('Concurrent fetch error:', err); + } finally { + setBusy(false); + } + } + + return ( + <> + + {results.length > 0 && ( +
    + {results.map((r, i) => ( +
  • {r}
  • + ))} +
+ )} + + ); +} + export default function App() { return ( <> + ); } diff --git a/documentation/docs/pages/index.mdx b/documentation/docs/pages/index.mdx index cac9844c41..fac51a908b 100644 --- a/documentation/docs/pages/index.mdx +++ b/documentation/docs/pages/index.mdx @@ -1,3 +1,12 @@ +--- +title: "Nym Docs: Guides, SDKs & Architecture" +description: "Official Nym documentation hub. Build privacy-enhanced applications, run mixnet nodes, and explore the network architecture and protocols powering NymVPN." +schemaType: "TechArticle" +section: "Documentation" +lastUpdated: "2026-02-11" +layout: raw +--- + import { LandingPage } from '../components/landing-page.tsx' diff --git a/documentation/docs/pages/network/index.md b/documentation/docs/pages/network/index.md index 2fc3a1a583..0cec22555c 100644 --- a/documentation/docs/pages/network/index.md +++ b/documentation/docs/pages/network/index.md @@ -1,3 +1,11 @@ +--- +title: "Nym Network Architecture: How the Mixnet Works" +description: "Deep dive into Nym network architecture, cryptographic systems, and how the mixnet provides network-level privacy against end-to-end attackers." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-02-11" +--- + # Introduction Nym's network documentation covering network architecture, node types, tokenomics, and crypto systems. diff --git a/documentation/docs/pages/operators/binaries/building-nym.mdx b/documentation/docs/pages/operators/binaries/building-nym.mdx index f730cfcdd8..8a4da7b3aa 100644 --- a/documentation/docs/pages/operators/binaries/building-nym.mdx +++ b/documentation/docs/pages/operators/binaries/building-nym.mdx @@ -1,3 +1,12 @@ +--- +title: "Building Nym from Source: Linux, macOS & Windows" +description: "How to build Nym platform binaries from source code. Covers dependencies for Debian, Arch, macOS, and Windows. Requires Rust toolchain and Git." +schemaType: "HowTo" +section: "Operators" +lastUpdated: "2026-02-01" +breadcrumbLabel: "Building from Source" +--- + import { Callout } from 'nextra/components'; import { VarInfo } from 'components/variable-info.tsx'; diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index 677cdaafc9..ae0666d813 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym Node Changelog & Release History" +description: "Complete changelog for Nym node releases, binary updates, SDK changes, and network upgrades. Sorted newest first with links to relevant documentation." +schemaType: "TechArticle" +section: "Operators" +lastUpdated: "2026-02-01" +--- + import { Callout } from 'nextra/components'; import { Tabs } from 'nextra/components'; import { MyTab } from 'components/generic-tabs.tsx'; @@ -49,6 +57,37 @@ This page displays a full list of all the changes during our release cycle from +## `v2026.4-quark` + +- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2026.4-quark) +- [`nym-node`](nodes/nym-node.mdx) version `1.26.0` + +```sh +nym-node +Binary Name: nym-node +Build Timestamp: 2026-02-24T13:43:24.098285047Z +Build Version: 1.26.0 +Commit SHA: a2081af6038ef3ef40b3d9368299d2676a2fbb6a +Commit Date: 2026-02-24T12:02:35.000000000+01:00 +Commit Branch: HEAD +rustc Version: 1.91.1 +rustc Channel: stable +cargo Profile: release +``` + +### Operator & Developer Updates + +### Features + +- [Stateless handshake improvements for LP Gateway](https://github.com/nymtech/nym/pull/6437) +- [HTTP & DNS improvements](https://github.com/nymtech/nym/pull/6423) +- [Endpoint support for exit gateway IPs](https://github.com/nymtech/nym/pull/6418) + +### Tools + +- **Diagnostic Tool** - a standalone binary for network diagnostics. It performs DNS, HTTP, and gateway connectivity tests, helping developers identify connectivity issues and monitor network performance. It can also be run via the daemon CLI. Read the full guide [here](https://nym.com/docs/developers/tools/diagnostic-tool). +- **Socks5 Score Calculation** - performed by the Gateway probe, which tests `nym-node --mode exit-gateway` instances over Socks5. The probe assigns a latency-based rating: high, medium, low, or offline. Full guide [here](https://nym.com/docs/operators/performance-and-testing#socks5-score-calculation-process). + ## `v2026.3-parmigiano` - [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2026.3-parmigiano) - [`nym-node`](nodes/nym-node.mdx) version `1.25.0` @@ -66,9 +105,9 @@ rustc Channel: stable cargo Profile: release ``` -### Key updates for operators include: +### Operator & Developer Updates -LP Gateway and Client fixes: +### Features - [Registration client now properly supports fallback](https://github.com/nymtech/nym/pull/6419) - [Exposed WireGuard PSK for vpn-client](https://github.com/nymtech/nym/pull/6411) @@ -77,28 +116,14 @@ LP Gateway and Client fixes: Note: This code is currently deactivated and doesn’t involve any changes for operators right now, but it will in the future. -Mixnet & Networking Enhancements: - -- [NS API Socks5 support](https://github.com/nymtech/nym/pull/6361) -- [Two-step dvpn registration flow](https://github.com/nymtech/nym/pull/6386) -- [DVPN PSK injection](https://github.com/nymtech/nym/pull/6378) - -Security & Encoding Improvements: - -- [Hex-encoding for LP key digests](https://github.com/nymtech/nym/pull/6394) -- [Encrypted KKT](https://github.com/nymtech/nym/pull/6331) -- [Reject packets with incompatible versions](https://github.com/nymtech/nym/pull/6326) - -Bugfixes & Quality-of-Life: +### Bugfix - [Share IP allocation fixes](https://github.com/nymtech/nym/pull/6395) - [Mixnet registration fixes](https://github.com/nymtech/nym/pull/6356) -- [Small QoL changes](https://github.com/nymtech/nym/pull/6340) -Chores & Maintenance: +### Refactors & Maintenance - [Cleanup x25519/ed22519 usage](https://github.com/nymtech/nym/pull/6335) -- [Upgrade to def_guard_wireguard v0.8.0](https://github.com/nymtech/nym/pull/6315) ## `v2026.2-oscypek` @@ -130,7 +155,7 @@ Secondly, the outcome of [NIP-7: Nym Exit Policy Update - Opening Ports for Stea 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.** +To simplify this, we made **a build in command, which operators must run after getting the new binary, but before restarting the node.** These are the steps to follow: @@ -154,7 +179,7 @@ chmod +x nym-node ```sh systemctl restart nym-node ``` -- Additionaly look for starus or serivice journal +- Additionally look for status or service journal ```sh service nym-node status # or @@ -1294,7 +1319,7 @@ cargo Profile: release - [Listen for shutdown signals during nym-node startup](https://github.com/nymtech/nym/pull/5879): This is to avoid situation where the process can't be killed without 'kill -9' because the logic to listen to shutdown signals hasn't been hit yet -### Bugfixes +### Bugfix - [Don't allow mixnode running in exit mode](https://github.com/nymtech/nym/pull/5898) diff --git a/documentation/docs/pages/operators/community-counsel/legal.mdx b/documentation/docs/pages/operators/community-counsel/legal.mdx index 6edc156904..341cbb0141 100644 --- a/documentation/docs/pages/operators/community-counsel/legal.mdx +++ b/documentation/docs/pages/operators/community-counsel/legal.mdx @@ -1,3 +1,11 @@ +--- +title: "Legal Guide for Nym Exit Gateway Operators" +description: "Legal considerations for running a Nym exit gateway. Covers ISP communication templates, jurisdiction guidance, and abuse report response strategies." +schemaType: "TechArticle" +section: "Operators" +lastUpdated: "2026-02-01" +--- + import { Callout } from 'nextra/components'; import { Tabs } from 'nextra/components'; import { RunTabs } from 'components/operators/nodes/node-run-command-tabs'; diff --git a/documentation/docs/pages/operators/community-counsel/templates.mdx b/documentation/docs/pages/operators/community-counsel/templates.mdx index c701f1a003..b27ceabc71 100644 --- a/documentation/docs/pages/operators/community-counsel/templates.mdx +++ b/documentation/docs/pages/operators/community-counsel/templates.mdx @@ -1,3 +1,11 @@ +--- +title: "Email & Legal Templates for Nym Node Operators" +description: "Ready-to-use templates for communicating with VPS providers about running Nym nodes. Includes introduction emails and abuse report response templates." +schemaType: "TechArticle" +section: "Operators" +lastUpdated: "2026-02-01" +--- + import { Callout } from 'nextra/components'; import { Tabs } from 'nextra/components'; import { RunTabs } from 'components/operators/nodes/node-run-command-tabs'; diff --git a/documentation/docs/pages/operators/faq/general-faq.mdx b/documentation/docs/pages/operators/faq/general-faq.mdx index 400c9c0a56..a31128de59 100644 --- a/documentation/docs/pages/operators/faq/general-faq.mdx +++ b/documentation/docs/pages/operators/faq/general-faq.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym Node Operator FAQ: Questions Answered" +description: "Frequently asked questions about running Nym nodes. Covers VPS selection, staking requirements, node configuration, rewards, and community resources." +schemaType: "FAQPage" +section: "Operators" +lastUpdated: "2026-02-01" +--- + # General Operators FAQ ## Nym Network @@ -16,8 +24,8 @@ Yes, there are.. **Built by community** -* [ExploreNYM](https://explorenym.net/) -* [Mixplorer](https://mixplorer.xyz/) +* [SpectreDAO Explorer](https://explorer.nym.spectredao.net/dashboard) +* [Nymesis](https://nymesis.vercel.app) ### Which VPS providers would you recommend? diff --git a/documentation/docs/pages/operators/introduction.mdx b/documentation/docs/pages/operators/introduction.mdx index 8a564662fe..1af11b5098 100644 --- a/documentation/docs/pages/operators/introduction.mdx +++ b/documentation/docs/pages/operators/introduction.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym Node Operator Guide & Prerequisites" +description: "Introduction to running Nym nodes. Learn requirements, skill expectations, time commitment, and how to get started operating a node on the Nym mixnet." +schemaType: "TechArticle" +section: "Operators" +lastUpdated: "2026-02-11" +--- + import QuickStart from 'components/operators/snippets/quick-start.mdx' # Introduction diff --git a/documentation/docs/pages/operators/nodes.mdx b/documentation/docs/pages/operators/nodes.mdx index 5deb625aea..630aed0838 100644 --- a/documentation/docs/pages/operators/nodes.mdx +++ b/documentation/docs/pages/operators/nodes.mdx @@ -1,3 +1,11 @@ +--- +title: "How to Set Up & Run a Nym Node on the Mixnet" +description: "Step-by-step guide to installing, configuring, and running a nym-node on the Nym mixnet. Covers prerequisites, staking requirements, and CLI setup." +schemaType: "HowTo" +section: "Operators" +lastUpdated: "2026-02-01" +--- + import { Callout } from 'nextra/components'; import { Steps } from 'nextra/components'; import { Tabs } from 'nextra/components'; diff --git a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx index d791e7f946..5ea51cd7fa 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym Node Configuration & Systemd Setup" +description: "Configure your nym-node with systemd automation, reverse proxy, IPv6, and custom settings. Includes service file templates and maintenance tips." +schemaType: "TechArticle" +section: "Operators" +lastUpdated: "2026-02-01" +--- + import { Callout } from 'nextra/components'; import { Tabs } from 'nextra/components'; import { VarInfo } from 'components/variable-info.tsx'; diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index 1f367a85fc..a90b687a6f 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -21,17 +21,16 @@ 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: 2026-01-27T14:54:15.579821601Z -Build Version: 1.24.0 -Commit SHA: 83bf9dc7cc2b01f65cab671733f2bf6c3abd471d -Commit Date: 2026-01-27T15:46:52.000000000+01:00 +Build Timestamp: 2026-02-24T13:43:24.098285047Z +Build Version: 1.26.0 +Commit SHA: a2081af6038ef3ef40b3d9368299d2676a2fbb6a +Commit Date: 2026-02-24T12:02:35.000000000+01:00 Commit Branch: HEAD rustc Version: 1.91.1 rustc Channel: stable cargo Profile: release ``` - Detailed version archive and release notes is documented [here](../../changelog.mdx). {/* COMMENTING THIS OUT ASS WE HAVE TO FIGURE OUT HOW TO SHOW THE LATEST VERSION FROM MASTER BRANCH diff --git a/documentation/docs/pages/operators/performance-and-testing.mdx b/documentation/docs/pages/operators/performance-and-testing.mdx index 317e4f0978..b6846e2dc8 100644 --- a/documentation/docs/pages/operators/performance-and-testing.mdx +++ b/documentation/docs/pages/operators/performance-and-testing.mdx @@ -1,4 +1,13 @@ +--- +title: "Nym Node Performance Monitoring & Testing Guide" +description: "Monitor your Nym node performance with Prometheus, Grafana, and community tools. Covers key metrics, routing score analysis, and testing best practices." +schemaType: "TechArticle" +section: "Operators" +lastUpdated: "2026-02-01" +--- + import { Callout } from 'nextra/components'; +import { Steps } from 'nextra/components'; import { Tabs } from 'nextra/components'; import { MyTab } from 'components/generic-tabs.tsx'; import { AccordionTemplate } from 'components/accordion-template.tsx'; @@ -7,11 +16,11 @@ import NodePerfMixnet from 'components/operators/snippets/node-perf-mixnet.mdx'; # Performance Monitoring & Testing -As Nym developers constantly improve the software, the role of Node Operators is to keep their nodes up to date, monitor their performance and share feedback with the rest of the community and Nym team. Node performance measurements and [server monitoring](#monitoring) are an essential pillar of our common work. +As Nym developers constantly improve the software, the role of Node Operators is to keep their nodes up to date, monitor their performance and share feedback with the rest of the community and Nym team. Node performance measurements and [server monitoring](#monitoring) are an essential pillar of our common work. -Nym Network is routed either through the Mixnet (5-hop) or through Wireguard (2-hop). In all cases Nym node operators always employ only one binary called [`nym-node`](/operators/nodes/nym-node). Through provided arguments (or changes in the config file), `nym-node` can be utilised for different [functionalities](/operators/nodes/nym-node/setup#functionality-mode). However, once it's [registered to Nym Network](/operators/nodes/nym-node/bonding) it's by default available for Nym Mixnet not for Wireguard routing. Only nodes with Wireguard enabled, are also available for Wireguard routing. This creates a situation where every Wireguard enabled `nym-node` is required to have a solid performance score in Mixnet to begin with, but not every Mixnet routing `nym-node` must have Wireguard enabled. +Nym Network is routed either through the Mixnet (5-hop) or through Wireguard (2-hop). In all cases Nym node operators always employ only one binary called [`nym-node`](/operators/nodes/nym-node). Through provided arguments (or changes in the config file), `nym-node` can be utilised for different [functionalities](/operators/nodes/nym-node/setup#functionality-mode). However, once it's [registered to Nym Network](/operators/nodes/nym-node/bonding) it's by default available for Nym Mixnet not for Wireguard routing. Only nodes with Wireguard enabled, are also available for Wireguard routing. This creates a situation where every Wireguard enabled `nym-node` is required to have a solid performance score in Mixnet to begin with, but not every Mixnet routing `nym-node` must have Wireguard enabled. -Given this complexity, we divided the part below about perfromance calculation logic and node selection into two parallel tabs: Mixnet and Wireguard. +Given this complexity, we divided the part below about performance calculation logic and node selection into two parallel tabs: Mixnet and Wireguard.
/api/v1/roles ``` +## Socks5 Score Calculation + +Gateway probe also runs tests through a Network requester - a module build as a part of `nym-node`, active only in mode Exit Gateway, used for [Socks5](/developers/clients/socks5) proxy TCP connection. + +Socks5 score is displayed in [Nym Node Status Observatory](https://harbourmaster.nymtech.net) (if you open a page with a particular gateway) and in detail it can be previewed at [mainnet-node-status-api.nymtech.cc/dvpn/v1/directory/gateways](https://mainnet-node-status-api.nymtech.cc/dvpn/v1/directory/gateways) or when running own instance of [Gateway probe](/operators/performance-and-testing/gateway-probe). + +### Socks5 Score Calculation Process + +Socks5 score is defined in the json output of Gateway probe as `"socks5"` key. Here is an example of the dictionary: + +```json + "socks5": { + "can_proxy_https": true, + "score": "medium", + "errors": null + } +``` + +> Note: When we write *gateway* we refer to a `nym-node --mode exit-gateway` in this sub-chapter. + + +1. Gateway gets probed as part of a Gateway probe test where other components get tested as well + +2. Probe tries to connect to the Gateway through Socks5 10 times per testing instance + +3. Latency is calculated as an average of the successful attempts + +4. Gateway is scored as `"low"`, `"medium"`, `"high"` or `"offline"`, in numbers it means: + - `"offline"`: Gateway failed the test 3 or more times (out of 10 attempts) + - `"high"`: Top 50% of nodes with lowest average latency + - `"medium"`: Following 25% of nodes with lowest average latency below top 50% nodes + - `"low"`: Remaining 25% of nodes with the highest average latency time + + ## Monitoring There are multiple ways to monitor performance of nodes and the machines on which they run. For the purpose of maximal privacy and decentralisation of the data - preventing Nym Mixnet from any global adversary takeover - we created these pages as a source of mutual empowerment, a place where operators can share and learn new skills to **setup metrics monitors on their own infrastructure**. ### Guides to Setup Own Metrics -A list of different scripts, templates and guides for easier navigation: +A list of different tools, templates and guides for easier navigation: + +* [`nym-gateway-probe`](performance-and-testing/gateway-probe.mdx): a useful tool used under the hood of [Node Status Observatory](https://harbourmaster.nymtech.net) + +* [Diagnostic Tool](/developers/tools/diagnostic-tool): diagnose connectivity issues and provides insights into network performance -* [`nym-gateway-probe`](performance-and-testing/gateway-probe.mdx) - a useful tool used under the hood of [Node Status Observatory](https://harbourmaster.nymtech.net) * [Prometheus and Grafana](performance-and-testing/prometheus-grafana.mdx) self-hosted setup -* [Nym-node CPU cron service](https://gist.github.com/tommyv1987/97e939a7adf491333d686a8eaa68d4bd) - an easy bash script by Nym core developer [@tommy1987](https://gist.github.com/tommyv1987), designed to monitor a CPU usage of your node, running locally -* Nym's script [`prom_targets.py`](https://github.com/nymtech/nym/blob/develop/scripts/prom_targets.py) - a useful python program to request data from API and can be run on its own or plugged to more sophisticated flows ### Collecting Testing Metrics @@ -93,4 +137,4 @@ We do testing in order to **understand and increase the overall quality of the N 7. Adjust rewarding based on the machine specs and server pricing Visit [Nym Harbour Master](https://harbourmaster.nymtech.net/) monitoring page to monitor network components (nodes) performance. -*/} \ No newline at end of file +*/} diff --git a/documentation/docs/pages/operators/sandbox.mdx b/documentation/docs/pages/operators/sandbox.mdx index a46d76a35c..20a4d97b85 100644 --- a/documentation/docs/pages/operators/sandbox.mdx +++ b/documentation/docs/pages/operators/sandbox.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym Sandbox Testnet for Node Operators" +description: "Run your Nym node in the Sandbox testnet environment. Test configurations, try new features, and experiment safely before deploying to mainnet." +schemaType: "HowTo" +section: "Operators" +lastUpdated: "2026-02-01" +--- + import { Callout } from 'nextra/components'; import { VarInfo } from 'components/variable-info.tsx'; import { Steps } from 'nextra/components';import { Tabs } from 'nextra/components' diff --git a/documentation/docs/pages/operators/tools.mdx b/documentation/docs/pages/operators/tools.mdx index c29ecd6038..77ffef91dc 100644 --- a/documentation/docs/pages/operators/tools.mdx +++ b/documentation/docs/pages/operators/tools.mdx @@ -202,3 +202,13 @@ PING_RETRIES=10 PING_TIMEOUT=5 CONCURRENCY=16 ./test-nodes-pings.sh You can look up the IPs from `ping_not_working.csv`, using some online database, like [ipinfo.io](https://ipinfo.io). Feel invited to share the outcome with Nym team, mentors and the rest of the operators in our [Matrix Node Operators channel](https://matrix.to/#/#operators:nymtech.chat). + +## Guides to Setup Own Metrics + +A list of different tools, templates and guides for easier navigation: + +* [`nym-gateway-probe`](performance-and-testing/gateway-probe.mdx): a useful tool used under the hood of [Node Status Observatory](https://harbourmaster.nymtech.net) + +* [Diagnostic Tool](/developers/tools/diagnostic-tool): diagnose connectivity issues and provides insights into network performance + +* [Prometheus and Grafana](performance-and-testing/prometheus-grafana.mdx) self-hosted setup \ No newline at end of file diff --git a/documentation/docs/pages/operators/troubleshooting/nodes.mdx b/documentation/docs/pages/operators/troubleshooting/nodes.mdx index 45b7b9793e..91cdbd6419 100644 --- a/documentation/docs/pages/operators/troubleshooting/nodes.mdx +++ b/documentation/docs/pages/operators/troubleshooting/nodes.mdx @@ -1,3 +1,11 @@ +--- +title: "Nym Node Troubleshooting: Common Errors & Fixes" +description: "Solutions for common nym-node issues including build failures, connectivity problems, and configuration errors. Includes error messages and fix steps." +schemaType: "TechArticle" +section: "Operators" +lastUpdated: "2026-02-01" +--- + import { Tabs } from 'nextra/components'; import { Callout } from 'nextra/components'; import { VarInfo } from 'components/variable-info.tsx'; diff --git a/documentation/docs/pages/styles.css b/documentation/docs/pages/styles.css index aa26004976..0e5c829a30 100644 --- a/documentation/docs/pages/styles.css +++ b/documentation/docs/pages/styles.css @@ -1,3 +1,29 @@ +/* nym.com-aligned colour tokens */ +:root { + --colorPrimary: #85E89D; + --textPrimary: #FFFFFF; + --bg-dark: #1E2426; + --border-dark: #2E3538; +} + +/* dark mode background override */ +html.dark { + background-color: var(--bg-dark); +} + +html.dark body { + background-color: var(--bg-dark); +} + +/* nextra main content area bg */ +html.dark .nextra-nav-container, +html.dark .nextra-sidebar-container, +html.dark .nextra-content, +html.dark .nx-bg-white, +html.dark .dark\:nx-bg-dark { + background-color: var(--bg-dark) !important; +} + footer { text-align: center; } @@ -19,7 +45,7 @@ footer { padding-right: 0px; padding-left: 0px; /* text-align: right; */ - border-left: 1px solid #262626; + border-left: 1px solid var(--border-dark); } .nextra-content { @@ -34,14 +60,21 @@ footer { background-color: hsl(var(black) 100% 77%/0.1) !important; } -/* Sidebar buttons */ +/* Sidebar active item */ :is(html .dark\:nx-bg-primary-400\/10) { - background: var(--colorPrimary) !important; - color: var(--textPrimary) !important; + background: transparent !important; + border-left: 2px solid #85E89D; + color: #FFFFFF !important; +} + +:is(html:not(.dark) .dark\:nx-bg-primary-400\/10) { + background: transparent !important; + border-left: 2px solid #4A9E5C; + color: #242B2D !important; } .nextra-sidebar-container { - border-right: 1px solid #262626; + border-right: 1px solid var(--border-dark); width: 300px !important; } diff --git a/documentation/docs/pnpm-lock.yaml b/documentation/docs/pnpm-lock.yaml index b04428b429..cf11dd2d53 100644 --- a/documentation/docs/pnpm-lock.yaml +++ b/documentation/docs/pnpm-lock.yaml @@ -78,8 +78,8 @@ importers: specifier: '>=1.2.4-rc.2 || ^1' version: 1.4.1 '@nymproject/mix-fetch-full-fat': - specifier: '>=1.5.1-rc.0 || ^1.4.1' - version: 1.4.1 + specifier: ^1.4.2 + version: 1.4.2 '@nymproject/sdk-full-fat': specifier: '>=1.5.1-rc.0 || ^1.4.1' version: 1.4.1 @@ -141,6 +141,9 @@ importers: eslint-config-next: specifier: 13.4.13 version: 13.4.13(eslint@8.46.0)(typescript@5.9.3) + next-sitemap: + specifier: 4.2.3 + version: 4.2.3(next@15.5.10(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) raw-loader: specifier: ^4.0.2 version: 4.0.2(webpack@5.101.3) @@ -247,6 +250,9 @@ packages: react: '>=17' react-dom: '>=17' + '@corex/deepmerge@4.0.43': + resolution: {integrity: sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==} + '@cosmjs/amino@0.25.6': resolution: {integrity: sha512-9dXN2W7LHjDtJUGNsQ9ok0DfxeN3ca/TXnxCR3Ikh/5YqBqxI8Gel1J9PQO9L6EheYyh045Wff4bsMaLjyEeqQ==} @@ -1029,6 +1035,9 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + '@next/env@13.5.11': + resolution: {integrity: sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==} + '@next/env@15.5.10': resolution: {integrity: sha512-plg+9A/KoZcTS26fe15LHg+QxReTazrIOoKKUC3Uz4leGGeNPgLHdevVraAAOX0snnUs3WkRx3eUQpj9mreG6A==} @@ -1705,8 +1714,8 @@ packages: '@nymproject/contract-clients@1.4.1': resolution: {integrity: sha512-HuJZ4Hv+Rl6ZZEtCHKgurNLJapM+QQRJlGkevFH2a4UdqUqF9omUkUi3AVes4679dPoSFgvA7plyVSDBdbgV6w==} - '@nymproject/mix-fetch-full-fat@1.4.1': - resolution: {integrity: sha512-AMa21sEd9FELqAJe1lCyHPqxxbc13ApiJ1P/exAslQjiFPb/de/3Ow0FHqKGNPrwyVRS/T2pSzjQ3l8TddiEBA==} + '@nymproject/mix-fetch-full-fat@1.4.2': + resolution: {integrity: sha512-QHPwa7A+c/2VUm4Imq2I21toFiZhbZxcjHud1sFsE9hN5BWxZ+QJKV2bg9oBUzulzoQabsk48RA13/hqU7c4KA==} '@nymproject/sdk-full-fat@1.4.1': resolution: {integrity: sha512-dh5bvMUj3m8nEssvO8Nl66WpcJAjwRZrGNwqfczJWLG4nX3Vt95tPLv4v0/Z1W3DQWQFW6WmEPPYHNjl18V/fA==} @@ -3215,6 +3224,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -3390,8 +3404,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.19: - resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} hasBin: true bech32@1.1.4: @@ -3510,6 +3525,9 @@ packages: caniuse-lite@1.0.30001769: resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + caniuse-lite@1.0.30001774: + resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==} + cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} hasBin: true @@ -4022,8 +4040,8 @@ packages: duplexify@4.1.3: resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} - electron-to-chromium@1.5.286: - resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + electron-to-chromium@1.5.302: + resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} elkjs@0.9.3: resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} @@ -5355,8 +5373,8 @@ packages: modern-ahocorasick@1.1.0: resolution: {integrity: sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==} - motion-dom@12.34.0: - resolution: {integrity: sha512-Lql3NuEcScRDxTAO6GgUsRHBZOWI/3fnMlkMcH5NftzcN37zJta+bpbMAV9px4Nj057TuvRooMK7QrzMCgtz6Q==} + motion-dom@12.34.3: + resolution: {integrity: sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ==} motion-utils@12.29.2: resolution: {integrity: sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==} @@ -5404,6 +5422,13 @@ packages: react: '>=16.0.0' react-dom: '>=16.0.0' + next-sitemap@4.2.3: + resolution: {integrity: sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==} + engines: {node: '>=14.18'} + hasBin: true + peerDependencies: + next: '*' + next-themes@0.2.1: resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} peerDependencies: @@ -6709,8 +6734,8 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - webpack-sources@3.3.3: - resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} engines: {node: '>=10.13.0'} webpack@5.101.3: @@ -6982,6 +7007,8 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + '@corex/deepmerge@4.0.43': {} + '@cosmjs/amino@0.25.6': dependencies: '@cosmjs/crypto': 0.25.6 @@ -8213,6 +8240,8 @@ snapshots: '@tybys/wasm-util': 0.10.0 optional: true + '@next/env@13.5.11': {} + '@next/env@15.5.10': {} '@next/eslint-plugin-next@13.4.13': @@ -9307,7 +9336,7 @@ snapshots: '@nymproject/contract-clients@1.4.1': {} - '@nymproject/mix-fetch-full-fat@1.4.1': {} + '@nymproject/mix-fetch-full-fat@1.4.2': {} '@nymproject/sdk-full-fat@1.4.1': {} @@ -11810,9 +11839,9 @@ snapshots: dependencies: event-target-shim: 5.0.1 - acorn-import-phases@1.0.4(acorn@8.15.0): + acorn-import-phases@1.0.4(acorn@8.16.0): dependencies: - acorn: 8.15.0 + acorn: 8.16.0 acorn-jsx@5.3.2(acorn@8.15.0): dependencies: @@ -11820,6 +11849,8 @@ snapshots: acorn@8.15.0: {} + acorn@8.16.0: {} + agent-base@7.1.4: {} ajv-draft-04@1.0.0(ajv@8.17.1): @@ -12009,7 +12040,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.9.19: {} + baseline-browser-mapping@2.10.0: {} bech32@1.1.4: {} @@ -12079,9 +12110,9 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.19 - caniuse-lite: 1.0.30001769 - electron-to-chromium: 1.5.286 + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001774 + electron-to-chromium: 1.5.302 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -12135,6 +12166,8 @@ snapshots: caniuse-lite@1.0.30001769: {} + caniuse-lite@1.0.30001774: {} + cardinal@2.1.1: dependencies: ansicolors: 0.3.2 @@ -12672,7 +12705,7 @@ snapshots: readable-stream: 3.6.2 stream-shift: 1.0.3 - electron-to-chromium@1.5.286: {} + electron-to-chromium@1.5.302: {} elkjs@0.9.3: {} @@ -13171,7 +13204,7 @@ snapshots: framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - motion-dom: 12.34.0 + motion-dom: 12.34.3 motion-utils: 12.29.2 tslib: 2.8.1 optionalDependencies: @@ -14491,7 +14524,7 @@ snapshots: modern-ahocorasick@1.1.0: {} - motion-dom@12.34.0: + motion-dom@12.34.3: dependencies: motion-utils: 12.29.2 @@ -14530,6 +14563,14 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + next-sitemap@4.2.3(next@15.5.10(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + dependencies: + '@corex/deepmerge': 4.0.43 + '@next/env': 13.5.11 + fast-glob: 3.3.3 + minimist: 1.2.8 + next: 15.5.10(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-themes@0.2.1(next@15.5.10(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: next: 15.5.10(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -15781,7 +15822,7 @@ snapshots: terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.15.0 + acorn: 8.16.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -16171,7 +16212,7 @@ snapshots: webidl-conversions@3.0.1: {} - webpack-sources@3.3.3: {} + webpack-sources@3.3.4: {} webpack@5.101.3: dependencies: @@ -16181,8 +16222,8 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.15.0 - acorn-import-phases: 1.0.4(acorn@8.15.0) + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.1 chrome-trace-event: 1.0.4 enhanced-resolve: 5.19.0 @@ -16199,7 +16240,7 @@ snapshots: tapable: 2.3.0 terser-webpack-plugin: 5.3.16(webpack@5.101.3) watchpack: 2.5.1 - webpack-sources: 3.3.3 + webpack-sources: 3.3.4 transitivePeerDependencies: - '@swc/core' - esbuild diff --git a/documentation/docs/public/robots.txt b/documentation/docs/public/robots.txt new file mode 100644 index 0000000000..b3276b24e6 --- /dev/null +++ b/documentation/docs/public/robots.txt @@ -0,0 +1,15 @@ +# * +User-agent: * +Allow: / + +# * +User-agent: * +Disallow: /api/ +Disallow: /_next/ + +# Host +Host: https://nymtech.net/docs + +# Sitemaps +Sitemap: https://nymtech.net/docs/sitemap.xml +Sitemap: https://nymtech.net/docs/sitemap-docs.xml diff --git a/documentation/docs/public/sitemap-0.xml b/documentation/docs/public/sitemap-0.xml new file mode 100644 index 0000000000..a0ce4e8387 --- /dev/null +++ b/documentation/docs/public/sitemap-0.xml @@ -0,0 +1,5 @@ + + +https://nymtech.net/docs2026-02-25T10:35:41.122Zyearly0.6 +https://nymtech.net/docs/network2026-02-25T10:35:41.122Zyearly0.6 + \ No newline at end of file diff --git a/documentation/docs/public/sitemap.xml b/documentation/docs/public/sitemap.xml new file mode 100644 index 0000000000..becdf3489f --- /dev/null +++ b/documentation/docs/public/sitemap.xml @@ -0,0 +1,5 @@ + + +https://nymtech.net/docs/sitemap-0.xml +https://nymtech.net/docs/sitemap-docs.xml + \ No newline at end of file diff --git a/documentation/docs/theme.config.tsx b/documentation/docs/theme.config.tsx index 9d07c2ba75..8906294882 100644 --- a/documentation/docs/theme.config.tsx +++ b/documentation/docs/theme.config.tsx @@ -13,52 +13,129 @@ const config: DocsThemeConfig = { const image = url + "/images/Nym_meta_Image.png"; const favicon = url + "/favicon.svg"; - // Define descriptions for different "books" - const bookDescriptions: Record = { - "/developers": - "Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.", - "/network": - "Nym's network documentation covering network architecture, node types, tokenomics, and cryptography.", - "/operators": - "Nym's Operators guide containing information and setup guides for the various components of Nym network and Nyx blockchain validators.", - "/apis": - "Interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet.", - }; - const defaultDescription = "Nym is a privacy platform. It provides strong network-level privacy against sophisticated end-to-end attackers, and anonymous access control using blinded, re-randomizable, decentralized credentials."; - const topLevel = "/" + route.split("/")[1]; - const description = - config.frontMatter.description || - bookDescriptions[topLevel] || - defaultDescription; + // Frontmatter-first description + const description = config.frontMatter.description || defaultDescription; - const title = (route === "/" ? "Nym docs" : config.title + " - Nym docs"); + const baseTitle = config.frontMatter.title || config.title || ""; + const title = + route === "/" + ? "Nym Docs: Privacy Network Documentation" + : baseTitle.includes("| Nym Docs") + ? baseTitle + : `${baseTitle} | Nym Docs`; + + const pageUrl = `${url}${route}`; + + // Frontmatter fields + const section = config.frontMatter.section || ""; + const lastUpdated = config.frontMatter.lastUpdated || ""; + const schemaType = config.frontMatter.schemaType || "TechArticle"; + + // JSON-LD structured data + const org = { + "@id": "https://nym.com/#org", + "@type": "Organization", + name: "Nym Technologies SA", + url: "https://nym.com", + logo: { + "@id": "https://nym.com/#logo", + "@type": "ImageObject", + url: "https://nym.com/apple-touch-icon.png", + }, + sameAs: ["https://x.com/nymproject", "https://github.com/nymtech"], + }; + + const website = { + "@id": "https://nym.com/docs#website", + "@type": "WebSite", + name: "Nym Docs", + url: "https://nym.com/docs", + publisher: { "@id": "https://nym.com/#org" }, + }; + + const webpage = { + "@id": `${pageUrl}#webpage`, + "@type": "WebPage", + url: pageUrl, + name: title, + description: description, + inLanguage: "en", + isPartOf: { "@id": "https://nym.com/docs#website" }, + breadcrumb: { "@id": `${pageUrl}#breadcrumb` }, + potentialAction: { "@type": "ReadAction", target: pageUrl }, + }; + + const articleSchema: Record = { + "@id": `${pageUrl}#article`, + "@type": schemaType, + ...(schemaType === "HowTo" + ? { name: baseTitle } + : { headline: baseTitle }), + description: description, + url: pageUrl, + author: { "@id": "https://nym.com/#org" }, + publisher: { "@id": "https://nym.com/#org" }, + mainEntityOfPage: { "@id": `${pageUrl}#webpage` }, + ...(lastUpdated && { + datePublished: lastUpdated, + dateModified: lastUpdated, + }), + }; + + const pathParts = route.split("/").filter(Boolean); + const breadcrumb = { + "@id": `${pageUrl}#breadcrumb`, + "@type": "BreadcrumbList", + itemListElement: pathParts.map((part: string, i: number) => ({ + "@type": "ListItem", + position: i + 1, + name: + config.frontMatter.breadcrumbLabel && i === pathParts.length - 1 + ? config.frontMatter.breadcrumbLabel + : part.charAt(0).toUpperCase() + part.slice(1).replace(/-/g, " "), + item: `${url}/${pathParts.slice(0, i + 1).join("/")}`, + })), + }; + + const schema = { + "@context": "https://schema.org", + "@graph": [org, website, webpage, articleSchema, breadcrumb], + }; return ( <> {title} - + - - + + - - - - - + + + + + {section && } + {lastUpdated && ( + + )} + + - + - - + + - - - - -

everything happens in the console

- - - - \ No newline at end of file diff --git a/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/index.js b/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/index.js deleted file mode 100644 index 3921411115..0000000000 --- a/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/index.js +++ /dev/null @@ -1,49 +0,0 @@ -import init, { NymIssuanceTicketbook } from "@nymproject/nym-vpn-api-lib-wasm"; - -async function main() { - await init(); - - let cryptoData = new NymIssuanceTicketbook({}); - - console.log("getting partial vks"); - const partialVksRes = await fetch("http://localhost:8080/api/v1/ticketbook/partial-verification-keys", { - headers: new Headers({ "Authorization": "Bearer foomp" }) - }); - const partialVks = await partialVksRes.json(); - console.debug(partialVks); - - console.log("getting master vk"); - const masterVkRes = await fetch("http://localhost:8080/api/v1/ticketbook/master-verification-key", { - headers: new Headers({ "Authorization": "Bearer foomp" }) - }); - const masterVk = await masterVkRes.json(); - console.debug(masterVk); - - let request = cryptoData.buildRequestPayload(false); - console.log(request); - - - console.log("getting blinded wallet shares"); - const sharesRes = await fetch("http://localhost:8080/api/v1/ticketbook/obtain?include-coin-index-signatures=true&include-expiration-date-signatures=true", { - method: "POST", - headers: new Headers( - { - "Authorization": "Bearer foomp", - "Content-Type": "application/json" - } - ), - body: request - }); - - const credentialShares = await sharesRes.json(); - console.log(credentialShares); - - console.log("unblinding shares"); - const unblinded = cryptoData.unblindWalletShares(credentialShares, partialVks, masterVk); - - const serialised = unblinded.serialise(); - console.log("serialised:\n", serialised); -} - - -main(); diff --git a/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/package.json b/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/package.json deleted file mode 100644 index 2eb37c3aa2..0000000000 --- a/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "create-wasm-app", - "version": "0.1.0", - "description": "create an app to consume rust-generated wasm packages", - "main": "index.js", - "bin": { - "create-wasm-app": ".bin/create-wasm-app.js" - }, - "scripts": { - "build": "webpack --config webpack.config.js", - "build:wasm": "cd ../ && make wasm-build", - "start": "webpack-dev-server --port 8001" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/rustwasm/create-wasm-app.git" - }, - "keywords": [ - "webassembly", - "wasm", - "rust", - "webpack" - ], - "author": "Dave Hrycyszyn ", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/nymtech/nym/issues" - }, - "homepage": "https://nymtech.net/docs", - "devDependencies": { - "copy-webpack-plugin": "^11.0.0", - "hello-wasm-pack": "^0.1.0", - "webpack": "^5.70.0", - "webpack-cli": "^4.9.2", - "webpack-dev-server": "^5.2.1" - }, - "dependencies": { - "@nymproject/nym-vpn-api-lib-wasm": "file:../../../dist/wasm/nym-vpn-api-lib-wasm" - } -} diff --git a/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/webpack.config.js b/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/webpack.config.js deleted file mode 100644 index d5f839485f..0000000000 --- a/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/webpack.config.js +++ /dev/null @@ -1,33 +0,0 @@ -const CopyWebpackPlugin = require("copy-webpack-plugin"); -const path = require("path"); - -module.exports = { - performance: { - hints: false, - maxEntrypointSize: 512000, - maxAssetSize: 512000 - }, - entry: { - bootstrap: "./bootstrap.js" - // worker: "./worker.js" - }, - output: { - path: path.resolve(__dirname, "dist"), - filename: "[name].js" - }, - mode: "development", - // mode: 'production', - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - "index.html", - { - from: "../../../dist/wasm/nym-vpn-api-lib-wasm/*.(js|wasm)", - to: "[name][ext]" - } - ] - }) - - ], - experiments: { syncWebAssembly: true } -}; diff --git a/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/yarn.lock b/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/yarn.lock deleted file mode 100644 index 01cda5871e..0000000000 --- a/nym-credential-proxy/vpn-api-lib-wasm/internal-dev/yarn.lock +++ /dev/null @@ -1,2204 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@discoveryjs/json-ext@^0.5.0": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" - integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/source-map@^0.3.3": - version "0.3.6" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" - integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@jsonjoy.com/base64@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" - integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== - -"@jsonjoy.com/json-pack@^1.0.3": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz#e658900e81d194903171c42546e1aa27f446846a" - integrity sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA== - dependencies: - "@jsonjoy.com/base64" "^1.1.1" - "@jsonjoy.com/util" "^1.1.2" - hyperdyperid "^1.2.0" - thingies "^1.20.0" - -"@jsonjoy.com/util@^1.1.2", "@jsonjoy.com/util@^1.3.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.6.0.tgz#23991b2fe12cb3a006573d9dc97c768d3ed2c9f1" - integrity sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A== - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" - integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@nymproject/nym-vpn-api-lib-wasm@file:../../../dist/wasm/nym-vpn-api-lib-wasm": - version "0.1.0" - -"@types/body-parser@*": - version "1.19.5" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" - integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.13": - version "3.5.13" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" - integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== - dependencies: - "@types/node" "*" - -"@types/connect-history-api-fallback@^1.5.4": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" - integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/eslint-scope@^3.7.7": - version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" - integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "9.6.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" - integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^1.0.8": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" - integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.21", "@types/express-serve-static-core@^4.17.33": - version "4.19.6" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" - integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*", "@types/express@^4.17.21": - version "4.17.22" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.22.tgz#14cfcf120f7eb56ebb8ca77b7fa9a14d21de7c96" - integrity sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/http-errors@*": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" - integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== - -"@types/http-proxy@^1.17.8": - version "1.17.15" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.15.tgz#12118141ce9775a6499ecb4c01d02f90fc839d36" - integrity sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ== - dependencies: - "@types/node" "*" - -"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/node-forge@^1.3.0": - version "1.3.11" - resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" - integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== - dependencies: - "@types/node" "*" - -"@types/node@*": - version "22.5.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.0.tgz#10f01fe9465166b4cab72e75f60d8b99d019f958" - integrity sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg== - dependencies: - undici-types "~6.19.2" - -"@types/qs@*": - version "6.9.15" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" - integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/retry@0.12.2": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" - integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== - -"@types/send@*": - version "0.17.4" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" - integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-index@^1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" - integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== - dependencies: - "@types/express" "*" - -"@types/serve-static@*", "@types/serve-static@^1.15.5": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" - integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "*" - -"@types/sockjs@^0.3.36": - version "0.3.36" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" - integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== - dependencies: - "@types/node" "*" - -"@types/ws@^8.5.10": - version "8.18.1" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" - integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== - dependencies: - "@types/node" "*" - -"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" - integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== - dependencies: - "@webassemblyjs/helper-numbers" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - -"@webassemblyjs/floating-point-hex-parser@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" - integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== - -"@webassemblyjs/helper-api-error@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" - integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== - -"@webassemblyjs/helper-buffer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" - integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== - -"@webassemblyjs/helper-numbers@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" - integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.13.2" - "@webassemblyjs/helper-api-error" "1.13.2" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" - integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== - -"@webassemblyjs/helper-wasm-section@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" - integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/wasm-gen" "1.14.1" - -"@webassemblyjs/ieee754@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" - integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" - integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" - integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== - -"@webassemblyjs/wasm-edit@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" - integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/helper-wasm-section" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-opt" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - "@webassemblyjs/wast-printer" "1.14.1" - -"@webassemblyjs/wasm-gen@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" - integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wasm-opt@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" - integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - -"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" - integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-api-error" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wast-printer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" - integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" - integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== - -"@webpack-cli/info@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" - integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ== - dependencies: - envinfo "^7.7.3" - -"@webpack-cli/serve@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" - integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-import-phases@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7" - integrity sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ== - -acorn@^8.15.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@^8.0.0, ajv@^8.9.0: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -baseline-browser-mapping@^2.9.0: - version "2.9.19" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488" - integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg== - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -bonjour-service@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.3.0.tgz#80d867430b5a0da64e82a8047fc1e355bdb71722" - integrity sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA== - dependencies: - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - -braces@^3.0.3, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.28.1: - version "4.28.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" - integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== - dependencies: - baseline-browser-mapping "^2.9.0" - caniuse-lite "^1.0.30001759" - electron-to-chromium "^1.5.263" - node-releases "^2.0.27" - update-browserslist-db "^1.2.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bundle-name@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" - integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== - dependencies: - run-applescript "^7.0.0" - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bound@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -caniuse-lite@^1.0.30001759: - version "1.0.30001769" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz#1ad91594fad7dc233777c2781879ab5409f7d9c2" - integrity sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg== - -chokidar@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chrome-trace-event@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" - integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -colorette@^2.0.10, colorette@^2.0.14: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - -connect-history-api-fallback@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" - integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== - -copy-webpack-plugin@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" - integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== - dependencies: - fast-glob "^3.2.11" - glob-parent "^6.0.1" - globby "^13.1.1" - normalize-path "^3.0.0" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^4.1.0: - version "4.3.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" - integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== - dependencies: - ms "2.1.2" - -default-browser-id@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" - integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== - -default-browser@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" - integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== - dependencies: - bundle-name "^4.1.0" - default-browser-id "^5.0.0" - -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -dns-packet@^5.2.2: - version "5.6.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" - integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.5.263: - version "1.5.286" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" - integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -enhanced-resolve@^5.17.4: - version "5.19.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz#6687446a15e969eaa63c2fa2694510e17ae6d97c" - integrity sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.3.0" - -envinfo@^7.7.3: - version "7.13.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.13.0.tgz#81fbb81e5da35d74e814941aeab7c325a606fb31" - integrity sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q== - -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-module-lexer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.0.0.tgz#f657cd7a9448dcdda9c070a3cb75e5dc1e85f5b1" - integrity sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -express@^4.21.2: - version "4.21.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" - integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.3" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.7.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.3.1" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.12" - proxy-addr "~2.0.7" - qs "6.13.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.2" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^3.2.11, fast-glob@^3.3.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-uri@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" - integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== - -fastest-levenshtein@^1.0.12: - version "1.0.16" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" - integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== - -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== - dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -follow-redirects@^1.0.0: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -globby@^13.1.1: - version "13.2.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" - integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== - dependencies: - dir-glob "^3.0.1" - fast-glob "^3.3.0" - ignore "^5.2.4" - merge2 "^1.4.1" - slash "^4.0.0" - -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -hello-wasm-pack@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/hello-wasm-pack/-/hello-wasm-pack-0.1.0.tgz#482a2e3371828056ac35f5b5fec76c0b99dcd530" - integrity sha512-3hx0GDkDLf/a9ThCMV2qG4mwza8N/MCtm8aeFFc/cdBCL2zMJ1kW1wjNl7xPqD1lz8Yl5+uhnc/cpui4dLwz/w== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -http-proxy-middleware@^2.0.7: - version "2.0.9" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef" - integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -hyperdyperid@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" - integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore@^5.2.4: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" - integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" - integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.13.0: - version "2.15.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" - integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== - dependencies: - hasown "^2.0.2" - -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - -is-network-error@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.1.0.tgz#d26a760e3770226d11c169052f266a4803d9c997" - integrity sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-wsl@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" - integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== - dependencies: - is-inside-container "^1.0.0" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -launch-editor@^2.6.1: - version "2.10.0" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.10.0.tgz#5ca3edfcb9667df1e8721310f3a40f1127d4bc42" - integrity sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA== - dependencies: - picocolors "^1.0.0" - shell-quote "^1.8.1" - -loader-runner@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3" - integrity sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^4.6.0: - version "4.17.2" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.17.2.tgz#1f71a6d85c8c53b4f1b388234ed981a690c7e227" - integrity sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg== - dependencies: - "@jsonjoy.com/json-pack" "^1.0.3" - "@jsonjoy.com/util" "^1.3.0" - tree-dump "^1.0.1" - tslib "^2.0.0" - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -"mime-db@>= 1.43.0 < 2": - version "1.53.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" - integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== - -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-forge@^1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.2.tgz#d0d2659a26eef778bf84d73e7f55c08144ee7750" - integrity sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw== - -node-releases@^2.0.27: - version "2.0.27" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" - integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -object-inspect@^1.13.3: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1, on-finished@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -open@^10.0.3: - version "10.1.2" - resolved "https://registry.yarnpkg.com/open/-/open-10.1.2.tgz#d5df40984755c9a9c3c93df8156a12467e882925" - integrity sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw== - dependencies: - default-browser "^5.2.1" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^3.1.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-retry@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.1.tgz#81828f8dc61c6ef5a800585491572cc9892703af" - integrity sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ== - dependencies: - "@types/retry" "0.12.2" - is-network-error "^1.0.0" - retry "^0.13.1" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -picocolors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" - integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== - -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -readable-stream@^2.0.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" - integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== - dependencies: - resolve "^1.9.0" - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve@^1.9.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -run-applescript@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" - integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -schema-utils@^4.0.0, schema-utils@^4.2.0, schema-utils@^4.3.0, schema-utils@^4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" - integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" - integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== - dependencies: - "@types/node-forge" "^1.3.0" - node-forge "^1" - -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@^6.0.0, serialize-javascript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.19.0" - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" - integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== - -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.0.6: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tapable@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" - integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== - -terser-webpack-plugin@^5.3.16: - version "5.3.16" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz#741e448cc3f93d8026ebe4f7ef9e4afacfd56330" - integrity sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q== - dependencies: - "@jridgewell/trace-mapping" "^0.3.25" - jest-worker "^27.4.5" - schema-utils "^4.3.0" - serialize-javascript "^6.0.2" - terser "^5.31.1" - -terser@^5.31.1: - version "5.46.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.46.0.tgz#1b81e560d584bbdd74a8ede87b4d9477b0ff9695" - integrity sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.15.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -thingies@^1.20.0: - version "1.21.0" - resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" - integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g== - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -tree-dump@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.3.tgz#2f0e42e77354714418ed7ab44291e435ccdb0f80" - integrity sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg== - -tslib@^2.0.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -undici-types@~6.19.2: - version "6.19.8" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" - integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" - integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -watchpack@^2.4.4: - version "2.5.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.1.tgz#dd38b601f669e0cbf567cb802e75cead82cde102" - integrity sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -webpack-cli@^4.9.2: - version "4.10.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" - integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^1.2.0" - "@webpack-cli/info" "^1.5.0" - "@webpack-cli/serve" "^1.7.0" - colorette "^2.0.14" - commander "^7.0.0" - cross-spawn "^7.0.3" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^2.2.0" - rechoir "^0.7.0" - webpack-merge "^5.7.3" - -webpack-dev-middleware@^7.4.2: - version "7.4.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz#40e265a3d3d26795585cff8207630d3a8ff05877" - integrity sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA== - dependencies: - colorette "^2.0.10" - memfs "^4.6.0" - mime-types "^2.1.31" - on-finished "^2.4.1" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.1.tgz#049072d6e19cbda8cf600b9e364e6662d61218ba" - integrity sha512-ml/0HIj9NLpVKOMq+SuBPLHcmbG+TGIjXRHsYfZwocUBIqEvws8NnS/V9AFQ5FKP+tgn5adwVwRrTEpGL33QFQ== - dependencies: - "@types/bonjour" "^3.5.13" - "@types/connect-history-api-fallback" "^1.5.4" - "@types/express" "^4.17.21" - "@types/express-serve-static-core" "^4.17.21" - "@types/serve-index" "^1.9.4" - "@types/serve-static" "^1.15.5" - "@types/sockjs" "^0.3.36" - "@types/ws" "^8.5.10" - ansi-html-community "^0.0.8" - bonjour-service "^1.2.1" - chokidar "^3.6.0" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - express "^4.21.2" - graceful-fs "^4.2.6" - http-proxy-middleware "^2.0.7" - ipaddr.js "^2.1.0" - launch-editor "^2.6.1" - open "^10.0.3" - p-retry "^6.2.0" - schema-utils "^4.2.0" - selfsigned "^2.4.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^7.4.2" - ws "^8.18.0" - -webpack-merge@^5.7.3: - version "5.10.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" - integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== - dependencies: - clone-deep "^4.0.1" - flat "^5.0.2" - wildcard "^2.0.0" - -webpack-sources@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" - integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== - -webpack@^5.70.0: - version "5.104.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.104.1.tgz#94bd41eb5dbf06e93be165ba8be41b8260d4fb1a" - integrity sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA== - dependencies: - "@types/eslint-scope" "^3.7.7" - "@types/estree" "^1.0.8" - "@types/json-schema" "^7.0.15" - "@webassemblyjs/ast" "^1.14.1" - "@webassemblyjs/wasm-edit" "^1.14.1" - "@webassemblyjs/wasm-parser" "^1.14.1" - acorn "^8.15.0" - acorn-import-phases "^1.0.3" - browserslist "^4.28.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.4" - es-module-lexer "^2.0.0" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.11" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.3.1" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^4.3.3" - tapable "^2.3.0" - terser-webpack-plugin "^5.3.16" - watchpack "^2.4.4" - webpack-sources "^3.3.3" - -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" - integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== - -ws@^8.18.0: - version "8.18.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.2.tgz#42738b2be57ced85f46154320aabb51ab003705a" - integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== diff --git a/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs b/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs deleted file mode 100644 index 5e1255bdbc..0000000000 --- a/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs +++ /dev/null @@ -1,35 +0,0 @@ -// 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; - -#[derive(Debug, Error)] -pub enum VpnApiLibError { - #[error("{0}")] - Json(String), - - #[error("[ecash] cryptographic failure: {source}")] - EcashFailure { - #[from] - source: nym_compact_ecash::CompactEcashError, - }, - - #[error("provided invalid ticket type")] - MalformedTicketType, - - #[error("the provided shares and issuers are not from the same epoch! {shares} and {issuers}")] - InconsistentEpochId { shares: u64, issuers: u64 }, - - #[error("failed to recover ed25519 private key from its base58 representation")] - MalformedEd25519Key, -} - -wasm_error!(VpnApiLibError); - -impl From for VpnApiLibError { - fn from(value: Error) -> Self { - VpnApiLibError::Json(value.to_string()) - } -} diff --git a/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs b/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs deleted file mode 100644 index 00ca53805a..0000000000 --- a/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2024 Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::error::VpnApiLibError; -use nym_compact_ecash::scheme::keygen::KeyPairUser; -use nym_compact_ecash::scheme::withdrawal::RequestInfo; -use nym_compact_ecash::{ - Base58, BlindedSignature, VerificationKeyAuth, WithdrawalRequest, aggregate_wallets, - issue_verify, withdrawal_request, -}; -use nym_credential_proxy_requests::api::v1::ticketbook::models::{ - MasterVerificationKeyResponse, PartialVerificationKeysResponse, TicketbookRequest, - TicketbookWalletSharesResponse, WalletShare, -}; -use nym_credentials::{ - AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey, - IssuedTicketBook, -}; -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 zeroize::Zeroizing; - -pub mod error; - -#[derive(Tsify, Debug, Default, Clone, Serialize, Deserialize)] -#[tsify(into_wasm_abi, from_wasm_abi)] -#[serde(rename_all = "camelCase")] -pub struct WalletShares(Vec); - -pub type WalletIssuers = PartialVerificationKeysResponse; - -impl From> for WalletShares { - fn from(shares: Vec) -> Self { - WalletShares(shares) - } -} - -#[derive(Tsify, Debug, Default, Clone, Serialize, Deserialize)] -#[tsify(into_wasm_abi, from_wasm_abi)] -#[serde(rename_all = "camelCase")] -pub struct NymIssuanceTicketbookOpts { - #[tsify(optional)] - pub ticketbook_type: Option, - - // bs58-encoded user secret key used for seeding the ecash crypto keypair generation - // I reiterate, this is a **SECRET** key, not a public key. - #[tsify(optional)] - pub user_secret_key: Option, -} - -#[wasm_bindgen] -#[derive(Debug)] -#[allow(dead_code)] -pub struct NymIssuanceTicketbook { - /// ecash keypair related to the credential - ecash_keypair: KeyPairUser, - - withdrawal_request: WithdrawalRequest, - - ticketbook_type: TicketType, - - expiration_date: Date, - - request_info: Zeroizing, -} - -#[wasm_bindgen] -impl NymIssuanceTicketbook { - #[wasm_bindgen(constructor)] - pub fn new(opts: NymIssuanceTicketbookOpts) -> Result { - let ecash_keypair = match opts.user_secret_key { - None => KeyPairUser::new(), - Some(maybe_sk) => { - let pk = ed25519::PrivateKey::from_base58_string(maybe_sk) - .map(Zeroizing::new) - .map_err(|_| VpnApiLibError::MalformedEd25519Key)?; - let bytes = Zeroizing::new(pk.to_bytes()); - KeyPairUser::new_seeded(&bytes) - } - }; - - let ticketbook_type = match opts.ticketbook_type { - None => TicketType::V1MixnetEntry, - Some(typ) => typ - .parse() - .map_err(|_| VpnApiLibError::MalformedTicketType)?, - }; - - let expiration_date = ecash_default_expiration_date(); - - let (withdrawal_request, request_info) = withdrawal_request( - ecash_keypair.secret_key(), - expiration_date.ecash_unix_timestamp(), - ticketbook_type.encode(), - )?; - - Ok(NymIssuanceTicketbook { - ecash_keypair, - withdrawal_request, - ticketbook_type, - expiration_date, - request_info: Zeroizing::new(request_info), - }) - } - - #[wasm_bindgen(js_name = "buildRequestPayload")] - pub fn build_request_payload(&self, is_freepass_request: bool) -> String { - serde_json::to_string(&TicketbookRequest { - withdrawal_request: self.withdrawal_request.clone().into(), - ecash_pubkey: self.ecash_keypair.public_key(), - expiration_date: self.expiration_date, - ticketbook_type: self.ticketbook_type, - is_freepass_request, - }) - .unwrap() - } - - #[wasm_bindgen(js_name = "getWithdrawalRequest")] - pub fn get_encoded_withdrawal_request(&self) -> String { - self.withdrawal_request.to_bs58() - } - - #[wasm_bindgen(js_name = "getEncodedPublicKey")] - pub fn get_encoded_public_key(&self) -> String { - self.ecash_keypair.public_key().to_bs58() - } - - // - // #[wasm_bindgen(js_name = "unblindShare")] - // pub fn unblind_share(&self, share: UnblindableShare) -> Result { - // let blinded_sig = BlindedSignature::try_from_bs58(share.blinded_share_bs58)?; - // let vk = VerificationKey::try_from_bs58(share.issuer_key_bs58)?; - // - // Ok(blinded_sig - // .unblind(&vk, &self.pedersen_commitments_openings) - // .into()) - // } - // - #[wasm_bindgen(js_name = "unblindWalletShares")] - pub fn unblind_wallet_shares( - self, - shares: JsValue, - issuers: WalletIssuers, - master_key: MasterVerificationKeyResponse, - ) -> Result { - // we couldn't derive all the required abi traits due to crypto types deep in the stack - let shares: TicketbookWalletSharesResponse = serde_wasm_bindgen::from_value(shares)?; - - if shares.epoch_id != issuers.epoch_id { - console_error!( - "the provided shares and issuers are not from the same epoch! {} and {}", - shares.epoch_id, - issuers.epoch_id - ); - return Err(VpnApiLibError::InconsistentEpochId { - shares: shares.epoch_id, - issuers: issuers.epoch_id, - }); - } - - let master_vk = VerificationKeyAuth::try_from_bs58(master_key.bs58_encoded_key)?; - - let mut decoded_keys = HashMap::new(); - for key in issuers.keys { - let vk = VerificationKeyAuth::try_from_bs58(key.bs58_encoded_key)?; - decoded_keys.insert(key.node_index, vk); - } - - let mut partial_wallets = Vec::new(); - for share in shares.shares { - let blinded_sig = BlindedSignature::try_from_bs58(share.bs58_encoded_share)?; - let Some(vk) = decoded_keys.get(&share.node_index) else { - console_error!( - "received a share from issuer {} but did not receive a corresponding verification key!", - share.node_index - ); - continue; - }; - - match issue_verify( - vk, - self.ecash_keypair.secret_key(), - &blinded_sig, - &self.request_info, - share.node_index, - ) { - Ok(partial_wallet) => partial_wallets.push(partial_wallet), - Err(err) => { - console_error!( - "failed to unblind partial wallet corresponding to index {}: {err}", - share.node_index - ) - } - } - } - - let aggregated_wallet = aggregate_wallets( - &master_vk, - self.ecash_keypair.secret_key(), - &partial_wallets, - &self.request_info, - )?; - - Ok(NymIssuedTicketbook { - inner_ticketbook: IssuedTicketBook::new( - aggregated_wallet.into_wallet_signatures(), - shares.epoch_id, - self.ecash_keypair.into(), - self.ticketbook_type, - self.expiration_date, - ), - master_vk: EpochVerificationKey { - epoch_id: shares.epoch_id, - key: master_vk, - }, - expiration_date_signatures: shares - .aggregated_expiration_date_signatures - .map(|s| s.signatures), - coin_index_signatures: shares - .aggregated_coin_index_signatures - .map(|s| s.signatures), - }) - } -} - -#[wasm_bindgen] -pub struct NymIssuedTicketbook { - inner_ticketbook: IssuedTicketBook, - - master_vk: EpochVerificationKey, - expiration_date_signatures: Option, - coin_index_signatures: Option, -} - -#[wasm_bindgen] -impl NymIssuedTicketbook { - pub fn serialise(self) -> FullSerialisedNymIssuedTicketbook { - let serialised = self - .inner_ticketbook - .begin_export() - .with_master_verification_key(&self.master_vk) - .with_maybe_expiration_date_signatures(&self.expiration_date_signatures) - .with_maybe_coin_index_signatures(&self.coin_index_signatures) - .finalize_export(); - - FullSerialisedNymIssuedTicketbook { - serialisation_revision: serialised.revision, - bs58_encoded_data: bs58::encode(serialised.data).into_string(), - } - } -} - -#[derive(Tsify, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[tsify(into_wasm_abi, from_wasm_abi)] -#[serde(rename_all = "camelCase")] -pub struct FullSerialisedNymIssuedTicketbook { - pub serialisation_revision: u8, - pub bs58_encoded_data: String, -} - -#[wasm_bindgen(start)] -pub fn main() { - 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() - ); - nym_wasm_utils::console_log!("[rust main]: setting panic hook"); - nym_wasm_utils::set_panic_hook(); -} diff --git a/nym-gateway-probe/Cargo.toml b/nym-gateway-probe/Cargo.toml index bf8c621e59..7cebd4f98b 100644 --- a/nym-gateway-probe/Cargo.toml +++ b/nym-gateway-probe/Cargo.toml @@ -22,9 +22,11 @@ hex.workspace = true tracing.workspace = true pnet_packet.workspace = true rand.workspace = true +rand09.workspace = true reqwest = { workspace = true, features = ["socks"] } serde.workspace = true serde_json.workspace = true +semver.workspace = true time = { workspace = true } tokio = { workspace = true, features = [ "process", diff --git a/nym-gateway-probe/src/common/helpers.rs b/nym-gateway-probe/src/common/helpers.rs index 5693b8a7a9..7f90fca60a 100644 --- a/nym-gateway-probe/src/common/helpers.rs +++ b/nym-gateway-probe/src/common/helpers.rs @@ -1,12 +1,9 @@ // 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, @@ -15,13 +12,6 @@ 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, diff --git a/nym-gateway-probe/src/common/nodes.rs b/nym-gateway-probe/src/common/nodes.rs index 4e79e84ce6..c36daff81b 100644 --- a/nym-gateway-probe/src/common/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -3,25 +3,26 @@ use anyhow::{Context, anyhow, bail}; use nym_api_requests::models::{ - AuthenticatorDetailsV1, DeclaredRolesV1, DescribedNodeTypeV1, HostInformationV1, - IpPacketRouterDetailsV1, NetworkRequesterDetailsV1, NymNodeDataV1, - OffsetDateTimeJsonSchemaWrapper, WebSocketsV1, WireguardDetailsV1, + AuthenticatorDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, HostInformationV2, + IpPacketRouterDetailsV2, NetworkRequesterDetailsV2, NymNodeDataV2, + OffsetDateTimeJsonSchemaWrapper, WebSocketsV2, WireguardDetailsV2, }; 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::Ciphersuite; use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; +use nym_lp::packet::version; +use nym_lp::peer::{DHPublicKey, LpRemotePeer}; 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::NymNodeDescriptionV1; +use nym_validator_client::models::NymNodeDescriptionV2; use rand::prelude::IteratorRandom; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::net::{IpAddr, SocketAddr}; use std::time::Duration; use time::OffsetDateTime; @@ -90,7 +91,7 @@ use url::Url; #[derive(Clone)] pub struct DirectoryNode { - described: NymNodeDescriptionV1, + described: NymNodeDescriptionV2, } impl DirectoryNode { @@ -129,16 +130,32 @@ impl DirectoryNode { .copied() .ok_or_else(|| anyhow!("no ip address known"))?; - // 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, - // }) - // }); + let identity = self.identity(); + + let lp_data = match description.lewes_protocol.as_ref() { + Some(lp_data) => { + // 1. verify the signature, if it's invalid, completely bail on the node + if !lp_data.verify(&identity) { + warn!("{identity} provided malformed lp data"); + bail!("{identity} provided malformed lp data"); + } + + let version: semver::Version = + description.build_information.build_version.parse()?; + let Some(ciphersuite) = Ciphersuite::from_node_version(version) else { + bail!("failed to identify valid ciphersuite for {identity}"); + }; + + Some(TestedNodeLpDetails { + address: SocketAddr::new(ip_address, lp_data.content.control_port), + expected_kem_key_hashes: lp_data.content.kem_keys()?, + x25519: lp_data.content.x25519, + lp_version: version::CURRENT, + ciphersuite, + }) + } + None => None, + }; Ok(TestedNodeDetails { identity: self.identity(), @@ -147,7 +164,7 @@ impl DirectoryNode { authenticator_address, authenticator_version, ip_address: Some(ip_address), - lp_data: None, + lp_data, }) } } @@ -237,11 +254,11 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result = - nr_result.map(|nr| NetworkRequesterDetailsV1 { + let network_requester: Option = + nr_result.map(|nr| NetworkRequesterDetailsV2 { 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 { + let ip_packet_router: Option = + ipr_result.map(|ipr| IpPacketRouterDetailsV2 { address: ipr.address, }); - let authenticator: Option = - authenticator_result.map(|auth| AuthenticatorDetailsV1 { + let authenticator: Option = + authenticator_result.map(|auth| AuthenticatorDetailsV2 { address: auth.address, }); #[allow(deprecated)] - let wireguard: Option = - wireguard_result.map(|wg| WireguardDetailsV1 { + let wireguard: Option = + wireguard_result.map(|wg| WireguardDetailsV2 { port: wg.tunnel_port, // Use tunnel_port for deprecated port field tunnel_port: wg.tunnel_port, metadata_port: wg.metadata_port, @@ -284,14 +301,14 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result, - pub expected_signing_key_hashes: HashMap, - pub x25519: x25519::PublicKey, + pub expected_kem_key_hashes: BTreeMap, + pub x25519: DHPublicKey, pub lp_version: u8, + pub ciphersuite: Ciphersuite, +} + +impl TestedNodeLpDetails { + pub fn into_remote_peer(self) -> LpRemotePeer { + LpRemotePeer::new(self.x25519).with_key_digests(self.expected_kem_key_hashes) + } } diff --git a/nym-gateway-probe/src/common/probe_tests.rs b/nym-gateway-probe/src/common/probe_tests.rs index 0ee8140f77..158cbfc011 100644 --- a/nym-gateway-probe/src/common/probe_tests.rs +++ b/nym-gateway-probe/src/common/probe_tests.rs @@ -28,10 +28,12 @@ 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_lp::peer::DHKeyPair; 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 rand09::SeedableRng; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, sync::Arc, @@ -163,23 +165,25 @@ pub async fn lp_registration_probe( ) -> 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); + let lp_ciphersuite = gateway_lp_data.ciphersuite; + let peer = gateway_lp_data.into_remote_peer(); 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)); + let mut rng09 = rand09::rngs::StdRng::from_os_rng(); + let client_x25519_keypair = Arc::new(DHKeyPair::new(&mut rng09)); // 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, + client_x25519_keypair, peer, lp_address, + lp_ciphersuite, lp_version, ); @@ -209,23 +213,13 @@ pub async fn lp_registration_probe( 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); - } - }; + let gateway_ed25519_pubkey = gateway_identity; // 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, + &mut rng09, &wg_keypair, &gateway_ed25519_pubkey, bandwidth_controller, @@ -299,21 +293,26 @@ pub async fn wg_probe_lp( let entry_lp_version = entry_lp_data.lp_version; let exit_lp_version = exit_lp_data.lp_version; + let entry_lp_ciphersuite = entry_lp_data.ciphersuite; + let exit_lp_ciphersuite = exit_lp_data.ciphersuite; + 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 x25519 keypairs for LP protocol + let mut rng09 = rand09::rngs::StdRng::from_os_rng(); + + let entry_lp_keypair = Arc::new(DHKeyPair::new(&mut rng09)); + let exit_lp_keypair = Arc::new(DHKeyPair::new(&mut rng09)); // Generate WireGuard keypairs for VPN registration + let mut rng = rand::rngs::OsRng; 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); + let entry_peer = entry_lp_data.into_remote_peer(); + let exit_peer = exit_lp_data.into_remote_peer(); // STEP 1: Establish outer LP session with entry gateway // LpRegistrationClient uses packet-per-connection model - connect() is gone, @@ -323,6 +322,7 @@ pub async fn wg_probe_lp( entry_lp_keypair, entry_peer, entry_address, + entry_lp_ciphersuite, entry_lp_version, ); @@ -335,16 +335,26 @@ pub async fn wg_probe_lp( // 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 mut nested_session = NestedLpSession::new( + exit_address, + exit_lp_keypair, + exit_peer, + exit_lp_ciphersuite, + exit_lp_version, + ); let exit_gateway_pubkey = exit_gateway.identity; // Perform handshake and registration with exit gateway via forwarding + if let Err(err) = nested_session.perform_handshake(&mut entry_client).await { + error!("Failed to perform handshake with exit gateway: {err}"); + return Ok(wg_outcome); + }; + let exit_gateway_data = match nested_session - .handshake_and_register_dvpn( + .register_dvpn( &mut entry_client, - &mut rng, + &mut rng09, &exit_wg_keypair, &exit_gateway_pubkey, bandwidth_controller, @@ -370,7 +380,7 @@ pub async fn wg_probe_lp( // Use packet-per-connection register() which returns GatewayData directly let entry_gateway_data = match entry_client .register_dvpn( - &mut rng, + &mut rng09, &entry_wg_keypair, &entry_gateway_pubkey, bandwidth_controller, diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 90f451deb4..b17f5272f5 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.26.0" +version = "1.27.0" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -13,6 +13,8 @@ license = "GPL-3.0" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +path = "src/lib.rs" [dependencies] async-trait = { workspace = true } @@ -27,6 +29,7 @@ colored = { workspace = true } console-subscriber = { workspace = true, optional = true } csv = { workspace = true } clap = { workspace = true, features = ["cargo", "env"] } +dashmap = { workspace = true } futures = { workspace = true } hex = { workspace = true } humantime-serde = { workspace = true } @@ -34,12 +37,15 @@ human-repr = { workspace = true } ipnetwork = { workspace = true } indicatif = { workspace = true } rand = { workspace = true } +rand09 = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true thiserror.workspace = true tracing.workspace = true tracing-indicatif = { workspace = true } tracing-subscriber.workspace = true +opentelemetry = { workspace = true, features = ["trace"], optional = true } +opentelemetry_sdk = { workspace = true, features = ["trace"], optional = true } tokio = { workspace = true, features = ["macros", "sync", "rt-multi-thread"] } tokio-util = { workspace = true, features = ["codec"] } tokio-stream = { workspace = true } @@ -110,6 +116,11 @@ nym-gateway = { path = "../gateway" } nym-network-requester = { path = "../service-providers/network-requester" } nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } +# LP dependencies +nym-lp = { path = "../common/nym-lp" } +nym-registration-common = { path = "../common/registration" } +bincode = { workspace = true } + # throughput tester to recreate lioness # we don't care about particular versions - just pull whatever is used by sphinx @@ -131,10 +142,11 @@ cargo_metadata = { workspace = true } [dev-dependencies] criterion = { workspace = true, features = ["async_tokio"] } -rand_chacha = { workspace = true } +nym-test-utils = { workspace = true } [features] tokio-console = ["console-subscriber", "nym-task/tokio-tracing"] +otel = ["nym-bin-common/otel-otlp", "dep:opentelemetry", "dep:opentelemetry_sdk"] [lints] workspace = true diff --git a/nym-node/nym-node-metrics/src/prometheus_wrapper.rs b/nym-node/nym-node-metrics/src/prometheus_wrapper.rs index 4102e82c98..ffdb66e5c0 100644 --- a/nym-node/nym-node-metrics/src/prometheus_wrapper.rs +++ b/nym-node/nym-node-metrics/src/prometheus_wrapper.rs @@ -24,6 +24,18 @@ const CLIENT_SESSION_DURATION_BUCKETS: &[f64] = &[ 259200., // 72h+ (implicitly) ]; +const REG_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, Debug, EnumIter, Display, EnumProperty, EnumCount, Eq, Hash, PartialEq)] #[strum(serialize_all = "snake_case", prefix = "nym_node_")] pub enum PrometheusMetric { @@ -139,6 +151,34 @@ pub enum PrometheusMetric { #[strum(props(help = "The current receiving rate of wireguard"))] WireguardBytesRxRate, + #[strum(props(help = "The distribution of defguard peer creation time"))] + WireguardDefguardPeerCreation, + + #[strum(props( + help = "The distribution of time it takes to verify a credential during peer registration" + ))] + PeerRegistrationCredentialVerification, + + #[strum(props( + help = "The distribution of time for client dvpn registration through the authenticator for msg1" + ))] + DvpnAuthenticatorClientRegistrationMsg1, + + #[strum(props( + help = "The distribution of time for client dvpn registration through the authenticator for msg2" + ))] + DvpnAuthenticatorClientRegistrationMsg2, + + #[strum(props( + help = "The distribution of time for client dvpn registration through the lp for msg1" + ))] + DvpnLpClientRegistrationMsg1, + + #[strum(props( + help = "The distribution of time for client dvpn registration through the lp for msg2" + ))] + DvpnLpClientRegistrationMsg2, + // # NETWORK #[strum(props(help = "The number of active ingress mixnet connections"))] NetworkActiveIngressMixnetConnections, @@ -271,6 +311,24 @@ impl PrometheusMetric { PrometheusMetric::WireguardActivePeers => Metric::new_int_gauge(&name, help), PrometheusMetric::WireguardBytesTxRate => Metric::new_float_gauge(&name, help), PrometheusMetric::WireguardBytesRxRate => Metric::new_float_gauge(&name, help), + PrometheusMetric::WireguardDefguardPeerCreation => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } + PrometheusMetric::DvpnAuthenticatorClientRegistrationMsg1 => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } + PrometheusMetric::DvpnAuthenticatorClientRegistrationMsg2 => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } + PrometheusMetric::DvpnLpClientRegistrationMsg1 => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } + PrometheusMetric::DvpnLpClientRegistrationMsg2 => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } + PrometheusMetric::PeerRegistrationCredentialVerification => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } PrometheusMetric::NetworkActiveIngressMixnetConnections => { Metric::new_int_gauge(&name, help) } @@ -388,7 +446,7 @@ mod tests { // a sanity check for anyone adding new metrics. if this test fails, // make sure any methods on `PrometheusMetric` enum don't need updating // or require custom Display impl - assert_eq!(39, PrometheusMetric::COUNT) + assert_eq!(45, PrometheusMetric::COUNT) } #[test] diff --git a/nym-node/nym-node-requests/Cargo.toml b/nym-node/nym-node-requests/Cargo.toml index 6c311152ab..b051086220 100644 --- a/nym-node/nym-node-requests/Cargo.toml +++ b/nym-node/nym-node-requests/Cargo.toml @@ -26,6 +26,7 @@ url = { workspace = true, features = ["serde"] } nym-crypto = { workspace = true, features = [ "asymmetric", "serde", + "libcrux_x25519" ] } nym-exit-policy = { workspace = true } nym-noise-keys = { workspace = true } @@ -47,7 +48,7 @@ nym-bin-common = { workspace = true, features = [ [dev-dependencies] tokio = { workspace = true, features = ["full"] } -rand_chacha = { workspace = true } +nym-test-utils = { workspace = true } nym-crypto = { workspace = true, features = ["rand"] } diff --git a/nym-node/nym-node-requests/src/api/client.rs b/nym-node/nym-node-requests/src/api/client.rs index dd014851cc..6e3b93eeca 100644 --- a/nym-node/nym-node-requests/src/api/client.rs +++ b/nym-node/nym-node-requests/src/api/client.rs @@ -1,24 +1,24 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use super::v1::gateway::models::Wireguard; +use super::v1::metrics::models::SessionStats; +use crate::api::SignedLewesProtocol; +use crate::api::v1::authenticator::models::Authenticator; use crate::api::v1::gateway::models::WebSockets; +use crate::api::v1::health::models::NodeHealth; +use crate::api::v1::ip_packet_router::models::IpPacketRouter; +use crate::api::v1::network_requester::exit_policy::models::UsedExitPolicy; +use crate::api::v1::network_requester::models::NetworkRequester; use crate::api::v1::node::models::{ AuxiliaryDetails, NodeDescription, NodeRoles, SignedHostInformation, }; +use crate::api::v1::node_load::models::NodeLoad; use crate::routes; use async_trait::async_trait; use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_http_api_client::{ApiClient, HttpClientError}; -use super::v1::gateway::models::Wireguard; -use super::v1::metrics::models::SessionStats; -use crate::api::v1::authenticator::models::Authenticator; -use crate::api::v1::health::models::NodeHealth; -use crate::api::v1::ip_packet_router::models::IpPacketRouter; -use crate::api::v1::lewes_protocol::models::LewesProtocol; -use crate::api::v1::network_requester::exit_policy::models::UsedExitPolicy; -use crate::api::v1::network_requester::models::NetworkRequester; -use crate::api::v1::node_load::models::NodeLoad; pub use nym_http_api_client::Client; pub type NymNodeApiClientError = HttpClientError; @@ -98,7 +98,7 @@ pub trait NymNodeApiClientExt: ApiClient { .await } - async fn get_lewes_protocol(&self) -> Result { + async fn get_lewes_protocol(&self) -> Result { self.get_json_from(routes::api::v1::lp_absolute()).await } } diff --git a/nym-node/nym-node-requests/src/api/mod.rs b/nym-node/nym-node-requests/src/api/mod.rs index ba32570c2e..d9c9835af0 100644 --- a/nym-node/nym-node-requests/src/api/mod.rs +++ b/nym-node/nym-node-requests/src/api/mod.rs @@ -20,6 +20,7 @@ pub use client::Client; // create the type alias manually if openapi is not enabled pub type SignedHostInformation = SignedData; +pub type SignedLewesProtocol = SignedData; #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct SignedDataHostInfo { @@ -28,11 +29,20 @@ pub struct SignedDataHostInfo { pub signature: String, } +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct SignedLewesProtocolInfo { + // #[serde(flatten)] + pub data: crate::api::v1::lewes_protocol::models::LewesProtocol, + pub signature: String, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignedData { // #[serde(flatten)] pub data: T, - pub signature: String, + + #[serde(with = "ed25519::bs58_ed25519_signature")] + pub signature: ed25519::Signature, } impl SignedData { @@ -42,7 +52,7 @@ impl SignedData { { let plaintext = serde_json::to_string(&data)?; - let signature = key.sign(plaintext).to_base58_string(); + let signature = key.sign(plaintext); Ok(SignedData { data, signature }) } @@ -54,11 +64,7 @@ impl SignedData { return false; }; - let Ok(signature) = ed25519::Signature::from_base58_string(&self.signature) else { - return false; - }; - - key.verify(plaintext, &signature).is_ok() + key.verify(plaintext, &self.signature).is_ok() } } @@ -72,7 +78,7 @@ impl SignedHostInformation { let legacy_v3 = SignedData { data: LegacyHostInformationV3::from(self.data.clone()), - signature: self.signature.clone(), + signature: self.signature, }; if legacy_v3.verify(&self.keys.ed25519_identity) { @@ -82,7 +88,7 @@ impl SignedHostInformation { // attempt to verify legacy signatures let legacy_v3 = SignedData { data: LegacyHostInformationV3::from(self.data.clone()), - signature: self.signature.clone(), + signature: self.signature, }; if legacy_v3.verify(&self.keys.ed25519_identity) { @@ -91,7 +97,7 @@ impl SignedHostInformation { let legacy_v2 = SignedData { data: LegacyHostInformationV2::from(legacy_v3.data), - signature: self.signature.clone(), + signature: self.signature, }; if legacy_v2.verify(&self.keys.ed25519_identity) { @@ -100,7 +106,7 @@ impl SignedHostInformation { SignedData { data: LegacyHostInformationV1::from(legacy_v2.data), - signature: self.signature.clone(), + signature: self.signature, } .verify(&self.keys.ed25519_identity) } @@ -128,16 +134,15 @@ impl Display for ErrorResponse { #[allow(deprecated)] #[cfg(test)] mod tests { - use super::*; use crate::api::v1::node::models::{HostKeys, SphinxKey}; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_noise_keys::{NoiseVersion, VersionedNoiseKeyV1}; - use rand_chacha::rand_core::SeedableRng; + use nym_test_utils::helpers::deterministic_rng; #[test] fn dummy_signed_host_verification() { - let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); + let mut rng = deterministic_rng(); let ed22519 = ed25519::KeyPair::new(&mut rng); let x25519_sphinx = x25519::KeyPair::new(&mut rng); let x25519_sphinx2 = x25519::KeyPair::new(&mut rng); @@ -237,7 +242,7 @@ mod tests { #[test] fn dummy_legacy_v3_signed_host_verification() { - let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); + let mut rng = deterministic_rng(); let ed22519 = ed25519::KeyPair::new(&mut rng); let x25519_sphinx = x25519::KeyPair::new(&mut rng); let x25519_noise = x25519::KeyPair::new(&mut rng); @@ -331,7 +336,7 @@ mod tests { #[test] fn dummy_legacy_v2_signed_host_verification() { - let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); + let mut rng = deterministic_rng(); let ed22519 = ed25519::KeyPair::new(&mut rng); let x25519_sphinx = x25519::KeyPair::new(&mut rng); let x25519_noise = x25519::KeyPair::new(&mut rng); @@ -420,7 +425,7 @@ mod tests { #[test] fn dummy_legacy_v1_signed_host_verification() { - let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); + let mut rng = deterministic_rng(); let ed22519 = ed25519::KeyPair::new(&mut rng); let x25519_sphinx = x25519::KeyPair::new(&mut rng); 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 17900472b6..9c8104e494 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 @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use nym_crypto::asymmetric::x25519; -use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; +use nym_crypto::asymmetric::x25519::serde_helpers::bs58_dh_public_key; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::BTreeMap; use strum_macros::{Display, EnumIter, EnumString}; #[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] @@ -21,21 +21,16 @@ pub struct LewesProtocol { /// LP UDP data address (default: 51264) for Sphinx packets wrapped in LP pub data_port: u16, - #[serde(with = "bs58_x25519_pubkey")] + #[serde(with = "bs58_dh_public_key")] #[schemars(with = "String")] - #[schema(value_type = String)] + #[cfg_attr(feature = "openapi", schema(value_type = String))] /// LP public key - pub x25519: x25519::PublicKey, + pub x25519: x25519::DHPublicKey, /// 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>, + pub kem_keys: BTreeMap>, } impl LewesProtocol { @@ -43,9 +38,8 @@ impl LewesProtocol { enabled: bool, control_port: u16, data_port: u16, - x25519: x25519::PublicKey, - kem_keys: HashMap>, - signing_keys: HashMap>, + x25519: x25519::DHPublicKey, + kem_keys: BTreeMap>, ) -> Self { LewesProtocol { enabled, @@ -53,7 +47,6 @@ impl LewesProtocol { data_port, x25519, kem_keys, - signing_keys, } } } @@ -76,13 +69,13 @@ impl LewesProtocol { PartialOrd, Display, EnumString, + Ord, )] +#[serde(rename_all = "snake_case")] #[strum(serialize_all = "lowercase")] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub enum LPKEM { MlKem768, - XWing, - X25519, McEliece, } @@ -90,8 +83,6 @@ 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, } } @@ -101,8 +92,6 @@ 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, } } @@ -122,7 +111,9 @@ impl From for LPKEM { Display, EnumString, EnumIter, + Ord, )] +#[serde(rename_all = "snake_case")] #[strum(serialize_all = "lowercase")] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub enum LPHashFunction { @@ -169,6 +160,7 @@ impl From for LPHashFunction { EnumString, EnumIter, )] +#[serde(rename_all = "snake_case")] #[strum(serialize_all = "lowercase")] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub enum LPSignatureScheme { @@ -199,8 +191,6 @@ mod tests { #[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"); } diff --git a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs index 125c8b4352..2d0eddfda4 100644 --- a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs @@ -106,6 +106,8 @@ pub mod verloc { #[serde(with = "bs58_ed25519_pubkey")] #[cfg_attr(feature = "openapi", schema(value_type = String))] pub node_identity: ed25519::PublicKey, + + pub latest_measurement: Option, } #[derive(Serialize, Deserialize, Default, Debug, Clone)] diff --git a/nym-node/src/cli/commands/run/args.rs b/nym-node/src/cli/commands/run/args.rs index 8c214f5e5e..2fbc4d3623 100644 --- a/nym-node/src/cli/commands/run/args.rs +++ b/nym-node/src/cli/commands/run/args.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::cli::helpers::{ - ConfigArgs, EntryGatewayArgs, ExitGatewayArgs, HostArgs, HttpArgs, MetricsArgs, MixnetArgs, - VerlocArgs, WireguardArgs, + ConfigArgs, EntryGatewayArgs, ExitGatewayArgs, HostArgs, HttpArgs, LpArgs, MetricsArgs, + MixnetArgs, VerlocArgs, WireguardArgs, }; use crate::config::persistence::NymNodePaths; use crate::config::{Config, ConfigBuilder, NodeMode, NodeModes}; @@ -125,6 +125,9 @@ pub(crate) struct Args { #[clap(flatten)] exit_gateway: ExitGatewayArgs, + + #[clap(flatten)] + lp: LpArgs, } impl Args { @@ -174,6 +177,7 @@ impl Args { .with_metrics(self.metrics.build_config_section()) .with_gateway_tasks(self.entry_gateway.build_config_section(&data_dir)?) .with_service_providers(self.exit_gateway.build_config_section(&data_dir)) + .with_lp(self.lp.build_config_section()) .build() } @@ -193,6 +197,7 @@ impl Args { config.service_providers = self .exit_gateway .override_config_section(config.service_providers); + config.lp = self.lp.override_config_section(config.lp); config } } diff --git a/nym-node/src/cli/helpers.rs b/nym-node/src/cli/helpers.rs index 00193698bf..147a5a0129 100644 --- a/nym-node/src/cli/helpers.rs +++ b/nym-node/src/cli/helpers.rs @@ -458,15 +458,6 @@ pub(crate) struct EntryGatewayArgs { )] #[zeroize(skip)] pub(crate) upgrade_mode_attester_public_key: Option, - - /// Use mock ecash manager for LP testing. - /// WARNING: Only use this for local testing! Never enable in production. - /// When enabled, the LP listener will accept any credential without blockchain verification. - #[clap( - long, - env = NYMNODE_LP_USE_MOCK_ECASH_ARG - )] - pub(crate) lp_use_mock_ecash: Option, } impl EntryGatewayArgs { @@ -500,9 +491,6 @@ impl EntryGatewayArgs { if let Some(upgrade_mode_attester_public_key) = self.upgrade_mode_attester_public_key { section.upgrade_mode.attester_public_key = upgrade_mode_attester_public_key } - if let Some(use_mock_ecash) = self.lp_use_mock_ecash { - section.lp.debug.use_mock_ecash = use_mock_ecash - } section } @@ -549,3 +537,62 @@ impl ExitGatewayArgs { section } } + +#[derive(clap::Args, Debug)] +pub(crate) struct LpArgs { + /// Bind address for the TCP LP control traffic. + /// default: `[::]:41264` + #[clap( + long, + env = NYMNODE_LP_CONTROL_BIND_ADDRESS_ARG + )] + pub(crate) lp_control_bind_address: Option, + + /// Custom announced port for listening for the TCP LP control traffic. + /// If unspecified, the value from the `lp_control_bind_address` will be used instead + #[clap( + long, + env = NYMNODE_LP_CONTROL_ANNOUNCE_PORT_ARG + )] + pub(crate) lp_control_announce_port: Option, + + /// Bind address for the UDP LP data traffic. + /// default: `[::]:51264` + #[clap( + long, + env = NYMNODE_LP_DATA_BIND_ADDRESS_ARG + )] + pub(crate) lp_data_bind_address: Option, + + /// Custom announced port for listening for the UDP LP data traffic. + /// If unspecified, the value from the `lp_data_bind_address` will be used instead + #[clap( + long, + env = NYMNODE_LP_DATA_ANNOUNCE_PORT_ARG + )] + pub(crate) lp_data_announce_port: Option, + + /// Use mock ecash manager for LP testing. + /// WARNING: Only use this for local testing! Never enable in production. + /// When enabled, the LP listener will accept any credential without blockchain verification. + #[clap( + long, + env = NYMNODE_LP_USE_MOCK_ECASH_ARG + )] + pub(crate) lp_use_mock_ecash: Option, +} + +impl LpArgs { + // TODO: could we perhaps make a clap error here and call `safe_exit` instead? + pub(crate) fn build_config_section(self) -> config::LpConfig { + self.override_config_section(config::LpConfig::default()) + } + + pub(crate) fn override_config_section(self, mut section: config::LpConfig) -> config::LpConfig { + if let Some(use_mock_ecash) = self.lp_use_mock_ecash { + section.debug.use_mock_ecash = use_mock_ecash + } + + section + } +} diff --git a/nym-node/src/cli/mod.rs b/nym-node/src/cli/mod.rs index ad72438f41..7042a739ea 100644 --- a/nym-node/src/cli/mod.rs +++ b/nym-node/src/cli/mod.rs @@ -8,7 +8,6 @@ use crate::cli::commands::{ use crate::env::vars::{NYMNODE_CONFIG_ENV_FILE_ARG, NYMNODE_NO_BANNER_ARG}; use clap::{Args, Parser, Subcommand}; use nym_bin_common::bin_info; -use std::future::Future; use std::sync::OnceLock; pub(crate) mod commands; @@ -22,6 +21,43 @@ fn pretty_build_info_static() -> &'static str { PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } +/// OpenTelemetry-related CLI arguments. Only present when built with the `otel` feature. +#[cfg(feature = "otel")] +#[derive(Args, Debug, Clone)] +pub(crate) struct OtelArgs { + /// Enable OpenTelemetry tracing export via OTLP/gRPC. + #[clap(long, env = "NYMNODE_OTEL_ENABLE")] + pub(crate) otel: bool, + + /// OpenTelemetry OTLP collector endpoint (gRPC). + /// Only used when --otel is enabled. + /// For SigNoz Cloud use https://ingest..signoz.cloud:443 + #[clap( + long, + env = "NYMNODE_OTEL_ENDPOINT", + default_value = "http://localhost:4317" + )] + pub(crate) otel_endpoint: String, + + /// SigNoz Cloud ingestion key for authenticated OTLP export. + /// Only needed for SigNoz Cloud (not self-hosted). + #[clap(long, env = "NYMNODE_OTEL_KEY")] + pub(crate) otel_key: Option, + + /// Deployment environment label attached to all exported traces. + /// Used to distinguish sandbox / mainnet / canary in the OTel backend. + #[clap(long, env = "NYMNODE_OTEL_ENV", default_value = "mainnet")] + pub(crate) otel_env: String, + + /// Trace sampling ratio (0.0 to 1.0). e.g. 0.1 = 10%% of traces exported. Reduces cost. + #[clap(long, env = "NYMNODE_OTEL_SAMPLE_RATIO", default_value = "0.1")] + pub(crate) otel_sample_ratio: f64, + + /// Timeout in seconds for each OTLP export batch. Prevents unbounded blocking. + #[clap(long, env = "NYMNODE_OTEL_EXPORT_TIMEOUT", default_value = "10")] + pub(crate) otel_export_timeout: u64, +} + #[derive(Parser, Debug)] #[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] pub(crate) struct Cli { @@ -40,44 +76,82 @@ pub(crate) struct Cli { )] pub(crate) no_banner: bool, + #[cfg(feature = "otel")] + #[clap(flatten)] + pub(crate) otel: OtelArgs, + #[clap(subcommand)] command: Commands, } impl Cli { - fn execute_async(fut: F) -> anyhow::Result { - Ok(tokio::runtime::Builder::new_multi_thread() + pub(crate) fn execute(self) -> anyhow::Result<()> { + let runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() - .build()? - .block_on(fut)) + .build()?; + + // Set up tracing inside the runtime so the OTel batch exporter (when enabled) + // can spawn its background tasks on the tokio reactor. + let use_otel = matches!(self.command, Commands::Run(..)); + let _otel_guard = runtime.block_on(async { self.setup_logging(use_otel) })?; + + // `_otel_guard` is dropped at function exit, flushing pending spans via its Drop impl + runtime.block_on(async { + match self.command { + Commands::BuildInfo(args) => build_info::execute(args)?, + Commands::BondingInformation(args) => bonding_information::execute(args).await?, + Commands::NodeDetails(args) => node_details::execute(args).await?, + Commands::Run(args) => run::execute(*args).await?, + Commands::Migrate(args) => migrate::execute(*args)?, + Commands::Sign(args) => sign::execute(args).await?, + Commands::TestThroughput(args) => test_throughput::execute(args)?, + Commands::UnsafeResetSphinxKeys(args) => reset_sphinx_keys::execute(args).await?, + Commands::Debug(debug) => match debug.command { + DebugCommands::ResetProvidersGatewayDbs(args) => { + debug::reset_providers_dbs::execute(args).await? + } + }, + } + Ok::<(), anyhow::Error>(()) + }) } - pub(crate) fn execute(self) -> anyhow::Result<()> { - // NOTE: `test_throughput` sets up its own logger as it has to include additional layers - if !matches!(self.command, Commands::TestThroughput(..)) { - crate::logging::setup_tracing_logger()?; + #[cfg(feature = "otel")] + fn build_otel_config(&self) -> Option { + if self.otel.otel { + Some(crate::logging::OtelConfig { + endpoint: self.otel.otel_endpoint.clone(), + service_name: "nym-node".to_string(), + ingestion_key: self.otel.otel_key.clone(), + environment: self.otel.otel_env.clone(), + sample_ratio: self.otel.otel_sample_ratio, + export_timeout_secs: self.otel.otel_export_timeout, + }) + } else { + None } + } - match self.command { - Commands::BuildInfo(args) => build_info::execute(args)?, - Commands::BondingInformation(args) => { - { Self::execute_async(bonding_information::execute(args))? }? - } - Commands::NodeDetails(args) => { Self::execute_async(node_details::execute(args))? }?, - Commands::Run(args) => { Self::execute_async(run::execute(*args))? }?, - Commands::Migrate(args) => migrate::execute(*args)?, - Commands::Sign(args) => { Self::execute_async(sign::execute(args))? }?, - Commands::TestThroughput(args) => test_throughput::execute(args)?, - Commands::UnsafeResetSphinxKeys(args) => { - { Self::execute_async(reset_sphinx_keys::execute(args))? }? - } - Commands::Debug(debug) => match debug.command { - DebugCommands::ResetProvidersGatewayDbs(args) => { - { Self::execute_async(debug::reset_providers_dbs::execute(args))? }? - } - }, + #[cfg(feature = "otel")] + fn setup_logging(&self, use_otel: bool) -> anyhow::Result> { + if matches!(self.command, Commands::TestThroughput(..)) { + return Ok(None); } - Ok(()) + let otel_config = if use_otel { + self.build_otel_config() + } else { + None + }; + crate::logging::setup_tracing_logger(otel_config) + } + + #[cfg(not(feature = "otel"))] + fn setup_logging(&self, _use_otel: bool) -> anyhow::Result> { + if matches!(self.command, Commands::TestThroughput(..)) { + return Ok(None); + } + crate::logging::setup_tracing_logger()?; + Ok(None) } } diff --git a/nym-node/src/config/gateway_tasks.rs b/nym-node/src/config/gateway_tasks.rs index 99577f82ca..ed6335fed8 100644 --- a/nym-node/src/config/gateway_tasks.rs +++ b/nym-node/src/config/gateway_tasks.rs @@ -46,9 +46,6 @@ pub struct GatewayTasksConfig { pub upgrade_mode: UpgradeModeWatcher, - #[serde(default)] - pub lp: nym_gateway::node::LpConfig, - #[serde(default)] pub debug: Debug, } @@ -228,7 +225,6 @@ impl GatewayTasksConfig { announce_ws_port: None, announce_wss_port: None, upgrade_mode: UpgradeModeWatcher::new()?, - lp: Default::default(), debug: Default::default(), }) } diff --git a/nym-node/src/config/helpers.rs b/nym-node/src/config/helpers.rs index 20aac7cf0c..7c2e96215b 100644 --- a/nym-node/src/config/helpers.rs +++ b/nym-node/src/config/helpers.rs @@ -27,7 +27,6 @@ fn ephemeral_gateway_config(config: &Config) -> nym_gateway::config::Config { enabled: config.service_providers.network_requester.debug.enabled, }, config.gateway_tasks.upgrade_mode.clone(), - config.gateway_tasks.lp, nym_gateway::config::Debug { client_bandwidth_max_flushing_rate: config .gateway_tasks @@ -92,8 +91,6 @@ pub struct GatewayTasksConfig { pub auth_opts: Option, #[allow(dead_code)] pub wg_opts: LocalWireguardOpts, - #[allow(dead_code)] - pub lp: nym_gateway::node::LpConfig, } // that function is rather disgusting, but I hope it's not going to live for too long @@ -227,7 +224,6 @@ pub fn gateway_tasks_config(config: &Config) -> GatewayTasksConfig { ipr_opts: Some(ipr_opts), auth_opts: Some(auth_opts), wg_opts, - lp: config.gateway_tasks.lp, } } diff --git a/nym-node/src/config/lp.rs b/nym-node/src/config/lp.rs new file mode 100644 index 0000000000..260fea64b1 --- /dev/null +++ b/nym-node/src/config/lp.rs @@ -0,0 +1,153 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_config::serde_helpers::de_maybe_port; +use std::net::{IpAddr, Ipv6Addr, SocketAddr}; +use std::time::Duration; + +/// Configuration for LP listener +#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct LpConfig { + /// Bind address for the TCP LP control traffic. + /// default: `[::]:41264` + pub control_bind_address: SocketAddr, + + /// Bind address for the UDP LP data traffic. + /// default: `[::]:51264` + pub data_bind_address: SocketAddr, + + /// Custom announced port for listening for the TCP LP control traffic. + /// If unspecified, the value from the `control_bind_address` will be used instead + /// (default: None) + #[serde(deserialize_with = "de_maybe_port")] + 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 + /// (default: None) + #[serde(deserialize_with = "de_maybe_port")] + pub announce_data_port: Option, + + /// Auxiliary configuration + #[serde(default)] + pub debug: LpDebug, +} + +#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct LpDebug { + /// Maximum concurrent connections + pub max_connections: usize, + + /// Use mock ecash manager for testing (default: false) + /// + /// When enabled, the LP listener will use a mock ecash verifier that + /// accepts any credential without blockchain verification. This is + /// useful for testing the LP protocol implementation without requiring + /// a full blockchain/contract setup. + /// + /// WARNING: Only use this for local testing! Never enable in production. + pub use_mock_ecash: bool, + + /// Maximum age of in-progress handshakes before cleanup (default: 90s) + /// + /// Handshakes should complete quickly (3-5 packets). This TTL accounts for: + /// - Network latency and retransmits + /// - Slow clients + /// - Clock skew tolerance + /// + /// Stale handshakes are removed by the cleanup task to prevent memory leaks. + #[serde(with = "humantime_serde")] + pub handshake_ttl: Duration, + + /// Maximum age of established sessions before cleanup (default: 24h) + /// + /// Sessions can be long-lived for dVPN tunnels. This TTL should be set + /// high enough to accommodate expected usage patterns: + /// - dVPN sessions: hours to days + /// - Registration: minutes + /// + /// Sessions with no activity for this duration are removed by the cleanup task. + #[serde(with = "humantime_serde")] + pub session_ttl: Duration, + + /// How often to run the state cleanup task (default: 5 minutes) + /// + /// The cleanup task scans for and removes stale handshakes and sessions. + /// Lower values = more frequent cleanup but higher overhead. + /// Higher values = less overhead but slower memory reclamation. + #[serde(with = "humantime_serde")] + pub state_cleanup_interval: Duration, + + /// Maximum concurrent forward connections (default: 1000) + /// + /// Limits simultaneous outbound connections when forwarding LP packets to other gateways + /// during telescope setup. This prevents file descriptor exhaustion under high load. + /// + /// When at capacity, new forward requests return an error, signaling the client + /// to choose a different gateway. + pub max_concurrent_forwards: usize, +} + +impl LpConfig { + pub const DEFAULT_CONTROL_PORT: u16 = 41264; + pub const DEFAULT_DATA_PORT: u16 = 51264; + + pub fn announced_control_port(&self) -> u16 { + self.announce_control_port + .unwrap_or(self.control_bind_address.port()) + } + + pub fn announced_data_port(&self) -> u16 { + self.announce_data_port + .unwrap_or(self.data_bind_address.port()) + } +} + +impl Default for LpConfig { + fn default() -> Self { + LpConfig { + control_bind_address: SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + Self::DEFAULT_CONTROL_PORT, + ), + data_bind_address: SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + Self::DEFAULT_DATA_PORT, + ), + announce_control_port: None, + announce_data_port: None, + debug: Default::default(), + } + } +} + +impl LpDebug { + pub const DEFAULT_MAX_CONNECTIONS: usize = 10000; + + // 90 seconds - handshakes should complete quickly + pub const DEFAULT_HANDSHAKE_TTL: Duration = Duration::from_secs(90); + + // 24 hours - for long-lived dVPN sessions + pub const DEFAULT_SESSION_TTL: Duration = Duration::from_secs(86400); + + // 5 minutes - balances memory reclamation with task overhead + pub const DEFAULT_STATE_CLEANUP_INTERVAL: Duration = Duration::from_secs(300); + + // Limits concurrent outbound connections to prevent fd exhaustion + pub const DEFAULT_MAX_CONCURRENT_FORWARDS: usize = 1000; +} + +impl Default for LpDebug { + fn default() -> Self { + LpDebug { + max_connections: Self::DEFAULT_MAX_CONNECTIONS, + use_mock_ecash: false, + handshake_ttl: Self::DEFAULT_HANDSHAKE_TTL, + session_ttl: Self::DEFAULT_SESSION_TTL, + state_cleanup_interval: Self::DEFAULT_STATE_CLEANUP_INTERVAL, + max_concurrent_forwards: Self::DEFAULT_MAX_CONCURRENT_FORWARDS, + } + } +} diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index 064efc8b6a..14b00b7f5e 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -4,6 +4,7 @@ use crate::config::persistence::{NymNodePaths, ReplayProtectionPaths}; use crate::config::template::CONFIG_TEMPLATE; use crate::error::NymNodeError; +use crate::node::replay_protection::{bitmap_size, items_in_bloomfilter}; use celes::Country; use clap::ValueEnum; use human_repr::HumanCount; @@ -35,6 +36,7 @@ use url::Url; pub mod authenticator; pub mod gateway_tasks; pub mod helpers; +pub mod lp; pub mod metrics; mod old_configs; pub mod persistence; @@ -43,9 +45,9 @@ mod template; pub mod upgrade_helpers; pub use crate::config::gateway_tasks::GatewayTasksConfig; +pub use crate::config::lp::{LpConfig, LpDebug}; pub use crate::config::metrics::MetricsConfig; pub use crate::config::service_providers::ServiceProvidersConfig; -use crate::node::replay_protection::{bitmap_size, items_in_bloomfilter}; const DEFAULT_NYMNODES_DIR: &str = "nym-nodes"; @@ -186,6 +188,8 @@ pub struct ConfigBuilder { pub metrics: Option, + pub lp: Option, + pub logging: Option, } @@ -205,6 +209,7 @@ impl ConfigBuilder { gateway_tasks: None, service_providers: None, metrics: None, + lp: None, logging: None, } } @@ -234,6 +239,11 @@ impl ConfigBuilder { self } + pub fn with_lp(mut self, section: impl Into>) -> Self { + self.lp = section.into(); + self + } + pub fn with_wireguard(mut self, section: impl Into>) -> Self { self.wireguard = section.into(); self @@ -284,6 +294,7 @@ impl ConfigBuilder { .storage_paths .unwrap_or_else(|| NymNodePaths::new(&self.data_dir)), metrics: self.metrics.unwrap_or_default(), + lp: self.lp.unwrap_or_default(), gateway_tasks, service_providers: self .service_providers @@ -323,6 +334,9 @@ pub struct Config { pub wireguard: Wireguard, + #[serde(default)] + pub lp: LpConfig, + #[serde(alias = "entry_gateway")] pub gateway_tasks: GatewayTasksConfig, diff --git a/nym-node/src/config/old_configs/mod.rs b/nym-node/src/config/old_configs/mod.rs index c46a72d1a1..2116f94ec8 100644 --- a/nym-node/src/config/old_configs/mod.rs +++ b/nym-node/src/config/old_configs/mod.rs @@ -4,6 +4,7 @@ mod old_config_v1; mod old_config_v10; mod old_config_v11; +mod old_config_v12; mod old_config_v2; mod old_config_v3; mod old_config_v4; @@ -24,3 +25,4 @@ pub use old_config_v8::try_upgrade_config_v8; pub use old_config_v9::try_upgrade_config_v9; pub use old_config_v10::try_upgrade_config_v10; pub use old_config_v11::try_upgrade_config_v11; +pub use old_config_v12::try_upgrade_config_v12; diff --git a/nym-node/src/config/old_configs/old_config_v11.rs b/nym-node/src/config/old_configs/old_config_v11.rs index 6d3a380357..6e1abb705d 100644 --- a/nym-node/src/config/old_configs/old_config_v11.rs +++ b/nym-node/src/config/old_configs/old_config_v11.rs @@ -1,24 +1,11 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::authenticator::{Authenticator, AuthenticatorDebug}; -use crate::config::gateway_tasks::{ - ClientBandwidthDebug, StaleMessageDebug, UpgradeModeWatcher, ZkNymTicketHandlerDebug, -}; -use crate::config::persistence::{ - AuthenticatorPaths, GatewayTasksPaths, IpPacketRouterPaths, KeysPaths, NetworkRequesterPaths, - NymNodePaths, ReplayProtectionPaths, ServiceProvidersPaths, WireguardPaths, -}; -use crate::config::service_providers::{ - IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug, -}; -use crate::config::{ - Config, GatewayTasksConfig, Host, Http, KeyRotation, KeyRotationDebug, Mixnet, MixnetDebug, - NodeModes, ReplayProtection, ReplayProtectionDebug, ServiceProvidersConfig, Verloc, - VerlocDebug, Wireguard, gateway_tasks, service_providers, +use crate::config::old_configs::old_config_v12::{ + ConfigV12, DebugV12, GatewayTasksConfigDebugV12, GatewayTasksConfigV12, LpConfigV12, + UpgradeModeWatcherV12, WireguardV12, }; use crate::error::NymNodeError; -use nym_bin_common::logging::LoggingSettings; use nym_config::read_config_from_toml_file; use serde::{Deserialize, Serialize}; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; @@ -185,7 +172,7 @@ impl ConfigV11 { pub async fn try_upgrade_config_v11>( path: P, prev_config: Option, -) -> Result { +) -> Result { debug!("attempting to load v11 config..."); let old_cfg = if let Some(prev_config) = prev_config { @@ -197,144 +184,16 @@ pub async fn try_upgrade_config_v11>( // for future reference: when creating v12 migration, // look at how v10 -> v11 is implemented // you might be able to create a bunch of type aliases again to save you some headache - let cfg = Config { + let cfg = ConfigV12 { save_path: old_cfg.save_path, id: old_cfg.id, - modes: NodeModes { - mixnode: old_cfg.modes.mixnode, - entry: old_cfg.modes.entry, - exit: old_cfg.modes.exit, - }, - host: Host { - public_ips: old_cfg.host.public_ips, - hostname: old_cfg.host.hostname, - location: old_cfg.host.location, - }, - mixnet: Mixnet { - bind_address: old_cfg.mixnet.bind_address, - announce_port: old_cfg.mixnet.announce_port, - nym_api_urls: old_cfg.mixnet.nym_api_urls, - nyxd_urls: old_cfg.mixnet.nyxd_urls, - replay_protection: ReplayProtection { - storage_paths: ReplayProtectionPaths { - current_bloomfilters_directory: old_cfg - .mixnet - .replay_protection - .storage_paths - .current_bloomfilters_directory, - }, - debug: ReplayProtectionDebug { - unsafe_disabled: old_cfg.mixnet.replay_protection.debug.unsafe_disabled, - maximum_replay_detection_deferral: old_cfg - .mixnet - .replay_protection - .debug - .maximum_replay_detection_deferral, - maximum_replay_detection_pending_packets: old_cfg - .mixnet - .replay_protection - .debug - .maximum_replay_detection_pending_packets, - false_positive_rate: old_cfg.mixnet.replay_protection.debug.false_positive_rate, - initial_expected_packets_per_second: old_cfg - .mixnet - .replay_protection - .debug - .initial_expected_packets_per_second, - bloomfilter_minimum_packets_per_second_size: old_cfg - .mixnet - .replay_protection - .debug - .bloomfilter_minimum_packets_per_second_size, - bloomfilter_size_multiplier: old_cfg - .mixnet - .replay_protection - .debug - .bloomfilter_size_multiplier, - bloomfilter_disk_flushing_rate: old_cfg - .mixnet - .replay_protection - .debug - .bloomfilter_disk_flushing_rate, - }, - }, - key_rotation: KeyRotation { - debug: KeyRotationDebug { - rotation_state_poling_interval: old_cfg - .mixnet - .key_rotation - .debug - .rotation_state_poling_interval, - }, - }, - debug: MixnetDebug { - maximum_forward_packet_delay: old_cfg.mixnet.debug.maximum_forward_packet_delay, - packet_forwarding_initial_backoff: old_cfg - .mixnet - .debug - .packet_forwarding_initial_backoff, - packet_forwarding_maximum_backoff: old_cfg - .mixnet - .debug - .packet_forwarding_maximum_backoff, - initial_connection_timeout: old_cfg.mixnet.debug.initial_connection_timeout, - maximum_connection_buffer_size: old_cfg.mixnet.debug.maximum_connection_buffer_size, - unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise, - use_legacy_packet_encoding: old_cfg.mixnet.debug.use_legacy_packet_encoding, - }, - }, - storage_paths: NymNodePaths { - keys: KeysPaths { - private_ed25519_identity_key_file: old_cfg - .storage_paths - .keys - .private_ed25519_identity_key_file, - public_ed25519_identity_key_file: old_cfg - .storage_paths - .keys - .public_ed25519_identity_key_file, - primary_x25519_sphinx_key_file: old_cfg - .storage_paths - .keys - .primary_x25519_sphinx_key_file, - private_x25519_noise_key_file: old_cfg - .storage_paths - .keys - .private_x25519_noise_key_file, - public_x25519_noise_key_file: old_cfg - .storage_paths - .keys - .public_x25519_noise_key_file, - secondary_x25519_sphinx_key_file: old_cfg - .storage_paths - .keys - .secondary_x25519_sphinx_key_file, - }, - description: old_cfg.storage_paths.description, - }, - http: Http { - bind_address: old_cfg.http.bind_address, - landing_page_assets_path: old_cfg.http.landing_page_assets_path, - access_token: old_cfg.http.access_token, - expose_system_info: old_cfg.http.expose_system_info, - expose_system_hardware: old_cfg.http.expose_system_hardware, - expose_crypto_hardware: old_cfg.http.expose_crypto_hardware, - node_load_cache_ttl: old_cfg.http.node_load_cache_ttl, - }, - verloc: Verloc { - bind_address: old_cfg.verloc.bind_address, - announce_port: old_cfg.verloc.announce_port, - debug: VerlocDebug { - packets_per_node: old_cfg.verloc.debug.packets_per_node, - connection_timeout: old_cfg.verloc.debug.connection_timeout, - packet_timeout: old_cfg.verloc.debug.packet_timeout, - delay_between_packets: old_cfg.verloc.debug.delay_between_packets, - tested_nodes_batch_size: old_cfg.verloc.debug.tested_nodes_batch_size, - testing_interval: old_cfg.verloc.debug.testing_interval, - retry_timeout: old_cfg.verloc.debug.retry_timeout, - }, - }, - wireguard: Wireguard { + modes: old_cfg.modes, + host: old_cfg.host, + mixnet: old_cfg.mixnet, + storage_paths: old_cfg.storage_paths, + http: old_cfg.http, + verloc: old_cfg.verloc, + wireguard: WireguardV12 { enabled: old_cfg.wireguard.enabled, bind_address: old_cfg.wireguard.bind_address, private_ipv4: old_cfg.wireguard.private_ipv4, @@ -343,259 +202,48 @@ pub async fn try_upgrade_config_v11>( announced_metadata_port: old_cfg.wireguard.announced_metadata_port, private_network_prefix_v4: old_cfg.wireguard.private_network_prefix_v4, private_network_prefix_v6: old_cfg.wireguard.private_network_prefix_v6, + // \/ ADDED use_userspace: false, - storage_paths: WireguardPaths { - private_diffie_hellman_key_file: old_cfg - .wireguard - .storage_paths - .private_diffie_hellman_key_file, - public_diffie_hellman_key_file: old_cfg - .wireguard - .storage_paths - .public_diffie_hellman_key_file, - }, + // /\ ADDED + storage_paths: old_cfg.wireguard.storage_paths, }, - gateway_tasks: GatewayTasksConfig { - storage_paths: GatewayTasksPaths { - clients_storage: old_cfg.gateway_tasks.storage_paths.clients_storage, - stats_storage: old_cfg.gateway_tasks.storage_paths.stats_storage, - cosmos_mnemonic: old_cfg.gateway_tasks.storage_paths.cosmos_mnemonic, - bridge_client_params: old_cfg.gateway_tasks.storage_paths.bridge_client_params, - }, + gateway_tasks: GatewayTasksConfigV12 { + storage_paths: old_cfg.gateway_tasks.storage_paths, enforce_zk_nyms: old_cfg.gateway_tasks.enforce_zk_nyms, ws_bind_address: old_cfg.gateway_tasks.ws_bind_address, announce_ws_port: old_cfg.gateway_tasks.announce_ws_port, announce_wss_port: old_cfg.gateway_tasks.announce_wss_port, // \/ ADDED - upgrade_mode: UpgradeModeWatcher::new() + upgrade_mode: UpgradeModeWatcherV12::new() .inspect_err(|_| { error!( "failed to set custom upgrade mode configuration - falling back to mainnet" ) }) - .unwrap_or(UpgradeModeWatcher::new_mainnet()), - lp: Default::default(), + .unwrap_or(UpgradeModeWatcherV12::new_mainnet()), + lp: LpConfigV12::default(), // /\ ADDED - debug: gateway_tasks::Debug { + debug: GatewayTasksConfigDebugV12 { message_retrieval_limit: old_cfg.gateway_tasks.debug.message_retrieval_limit, maximum_open_connections: old_cfg.gateway_tasks.debug.maximum_open_connections, minimum_mix_performance: old_cfg.gateway_tasks.debug.minimum_mix_performance, max_request_timestamp_skew: old_cfg.gateway_tasks.debug.max_request_timestamp_skew, - stale_messages: StaleMessageDebug { - cleaner_run_interval: old_cfg - .gateway_tasks - .debug - .stale_messages - .cleaner_run_interval, - max_age: old_cfg.gateway_tasks.debug.stale_messages.max_age, - }, - client_bandwidth: ClientBandwidthDebug { - max_flushing_rate: old_cfg - .gateway_tasks - .debug - .client_bandwidth - .max_flushing_rate, - max_delta_flushing_amount: old_cfg - .gateway_tasks - .debug - .client_bandwidth - .max_delta_flushing_amount, - }, - zk_nym_tickets: ZkNymTicketHandlerDebug { - revocation_bandwidth_penalty: old_cfg - .gateway_tasks - .debug - .zk_nym_tickets - .revocation_bandwidth_penalty, - pending_poller: old_cfg.gateway_tasks.debug.zk_nym_tickets.pending_poller, - minimum_api_quorum: old_cfg - .gateway_tasks - .debug - .zk_nym_tickets - .minimum_api_quorum, - minimum_redemption_tickets: old_cfg - .gateway_tasks - .debug - .zk_nym_tickets - .minimum_redemption_tickets, - maximum_time_between_redemption: old_cfg - .gateway_tasks - .debug - .zk_nym_tickets - .maximum_time_between_redemption, - }, + stale_messages: old_cfg.gateway_tasks.debug.stale_messages, + client_bandwidth: old_cfg.gateway_tasks.debug.client_bandwidth, + zk_nym_tickets: old_cfg.gateway_tasks.debug.zk_nym_tickets, + // \/ ADDED (be explicit about the value rather than using ..Default::default() - upgrade_mode_min_staleness_recheck: gateway_tasks::Debug::default() + upgrade_mode_min_staleness_recheck: GatewayTasksConfigDebugV12::default() .upgrade_mode_min_staleness_recheck, // /\ ADDED }, }, - service_providers: ServiceProvidersConfig { - storage_paths: ServiceProvidersPaths { - clients_storage: old_cfg.service_providers.storage_paths.clients_storage, - stats_storage: old_cfg.service_providers.storage_paths.stats_storage, - network_requester: NetworkRequesterPaths { - private_ed25519_identity_key_file: old_cfg - .service_providers - .storage_paths - .network_requester - .private_ed25519_identity_key_file, - public_ed25519_identity_key_file: old_cfg - .service_providers - .storage_paths - .network_requester - .public_ed25519_identity_key_file, - private_x25519_diffie_hellman_key_file: old_cfg - .service_providers - .storage_paths - .network_requester - .private_x25519_diffie_hellman_key_file, - public_x25519_diffie_hellman_key_file: old_cfg - .service_providers - .storage_paths - .network_requester - .public_x25519_diffie_hellman_key_file, - ack_key_file: old_cfg - .service_providers - .storage_paths - .network_requester - .ack_key_file, - reply_surb_database: old_cfg - .service_providers - .storage_paths - .network_requester - .reply_surb_database, - gateway_registrations: old_cfg - .service_providers - .storage_paths - .network_requester - .gateway_registrations, - }, - ip_packet_router: IpPacketRouterPaths { - private_ed25519_identity_key_file: old_cfg - .service_providers - .storage_paths - .ip_packet_router - .private_ed25519_identity_key_file, - public_ed25519_identity_key_file: old_cfg - .service_providers - .storage_paths - .ip_packet_router - .public_ed25519_identity_key_file, - private_x25519_diffie_hellman_key_file: old_cfg - .service_providers - .storage_paths - .ip_packet_router - .private_x25519_diffie_hellman_key_file, - public_x25519_diffie_hellman_key_file: old_cfg - .service_providers - .storage_paths - .ip_packet_router - .public_x25519_diffie_hellman_key_file, - ack_key_file: old_cfg - .service_providers - .storage_paths - .ip_packet_router - .ack_key_file, - reply_surb_database: old_cfg - .service_providers - .storage_paths - .ip_packet_router - .reply_surb_database, - gateway_registrations: old_cfg - .service_providers - .storage_paths - .ip_packet_router - .gateway_registrations, - }, - authenticator: AuthenticatorPaths { - private_ed25519_identity_key_file: old_cfg - .service_providers - .storage_paths - .authenticator - .private_ed25519_identity_key_file, - public_ed25519_identity_key_file: old_cfg - .service_providers - .storage_paths - .authenticator - .public_ed25519_identity_key_file, - private_x25519_diffie_hellman_key_file: old_cfg - .service_providers - .storage_paths - .authenticator - .private_x25519_diffie_hellman_key_file, - public_x25519_diffie_hellman_key_file: old_cfg - .service_providers - .storage_paths - .authenticator - .public_x25519_diffie_hellman_key_file, - ack_key_file: old_cfg - .service_providers - .storage_paths - .authenticator - .ack_key_file, - reply_surb_database: old_cfg - .service_providers - .storage_paths - .authenticator - .reply_surb_database, - gateway_registrations: old_cfg - .service_providers - .storage_paths - .authenticator - .gateway_registrations, - }, - }, - open_proxy: old_cfg.service_providers.open_proxy, - upstream_exit_policy_url: old_cfg.service_providers.upstream_exit_policy_url, - network_requester: NetworkRequester { - debug: NetworkRequesterDebug { - enabled: old_cfg.service_providers.network_requester.debug.enabled, - disable_poisson_rate: old_cfg - .service_providers - .network_requester - .debug - .disable_poisson_rate, - client_debug: old_cfg - .service_providers - .network_requester - .debug - .client_debug, - }, - }, - ip_packet_router: IpPacketRouter { - debug: IpPacketRouterDebug { - enabled: old_cfg.service_providers.ip_packet_router.debug.enabled, - disable_poisson_rate: old_cfg - .service_providers - .ip_packet_router - .debug - .disable_poisson_rate, - client_debug: old_cfg - .service_providers - .ip_packet_router - .debug - .client_debug, - }, - }, - authenticator: Authenticator { - debug: AuthenticatorDebug { - enabled: old_cfg.service_providers.authenticator.debug.enabled, - disable_poisson_rate: old_cfg - .service_providers - .authenticator - .debug - .disable_poisson_rate, - client_debug: old_cfg.service_providers.authenticator.debug.client_debug, - }, - }, - debug: service_providers::Debug { - message_retrieval_limit: old_cfg.service_providers.debug.message_retrieval_limit, - }, - }, - metrics: Default::default(), - logging: LoggingSettings {}, - debug: Default::default(), + service_providers: old_cfg.service_providers, + metrics: old_cfg.metrics, + logging: old_cfg.logging, + // \/ FIXED + debug: DebugV12::default(), + // /\ FIXED }; Ok(cfg) } diff --git a/nym-node/src/config/old_configs/old_config_v12.rs b/nym-node/src/config/old_configs/old_config_v12.rs new file mode 100644 index 0000000000..268da56802 --- /dev/null +++ b/nym-node/src/config/old_configs/old_config_v12.rs @@ -0,0 +1,1001 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::authenticator::{Authenticator, AuthenticatorDebug}; +use crate::config::gateway_tasks::{ + ClientBandwidthDebug, StaleMessageDebug, UpgradeModeWatcher, UpgradeModeWatcherDebug, + ZkNymTicketHandlerDebug, +}; +use crate::config::helpers::log_error_and_return; +use crate::config::persistence::{ + AuthenticatorPaths, DEFAULT_MCELIECE_PRIVATE_KEY_FILENAME, + DEFAULT_MCELIECE_PUBLIC_KEY_FILENAME, DEFAULT_MLKEM768_PRIVATE_KEY_FILENAME, + DEFAULT_MLKEM768_PUBLIC_KEY_FILENAME, DEFAULT_X25519_PRIVATE_LP_KEY_FILENAME, + DEFAULT_X25519_PUBLIC_LP_KEY_FILENAME, GatewayTasksPaths, IpPacketRouterPaths, KeysPaths, + NetworkRequesterPaths, NymNodePaths, ReplayProtectionPaths, ServiceProvidersPaths, + WireguardPaths, +}; +use crate::config::service_providers::{ + IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug, +}; +use crate::config::{ + Config, Debug, GatewayTasksConfig, Host, Http, KeyRotation, KeyRotationDebug, MetricsConfig, + Mixnet, MixnetDebug, NodeModes, ReplayProtection, ReplayProtectionDebug, + ServiceProvidersConfig, Verloc, VerlocDebug, Wireguard, gateway_tasks, metrics, + service_providers, +}; +use crate::error::NymNodeError; +use crate::node::helpers::{ + store_mceliece_keypair, store_mlkem768_keypair, store_x25519_lp_keypair, +}; +use nym_bin_common::logging::LoggingSettings; +use nym_config::defaults::{mainnet, var_names}; +use nym_config::read_config_from_toml_file; +use nym_config::serde_helpers::de_maybe_port; +use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey; +use nym_kkt::key_utils::{ + generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519, +}; +use rand09::SeedableRng; +use serde::{Deserialize, Serialize}; +use std::env; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use tracing::{debug, info, instrument}; +use url::Url; + +use crate::config::lp::{LpConfig, LpDebug}; +pub use unchanged_v12_types::*; + +// (while some of those are technically unused, they might be needed in future migrations, +// thus allow them to exist) +#[allow(dead_code)] +pub mod unchanged_v12_types { + use crate::config::old_configs::old_config_v11::{ + AuthenticatorDebugV11, AuthenticatorPathsV11, AuthenticatorV11, ClientBandwidthDebugV11, + GatewayTasksPathsV11, HostV11, HttpV11, IpPacketRouterDebugV11, IpPacketRouterPathsV11, + IpPacketRouterV11, KeyRotationDebugV11, KeyRotationV11, KeysPathsV11, LoggingSettingsV11, + MetricsConfigV11, MetricsDebugV11, MixnetDebugV11, MixnetV11, NetworkRequesterDebugV11, + NetworkRequesterPathsV11, NetworkRequesterV11, NodeModeV11, NodeModesV11, NymNodePathsV11, + ReplayProtectionDebugV11, ReplayProtectionPathsV11, ReplayProtectionV11, + ServiceProvidersConfigDebugV11, ServiceProvidersConfigV11, ServiceProvidersPathsV11, + StaleMessageDebugV11, VerlocDebugV11, VerlocV11, WireguardPathsV11, + ZkNymTicketHandlerDebugV11, + }; + + pub type WireguardPathsV12 = WireguardPathsV11; + pub type NodeModeV12 = NodeModeV11; + pub type NodeModesV12 = NodeModesV11; + pub type HostV12 = HostV11; + pub type KeyRotationDebugV12 = KeyRotationDebugV11; + pub type KeyRotationV12 = KeyRotationV11; + pub type MixnetDebugV12 = MixnetDebugV11; + pub type MixnetV12 = MixnetV11; + pub type ReplayProtectionV12 = ReplayProtectionV11; + pub type ReplayProtectionPathsV12 = ReplayProtectionPathsV11; + pub type ReplayProtectionDebugV12 = ReplayProtectionDebugV11; + pub type KeysPathsV12 = KeysPathsV11; + pub type NymNodePathsV12 = NymNodePathsV11; + pub type HttpV12 = HttpV11; + pub type VerlocDebugV12 = VerlocDebugV11; + pub type VerlocV12 = VerlocV11; + pub type ZkNymTicketHandlerDebugV12 = ZkNymTicketHandlerDebugV11; + pub type NetworkRequesterPathsV12 = NetworkRequesterPathsV11; + pub type IpPacketRouterPathsV12 = IpPacketRouterPathsV11; + pub type AuthenticatorPathsV12 = AuthenticatorPathsV11; + pub type AuthenticatorV12 = AuthenticatorV11; + pub type AuthenticatorDebugV12 = AuthenticatorDebugV11; + pub type IpPacketRouterDebugV12 = IpPacketRouterDebugV11; + pub type IpPacketRouterV12 = IpPacketRouterV11; + pub type NetworkRequesterDebugV12 = NetworkRequesterDebugV11; + pub type NetworkRequesterV12 = NetworkRequesterV11; + pub type GatewayTasksPathsV12 = GatewayTasksPathsV11; + pub type StaleMessageDebugV12 = StaleMessageDebugV11; + pub type ClientBandwidthDebugV12 = ClientBandwidthDebugV11; + pub type ServiceProvidersPathsV12 = ServiceProvidersPathsV11; + pub type ServiceProvidersConfigDebugV12 = ServiceProvidersConfigDebugV11; + pub type ServiceProvidersConfigV12 = ServiceProvidersConfigV11; + pub type MetricsConfigV12 = MetricsConfigV11; + pub type MetricsDebugV12 = MetricsDebugV11; + pub type LoggingSettingsV12 = LoggingSettingsV11; +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WireguardV12 { + /// Specifies whether the wireguard service is enabled on this node. + pub enabled: bool, + + /// Socket address this node will use for binding its wireguard interface. + /// default: `[::]:51822` + pub bind_address: SocketAddr, + + /// Private IPv4 address of the wireguard gateway. + /// default: `10.1.0.1` + pub private_ipv4: Ipv4Addr, + + /// Private IPv6 address of the wireguard gateway. + /// default: `fc01::1` + pub private_ipv6: Ipv6Addr, + + /// Tunnel port announced to external clients wishing to connect to the wireguard interface. + /// Useful in the instances where the node is behind a proxy. + pub announced_tunnel_port: u16, + + /// Metadata port announced to external clients wishing to connect to the metadata endpoint. + /// Useful in the instances where the node is behind a proxy. + pub announced_metadata_port: u16, + + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4. + /// The maximum value for IPv4 is 32 + pub private_network_prefix_v4: u8, + + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv6. + /// The maximum value for IPv6 is 128 + pub private_network_prefix_v6: u8, + + /// Use userspace implementation of WireGuard (wireguard-go) instead of kernel module. + /// Useful in containerized environments without kernel WireGuard support. + /// default: `false` + #[serde(default)] + pub use_userspace: bool, + + /// Paths for wireguard keys, client registries, etc. + pub storage_paths: WireguardPathsV12, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct GatewayTasksConfigDebugV12 { + /// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage. + pub message_retrieval_limit: i64, + + /// The maximum number of client connections the gateway will keep open at once. + pub maximum_open_connections: usize, + + /// Specifies the minimum performance of mixnodes in the network that are to be used in internal topologies + /// of the services providers + pub minimum_mix_performance: u8, + + /// Defines the timestamp skew of a signed authentication request before it's deemed too excessive to process. + #[serde(alias = "maximum_auth_request_age")] + pub max_request_timestamp_skew: Duration, + + /// The minimum duration since the last explicit check for the upgrade mode to allow creation of new requests. + #[serde(with = "humantime_serde")] + pub upgrade_mode_min_staleness_recheck: Duration, + + pub stale_messages: StaleMessageDebugV12, + + pub client_bandwidth: ClientBandwidthDebugV12, + + pub zk_nym_tickets: ZkNymTicketHandlerDebugV12, +} + +impl GatewayTasksConfigDebugV12 { + pub const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; + pub const DEFAULT_MINIMUM_MIX_PERFORMANCE: u8 = 50; + pub const DEFAULT_MAXIMUM_AUTH_REQUEST_TIMESTAMP_SKEW: Duration = Duration::from_secs(120); + pub const DEFAULT_MAXIMUM_OPEN_CONNECTIONS: usize = 8192; + pub const DEFAULT_UPGRADE_MODE_MIN_STALENESS_RECHECK: Duration = Duration::from_secs(30); +} + +impl Default for GatewayTasksConfigDebugV12 { + fn default() -> Self { + GatewayTasksConfigDebugV12 { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + maximum_open_connections: Self::DEFAULT_MAXIMUM_OPEN_CONNECTIONS, + max_request_timestamp_skew: Self::DEFAULT_MAXIMUM_AUTH_REQUEST_TIMESTAMP_SKEW, + minimum_mix_performance: Self::DEFAULT_MINIMUM_MIX_PERFORMANCE, + stale_messages: Default::default(), + client_bandwidth: Default::default(), + zk_nym_tickets: Default::default(), + upgrade_mode_min_staleness_recheck: Self::DEFAULT_UPGRADE_MODE_MIN_STALENESS_RECHECK, + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct UpgradeModeWatcherDebugV12 { + /// Default polling interval + #[serde(with = "humantime_serde")] + pub regular_polling_interval: Duration, + + /// Expedited polling interval for once upgrade mode is detected + #[serde(with = "humantime_serde")] + pub expedited_poll_interval: Duration, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpgradeModeWatcherV12 { + /// Specifies whether this gateway watches for upgrade mode changes + /// via the published attestation file. + pub enabled: bool, + + /// Endpoint to query to retrieve current upgrade mode attestation. + pub attestation_url: Url, + + /// Expected public key of the attester providing the upgrade mode attestation + /// on the specified endpoint + #[serde(with = "bs58_ed25519_pubkey")] + pub attester_public_key: ed25519::PublicKey, + + #[serde(default)] + pub debug: UpgradeModeWatcherDebugV12, +} + +impl UpgradeModeWatcherV12 { + pub fn new_mainnet() -> UpgradeModeWatcherV12 { + info!("using mainnet configuration for the upgrade mode:"); + info!("\t- url: {}", mainnet::UPGRADE_MODE_ATTESTATION_URL); + info!( + "\t- attester public key: {}", + mainnet::UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY + ); + + // SAFETY: + // our hardcoded values should always be valid + #[allow(clippy::expect_used)] + let attestation_url = mainnet::UPGRADE_MODE_ATTESTATION_URL + .parse() + .expect("invalid default upgrade mode attestation URL"); + + #[allow(clippy::expect_used)] + let attester_public_key = mainnet::UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY + .parse() + .expect("invalid default upgrade mode attester public key"); + + UpgradeModeWatcherV12 { + enabled: true, + attestation_url, + attester_public_key, + debug: UpgradeModeWatcherDebugV12::default(), + } + } + + pub fn new() -> Result { + // if env is configured, extract relevant values from there, otherwise fallback to mainnet + if env::var(var_names::CONFIGURED).is_err() { + return Ok(Self::new_mainnet()); + } + + // if env is configured, the relevant values should be set + let Ok(env_attestation_url) = env::var(var_names::UPGRADE_MODE_ATTESTATION_URL) else { + return log_error_and_return(format!( + "'{}' is not set whilst the env is set to be configured", + var_names::UPGRADE_MODE_ATTESTATION_URL + )); + }; + + let Ok(env_attester_pubkey) = + env::var(var_names::UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY) + else { + return log_error_and_return(format!( + "'{}' is not set whilst the env is set to be configured", + var_names::UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY + )); + }; + + let attestation_url = match env_attestation_url.parse() { + Ok(url) => url, + Err(err) => { + return log_error_and_return(format!( + "provided attestation url {env_attestation_url} is invalid: {err}!" + )); + } + }; + + let attester_public_key = match env_attester_pubkey.parse() { + Ok(public_key) => public_key, + Err(err) => { + return log_error_and_return(format!( + "provided attester public key {env_attester_pubkey} is invalid: {err}!" + )); + } + }; + + Ok(UpgradeModeWatcherV12 { + enabled: true, + attestation_url, + attester_public_key, + debug: UpgradeModeWatcherDebugV12::default(), + }) + } +} + +impl UpgradeModeWatcherDebugV12 { + const DEFAULT_REGULAR_POLLING_INTERVAL: Duration = Duration::from_secs(15 * 60); + const DEFAULT_EXPEDITED_POLL_INTERVAL: Duration = Duration::from_secs(2 * 60); +} + +impl Default for UpgradeModeWatcherDebugV12 { + fn default() -> Self { + UpgradeModeWatcherDebugV12 { + regular_polling_interval: Self::DEFAULT_REGULAR_POLLING_INTERVAL, + expedited_poll_interval: Self::DEFAULT_EXPEDITED_POLL_INTERVAL, + } + } +} + +/// Configuration for LP listener +#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct LpConfigV12 { + pub control_bind_address: SocketAddr, + + pub data_bind_address: SocketAddr, + + #[serde(deserialize_with = "de_maybe_port")] + pub announce_control_port: Option, + + #[serde(deserialize_with = "de_maybe_port")] + pub announce_data_port: Option, + + #[serde(default)] + pub debug: LpDebugV12, +} + +#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct LpDebugV12 { + pub max_connections: usize, + #[serde(with = "humantime_serde")] + pub timestamp_tolerance: Duration, + pub use_mock_ecash: bool, + #[serde(with = "humantime_serde")] + pub handshake_ttl: Duration, + #[serde(with = "humantime_serde")] + pub session_ttl: Duration, + #[serde(with = "humantime_serde")] + pub state_cleanup_interval: Duration, + pub max_concurrent_forwards: usize, +} + +impl LpConfigV12 { + pub const DEFAULT_CONTROL_PORT: u16 = 41264; + pub const DEFAULT_DATA_PORT: u16 = 51264; +} + +impl Default for LpConfigV12 { + fn default() -> Self { + LpConfigV12 { + control_bind_address: SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + Self::DEFAULT_CONTROL_PORT, + ), + data_bind_address: SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + Self::DEFAULT_DATA_PORT, + ), + announce_control_port: None, + announce_data_port: None, + debug: Default::default(), + } + } +} + +impl LpDebugV12 { + pub const DEFAULT_MAX_CONNECTIONS: usize = 10000; + pub const DEFAULT_TIMESTAMP_TOLERANCE: Duration = Duration::from_secs(30); + pub const DEFAULT_HANDSHAKE_TTL: Duration = Duration::from_secs(90); + pub const DEFAULT_SESSION_TTL: Duration = Duration::from_secs(86400); + pub const DEFAULT_STATE_CLEANUP_INTERVAL: Duration = Duration::from_secs(300); + pub const DEFAULT_MAX_CONCURRENT_FORWARDS: usize = 1000; +} + +impl Default for LpDebugV12 { + fn default() -> Self { + LpDebugV12 { + max_connections: Self::DEFAULT_MAX_CONNECTIONS, + timestamp_tolerance: Self::DEFAULT_TIMESTAMP_TOLERANCE, + use_mock_ecash: false, + handshake_ttl: Self::DEFAULT_HANDSHAKE_TTL, + session_ttl: Self::DEFAULT_SESSION_TTL, + state_cleanup_interval: Self::DEFAULT_STATE_CLEANUP_INTERVAL, + max_concurrent_forwards: Self::DEFAULT_MAX_CONCURRENT_FORWARDS, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct GatewayTasksConfigV12 { + pub storage_paths: GatewayTasksPathsV12, + + /// Indicates whether this gateway is accepting only zk-nym credentials for accessing the mixnet + /// or if it also accepts non-paying clients + pub enforce_zk_nyms: bool, + + /// Socket address this node will use for binding its client websocket API. + /// default: `[::]:9000` + pub ws_bind_address: SocketAddr, + + /// Custom announced port for listening for websocket client traffic. + /// If unspecified, the value from the `bind_address` will be used instead + /// default: None + #[serde(deserialize_with = "de_maybe_port")] + pub announce_ws_port: Option, + + /// If applicable, announced port for listening for secure websocket client traffic. + /// (default: None) + #[serde(deserialize_with = "de_maybe_port")] + pub announce_wss_port: Option, + + pub upgrade_mode: UpgradeModeWatcherV12, + + #[serde(default)] + pub lp: LpConfigV12, + + #[serde(default)] + pub debug: GatewayTasksConfigDebugV12, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct DebugV12 { + /// Specifies the time to live of the internal topology provider cache. + #[serde(with = "humantime_serde")] + pub topology_cache_ttl: Duration, + + /// Specifies the time between attempting to resolve any pending unknown nodes in the routing filter + #[serde(with = "humantime_serde")] + pub routing_nodes_check_interval: Duration, + + /// Specifies whether this node runs in testnet mode thus allowing it to route packets on local interfaces + pub testnet: bool, +} + +impl DebugV12 { + pub const DEFAULT_TOPOLOGY_CACHE_TTL: Duration = Duration::from_secs(10 * 60); + pub const DEFAULT_ROUTING_NODES_CHECK_INTERVAL: Duration = Duration::from_secs(5 * 60); +} + +impl Default for DebugV12 { + fn default() -> Self { + DebugV12 { + topology_cache_ttl: Self::DEFAULT_TOPOLOGY_CACHE_TTL, + routing_nodes_check_interval: Self::DEFAULT_ROUTING_NODES_CHECK_INTERVAL, + testnet: false, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV12 { + // additional metadata holding on-disk location of this config file + #[serde(skip)] + pub(crate) save_path: Option, + + /// Human-readable ID of this particular node. + pub id: String, + + /// Current modes of this nym-node. + pub modes: NodeModesV12, + + pub host: HostV12, + + pub mixnet: MixnetV12, + + /// Storage paths to persistent nym-node data, such as its long term keys. + pub storage_paths: NymNodePathsV12, + + #[serde(default)] + pub http: HttpV12, + + #[serde(default)] + pub verloc: VerlocV12, + + pub wireguard: WireguardV12, + + #[serde(alias = "entry_gateway")] + pub gateway_tasks: GatewayTasksConfigV12, + + #[serde(alias = "exit_gateway")] + pub service_providers: ServiceProvidersConfigV12, + + #[serde(default)] + pub metrics: MetricsConfigV12, + + #[serde(default)] + pub logging: LoggingSettingsV12, + + #[serde(default)] + pub debug: DebugV12, +} + +impl ConfigV12 { + // simple wrapper that reads config file and assigns path location + fn read_from_path>(path: P) -> Result { + let path = path.as_ref(); + let mut loaded: ConfigV12 = + read_config_from_toml_file(path).map_err(|source| NymNodeError::ConfigLoadFailure { + path: path.to_path_buf(), + source, + })?; + loaded.save_path = Some(path.to_path_buf()); + debug!("loaded config file from {}", path.display()); + Ok(loaded) + } +} + +#[instrument(skip_all)] +pub async fn try_upgrade_config_v12>( + path: P, + prev_config: Option, +) -> Result { + debug!("attempting to load v12 config..."); + + let old_cfg = if let Some(prev_config) = prev_config { + prev_config + } else { + ConfigV12::read_from_path(&path)? + }; + + info!("migrating the old config (v12)..."); + let keys_dir = old_cfg + .storage_paths + .keys + .public_ed25519_identity_key_file + .parent() + .ok_or(NymNodeError::DataDirDerivationFailure)? + .to_path_buf(); + + let updated_keys = KeysPaths { + private_ed25519_identity_key_file: old_cfg + .storage_paths + .keys + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .storage_paths + .keys + .public_ed25519_identity_key_file, + primary_x25519_sphinx_key_file: old_cfg.storage_paths.keys.primary_x25519_sphinx_key_file, + secondary_x25519_sphinx_key_file: old_cfg + .storage_paths + .keys + .secondary_x25519_sphinx_key_file, + private_x25519_noise_key_file: old_cfg.storage_paths.keys.private_x25519_noise_key_file, + public_x25519_noise_key_file: old_cfg.storage_paths.keys.public_x25519_noise_key_file, + private_x25519_lp_key_file: keys_dir.join(DEFAULT_X25519_PRIVATE_LP_KEY_FILENAME), + public_x25519_lp_key_file: keys_dir.join(DEFAULT_X25519_PUBLIC_LP_KEY_FILENAME), + private_mlkem768_lp_key_file: keys_dir.join(DEFAULT_MLKEM768_PRIVATE_KEY_FILENAME), + public_mlkem768_lp_key_file: keys_dir.join(DEFAULT_MLKEM768_PUBLIC_KEY_FILENAME), + private_mceliece_lp_key_file: keys_dir.join(DEFAULT_MCELIECE_PRIVATE_KEY_FILENAME), + public_mceliece_lp_key_file: keys_dir.join(DEFAULT_MCELIECE_PUBLIC_KEY_FILENAME), + }; + + let mut rng = rand09::rngs::StdRng::from_os_rng(); + + // generate new keys for LP + info!("generating new LP x25519 DH keypair"); + let x25519 = generate_lp_keypair_x25519(&mut rng); + let paths = updated_keys.x25519_lp_key_paths(); + store_x25519_lp_keypair(&x25519, &paths)?; + + info!("generating new mlkem768 keypair"); + let mlkem = generate_keypair_mlkem(&mut rng); + let paths = updated_keys.mlkem768_key_paths(); + store_mlkem768_keypair(&mlkem, &paths)?; + + info!("generating new mceliece keypair (this might take a while)"); + let mceliece = generate_keypair_mceliece(&mut rng); + let paths = updated_keys.mceliece_key_paths(); + store_mceliece_keypair(&mceliece, &paths)?; + + let cfg = Config { + save_path: old_cfg.save_path, + id: old_cfg.id, + modes: NodeModes { + mixnode: old_cfg.modes.mixnode, + entry: old_cfg.modes.entry, + exit: old_cfg.modes.exit, + }, + host: Host { + public_ips: old_cfg.host.public_ips, + hostname: old_cfg.host.hostname, + location: old_cfg.host.location, + }, + mixnet: Mixnet { + bind_address: old_cfg.mixnet.bind_address, + announce_port: old_cfg.mixnet.announce_port, + nym_api_urls: old_cfg.mixnet.nym_api_urls, + nyxd_urls: old_cfg.mixnet.nyxd_urls, + replay_protection: ReplayProtection { + storage_paths: ReplayProtectionPaths { + current_bloomfilters_directory: old_cfg + .mixnet + .replay_protection + .storage_paths + .current_bloomfilters_directory, + }, + debug: ReplayProtectionDebug { + unsafe_disabled: old_cfg.mixnet.replay_protection.debug.unsafe_disabled, + maximum_replay_detection_deferral: old_cfg + .mixnet + .replay_protection + .debug + .maximum_replay_detection_deferral, + maximum_replay_detection_pending_packets: old_cfg + .mixnet + .replay_protection + .debug + .maximum_replay_detection_pending_packets, + false_positive_rate: old_cfg.mixnet.replay_protection.debug.false_positive_rate, + initial_expected_packets_per_second: old_cfg + .mixnet + .replay_protection + .debug + .initial_expected_packets_per_second, + bloomfilter_minimum_packets_per_second_size: old_cfg + .mixnet + .replay_protection + .debug + .bloomfilter_minimum_packets_per_second_size, + bloomfilter_size_multiplier: old_cfg + .mixnet + .replay_protection + .debug + .bloomfilter_size_multiplier, + bloomfilter_disk_flushing_rate: old_cfg + .mixnet + .replay_protection + .debug + .bloomfilter_disk_flushing_rate, + }, + }, + key_rotation: KeyRotation { + debug: KeyRotationDebug { + rotation_state_poling_interval: old_cfg + .mixnet + .key_rotation + .debug + .rotation_state_poling_interval, + }, + }, + debug: MixnetDebug { + maximum_forward_packet_delay: old_cfg.mixnet.debug.maximum_forward_packet_delay, + packet_forwarding_initial_backoff: old_cfg + .mixnet + .debug + .packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: old_cfg + .mixnet + .debug + .packet_forwarding_maximum_backoff, + initial_connection_timeout: old_cfg.mixnet.debug.initial_connection_timeout, + maximum_connection_buffer_size: old_cfg.mixnet.debug.maximum_connection_buffer_size, + unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise, + use_legacy_packet_encoding: old_cfg.mixnet.debug.use_legacy_packet_encoding, + }, + }, + storage_paths: NymNodePaths { + keys: updated_keys, + description: old_cfg.storage_paths.description, + }, + http: Http { + bind_address: old_cfg.http.bind_address, + landing_page_assets_path: old_cfg.http.landing_page_assets_path, + access_token: old_cfg.http.access_token, + expose_system_info: old_cfg.http.expose_system_info, + expose_system_hardware: old_cfg.http.expose_system_hardware, + expose_crypto_hardware: old_cfg.http.expose_crypto_hardware, + node_load_cache_ttl: old_cfg.http.node_load_cache_ttl, + }, + verloc: Verloc { + bind_address: old_cfg.verloc.bind_address, + announce_port: old_cfg.verloc.announce_port, + debug: VerlocDebug { + packets_per_node: old_cfg.verloc.debug.packets_per_node, + connection_timeout: old_cfg.verloc.debug.connection_timeout, + packet_timeout: old_cfg.verloc.debug.packet_timeout, + delay_between_packets: old_cfg.verloc.debug.delay_between_packets, + tested_nodes_batch_size: old_cfg.verloc.debug.tested_nodes_batch_size, + testing_interval: old_cfg.verloc.debug.testing_interval, + retry_timeout: old_cfg.verloc.debug.retry_timeout, + }, + }, + wireguard: Wireguard { + enabled: old_cfg.wireguard.enabled, + bind_address: old_cfg.wireguard.bind_address, + private_ipv4: old_cfg.wireguard.private_ipv4, + private_ipv6: old_cfg.wireguard.private_ipv6, + announced_tunnel_port: old_cfg.wireguard.announced_tunnel_port, + announced_metadata_port: old_cfg.wireguard.announced_metadata_port, + private_network_prefix_v4: old_cfg.wireguard.private_network_prefix_v4, + private_network_prefix_v6: old_cfg.wireguard.private_network_prefix_v6, + use_userspace: old_cfg.wireguard.use_userspace, + storage_paths: WireguardPaths { + private_diffie_hellman_key_file: old_cfg + .wireguard + .storage_paths + .private_diffie_hellman_key_file, + public_diffie_hellman_key_file: old_cfg + .wireguard + .storage_paths + .public_diffie_hellman_key_file, + }, + }, + lp: LpConfig { + control_bind_address: old_cfg.gateway_tasks.lp.control_bind_address, + data_bind_address: old_cfg.gateway_tasks.lp.data_bind_address, + announce_control_port: old_cfg.gateway_tasks.lp.announce_control_port, + announce_data_port: old_cfg.gateway_tasks.lp.announce_data_port, + debug: LpDebug { + max_connections: old_cfg.gateway_tasks.lp.debug.max_connections, + use_mock_ecash: old_cfg.gateway_tasks.lp.debug.use_mock_ecash, + handshake_ttl: old_cfg.gateway_tasks.lp.debug.handshake_ttl, + session_ttl: old_cfg.gateway_tasks.lp.debug.session_ttl, + state_cleanup_interval: old_cfg.gateway_tasks.lp.debug.state_cleanup_interval, + max_concurrent_forwards: old_cfg.gateway_tasks.lp.debug.max_concurrent_forwards, + }, + }, + gateway_tasks: GatewayTasksConfig { + storage_paths: GatewayTasksPaths { + clients_storage: old_cfg.gateway_tasks.storage_paths.clients_storage, + stats_storage: old_cfg.gateway_tasks.storage_paths.stats_storage, + cosmos_mnemonic: old_cfg.gateway_tasks.storage_paths.cosmos_mnemonic, + bridge_client_params: old_cfg.gateway_tasks.storage_paths.bridge_client_params, + }, + enforce_zk_nyms: old_cfg.gateway_tasks.enforce_zk_nyms, + ws_bind_address: old_cfg.gateway_tasks.ws_bind_address, + announce_ws_port: old_cfg.gateway_tasks.announce_ws_port, + announce_wss_port: old_cfg.gateway_tasks.announce_wss_port, + upgrade_mode: UpgradeModeWatcher { + enabled: old_cfg.gateway_tasks.upgrade_mode.enabled, + attestation_url: old_cfg.gateway_tasks.upgrade_mode.attestation_url, + attester_public_key: old_cfg.gateway_tasks.upgrade_mode.attester_public_key, + debug: UpgradeModeWatcherDebug { + regular_polling_interval: old_cfg + .gateway_tasks + .upgrade_mode + .debug + .regular_polling_interval, + expedited_poll_interval: old_cfg + .gateway_tasks + .upgrade_mode + .debug + .expedited_poll_interval, + }, + }, + debug: gateway_tasks::Debug { + message_retrieval_limit: old_cfg.gateway_tasks.debug.message_retrieval_limit, + maximum_open_connections: old_cfg.gateway_tasks.debug.maximum_open_connections, + minimum_mix_performance: old_cfg.gateway_tasks.debug.minimum_mix_performance, + max_request_timestamp_skew: old_cfg.gateway_tasks.debug.max_request_timestamp_skew, + stale_messages: StaleMessageDebug { + cleaner_run_interval: old_cfg + .gateway_tasks + .debug + .stale_messages + .cleaner_run_interval, + max_age: old_cfg.gateway_tasks.debug.stale_messages.max_age, + }, + client_bandwidth: ClientBandwidthDebug { + max_flushing_rate: old_cfg + .gateway_tasks + .debug + .client_bandwidth + .max_flushing_rate, + max_delta_flushing_amount: old_cfg + .gateway_tasks + .debug + .client_bandwidth + .max_delta_flushing_amount, + }, + zk_nym_tickets: ZkNymTicketHandlerDebug { + revocation_bandwidth_penalty: old_cfg + .gateway_tasks + .debug + .zk_nym_tickets + .revocation_bandwidth_penalty, + pending_poller: old_cfg.gateway_tasks.debug.zk_nym_tickets.pending_poller, + minimum_api_quorum: old_cfg + .gateway_tasks + .debug + .zk_nym_tickets + .minimum_api_quorum, + minimum_redemption_tickets: old_cfg + .gateway_tasks + .debug + .zk_nym_tickets + .minimum_redemption_tickets, + maximum_time_between_redemption: old_cfg + .gateway_tasks + .debug + .zk_nym_tickets + .maximum_time_between_redemption, + }, + upgrade_mode_min_staleness_recheck: old_cfg + .gateway_tasks + .debug + .upgrade_mode_min_staleness_recheck, + }, + }, + service_providers: ServiceProvidersConfig { + storage_paths: ServiceProvidersPaths { + clients_storage: old_cfg.service_providers.storage_paths.clients_storage, + stats_storage: old_cfg.service_providers.storage_paths.stats_storage, + network_requester: NetworkRequesterPaths { + private_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .network_requester + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .network_requester + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .network_requester + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .network_requester + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .service_providers + .storage_paths + .network_requester + .ack_key_file, + reply_surb_database: old_cfg + .service_providers + .storage_paths + .network_requester + .reply_surb_database, + gateway_registrations: old_cfg + .service_providers + .storage_paths + .network_requester + .gateway_registrations, + }, + ip_packet_router: IpPacketRouterPaths { + private_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .ack_key_file, + reply_surb_database: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .reply_surb_database, + gateway_registrations: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .gateway_registrations, + }, + authenticator: AuthenticatorPaths { + private_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .authenticator + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .authenticator + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .authenticator + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .authenticator + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .service_providers + .storage_paths + .authenticator + .ack_key_file, + reply_surb_database: old_cfg + .service_providers + .storage_paths + .authenticator + .reply_surb_database, + gateway_registrations: old_cfg + .service_providers + .storage_paths + .authenticator + .gateway_registrations, + }, + }, + open_proxy: old_cfg.service_providers.open_proxy, + upstream_exit_policy_url: old_cfg.service_providers.upstream_exit_policy_url, + network_requester: NetworkRequester { + debug: NetworkRequesterDebug { + enabled: old_cfg.service_providers.network_requester.debug.enabled, + disable_poisson_rate: old_cfg + .service_providers + .network_requester + .debug + .disable_poisson_rate, + client_debug: old_cfg + .service_providers + .network_requester + .debug + .client_debug, + }, + }, + ip_packet_router: IpPacketRouter { + debug: IpPacketRouterDebug { + enabled: old_cfg.service_providers.ip_packet_router.debug.enabled, + disable_poisson_rate: old_cfg + .service_providers + .ip_packet_router + .debug + .disable_poisson_rate, + client_debug: old_cfg + .service_providers + .ip_packet_router + .debug + .client_debug, + }, + }, + authenticator: Authenticator { + debug: AuthenticatorDebug { + enabled: old_cfg.service_providers.authenticator.debug.enabled, + disable_poisson_rate: old_cfg + .service_providers + .authenticator + .debug + .disable_poisson_rate, + client_debug: old_cfg.service_providers.authenticator.debug.client_debug, + }, + }, + debug: service_providers::Debug { + message_retrieval_limit: old_cfg.service_providers.debug.message_retrieval_limit, + }, + }, + metrics: MetricsConfig { + debug: metrics::Debug { + log_stats_to_console: false, + aggregator_update_rate: Default::default(), + stale_mixnet_metrics_cleaner_rate: Default::default(), + global_prometheus_counters_update_rate: Default::default(), + pending_egress_packets_update_rate: Default::default(), + clients_sessions_update_rate: Default::default(), + console_logging_update_interval: Default::default(), + legacy_mixing_metrics_update_rate: Default::default(), + }, + }, + logging: LoggingSettings {}, + debug: Debug { + topology_cache_ttl: old_cfg.debug.topology_cache_ttl, + routing_nodes_check_interval: old_cfg.debug.routing_nodes_check_interval, + testnet: old_cfg.debug.testnet, + }, + }; + Ok(cfg) +} diff --git a/nym-node/src/config/persistence.rs b/nym-node/src/config/persistence.rs index 332fbf2c1d..4d0a5c645c 100644 --- a/nym-node/src/config/persistence.rs +++ b/nym-node/src/config/persistence.rs @@ -19,6 +19,14 @@ pub const DEFAULT_X25519_PRIVATE_NOISE_KEY_FILENAME: &str = "x25519_noise"; pub const DEFAULT_X25519_PUBLIC_NOISE_KEY_FILENAME: &str = "x25519_noise.pub"; pub const DEFAULT_NYMNODE_DESCRIPTION_FILENAME: &str = "description.toml"; +// Global/LP: +pub const DEFAULT_X25519_PRIVATE_LP_KEY_FILENAME: &str = "x25519_lp"; +pub const DEFAULT_X25519_PUBLIC_LP_KEY_FILENAME: &str = "x25519_lp.pub"; +pub const DEFAULT_MLKEM768_PRIVATE_KEY_FILENAME: &str = "mlkem768"; +pub const DEFAULT_MLKEM768_PUBLIC_KEY_FILENAME: &str = "mlkem768.pub"; +pub const DEFAULT_MCELIECE_PRIVATE_KEY_FILENAME: &str = "mceliece"; +pub const DEFAULT_MCELIECE_PUBLIC_KEY_FILENAME: &str = "mceliece.pub"; + // Mixnode: // Entry Gateway: @@ -99,6 +107,26 @@ pub struct KeysPaths { /// Path to file containing x25519 noise public key. pub public_x25519_noise_key_file: PathBuf, + + // >> LP KEYS START: + /// Path to file containing x25519 lp private key. + pub private_x25519_lp_key_file: PathBuf, + + /// Path to file containing x25519 lp public key. + pub public_x25519_lp_key_file: PathBuf, + + /// Path to file containing mlkem768 lp private key. + pub private_mlkem768_lp_key_file: PathBuf, + + /// Path to file containing mlkem768 lp public key. + pub public_mlkem768_lp_key_file: PathBuf, + + /// Path to file containing mceliece lp private key. + pub private_mceliece_lp_key_file: PathBuf, + + /// Path to file containing mceliece lp public key. + pub public_mceliece_lp_key_file: PathBuf, + // >> LP KEYS END } impl KeysPaths { @@ -116,6 +144,12 @@ impl KeysPaths { .join(DEFAULT_SECONDARY_X25519_SPHINX_KEY_FILENAME), private_x25519_noise_key_file: data_dir.join(DEFAULT_X25519_PRIVATE_NOISE_KEY_FILENAME), public_x25519_noise_key_file: data_dir.join(DEFAULT_X25519_PUBLIC_NOISE_KEY_FILENAME), + private_x25519_lp_key_file: data_dir.join(DEFAULT_X25519_PRIVATE_LP_KEY_FILENAME), + public_x25519_lp_key_file: data_dir.join(DEFAULT_X25519_PUBLIC_LP_KEY_FILENAME), + private_mlkem768_lp_key_file: data_dir.join(DEFAULT_MLKEM768_PRIVATE_KEY_FILENAME), + public_mlkem768_lp_key_file: data_dir.join(DEFAULT_MLKEM768_PUBLIC_KEY_FILENAME), + private_mceliece_lp_key_file: data_dir.join(DEFAULT_MCELIECE_PRIVATE_KEY_FILENAME), + public_mceliece_lp_key_file: data_dir.join(DEFAULT_MCELIECE_PUBLIC_KEY_FILENAME), } } @@ -132,6 +166,27 @@ impl KeysPaths { &self.public_x25519_noise_key_file, ) } + + pub fn x25519_lp_key_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_lp_key_file, + &self.public_x25519_lp_key_file, + ) + } + + pub fn mlkem768_key_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_mlkem768_lp_key_file, + &self.public_mlkem768_lp_key_file, + ) + } + + pub fn mceliece_key_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_mceliece_lp_key_file, + &self.public_mceliece_lp_key_file, + ) + } } #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] diff --git a/nym-node/src/config/template.rs b/nym-node/src/config/template.rs index 8177119780..6b5c1e39b7 100644 --- a/nym-node/src/config/template.rs +++ b/nym-node/src/config/template.rs @@ -96,6 +96,23 @@ private_x25519_noise_key_file = '{{ storage_paths.keys.private_x25519_noise_key_ # Path to file containing x25519 noise public key. public_x25519_noise_key_file = '{{ storage_paths.keys.public_x25519_noise_key_file }}' +# Path to file containing x25519 lp private key. +private_x25519_lp_key_file = '{{ storage_paths.keys.private_x25519_lp_key_file }}' + +# Path to file containing x25519 lp public key. +public_x25519_lp_key_file = '{{ storage_paths.keys.public_x25519_lp_key_file }}' + +# Path to file containing mlkem768 lp private key. +private_mlkem768_lp_key_file = '{{ storage_paths.keys.private_mlkem768_lp_key_file }}' + +# Path to file containing mlkem768 lp public key. +public_mlkem768_lp_key_file = '{{ storage_paths.keys.public_mlkem768_lp_key_file }}' + +# Path to file containing mceliece lp private key. +private_mceliece_lp_key_file = '{{ storage_paths.keys.private_mceliece_lp_key_file }}' + +# Path to file containing mceliece lp public key. +public_mceliece_lp_key_file = '{{ storage_paths.keys.public_mceliece_lp_key_file }}' ##### http-API nym-node config options ##### @@ -166,6 +183,29 @@ private_diffie_hellman_key_file = '{{ wireguard.storage_paths.private_diffie_hel # Path to file containing wireguard x25519 diffie hellman public key. public_diffie_hellman_key_file = '{{ wireguard.storage_paths.public_diffie_hellman_key_file }}' +##### Lewes Protocol config options ##### + +[lp] +# Bind address for the TCP LP control traffic. +# default: `[::]:41264` +control_bind_address = '{{ lp.control_bind_address }}' + +# Bind address for the UDP LP data traffic. +# default: `[::]:51264` +data_bind_address = '{{ lp.data_bind_address }}' + +# Custom announced port for listening for the TCP LP control traffic. +# If unspecified, the value from the `control_bind_address` will be used instead +# Useful when the node is behind a proxy. +# (default: 0 - disabled) +announce_control_port ={{#if lp.announce_control_port }} {{ lp.announce_control_port }} {{else}} 0 {{/if}} + +# Custom announced port for listening for the UDP LP data traffic. +# If unspecified, the value from the `data_bind_address` will be used instead +# Useful when the node is behind a proxy. +# (default: 0 - disabled) +announce_data_port ={{#if lp.announce_data_port }} {{ lp.announce_data_port }} {{else}} 0 {{/if}} + ##### verloc config options ##### diff --git a/nym-node/src/config/upgrade_helpers.rs b/nym-node/src/config/upgrade_helpers.rs index b2b8018231..8c556b409d 100644 --- a/nym-node/src/config/upgrade_helpers.rs +++ b/nym-node/src/config/upgrade_helpers.rs @@ -18,7 +18,8 @@ async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> { let cfg = try_upgrade_config_v8(path, cfg).await.ok(); let cfg = try_upgrade_config_v9(path, cfg).await.ok(); let cfg = try_upgrade_config_v10(path, cfg).await.ok(); - match try_upgrade_config_v11(path, cfg).await { + let cfg = try_upgrade_config_v11(path, cfg).await.ok(); + match try_upgrade_config_v12(path, cfg).await { Ok(cfg) => cfg.save(), Err(e) => { tracing::error!("Failed to finish upgrade: {e}"); diff --git a/nym-node/src/env.rs b/nym-node/src/env.rs index 318f96999f..01f8a7113d 100644 --- a/nym-node/src/env.rs +++ b/nym-node/src/env.rs @@ -66,9 +66,15 @@ pub mod vars { "NYMNODE_UPGRADE_MODE_ATTESTATION_URL"; pub const NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY_ARG: &str = "NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY"; - pub const NYMNODE_LP_USE_MOCK_ECASH_ARG: &str = "NYMNODE_LP_USE_MOCK_ECASH"; // exit gateway: pub const NYMNODE_UPSTREAM_EXIT_POLICY_ARG: &str = "NYMNODE_UPSTREAM_EXIT_POLICY"; pub const NYMNODE_OPEN_PROXY_ARG: &str = "NYMNODE_OPEN_PROXY"; + + // LP: + pub const NYMNODE_LP_CONTROL_BIND_ADDRESS_ARG: &str = "NYMNODE_LP_CONTROL_BIND_ADDRESS"; + pub const NYMNODE_LP_CONTROL_ANNOUNCE_PORT_ARG: &str = "NYMNODE_LP_CONTROL_ANNOUNCE_PORT"; + pub const NYMNODE_LP_DATA_BIND_ADDRESS_ARG: &str = "NYMNODE_LP_DATA_BIND_ADDRESS"; + pub const NYMNODE_LP_DATA_ANNOUNCE_PORT_ARG: &str = "NYMNODE_LP_DATA_ANNOUNCE_PORT"; + pub const NYMNODE_LP_USE_MOCK_ECASH_ARG: &str = "NYMNODE_LP_USE_MOCK_ECASH"; } diff --git a/nym-node/src/error.rs b/nym-node/src/error.rs index abe336dc44..f1328c61c9 100644 --- a/nym-node/src/error.rs +++ b/nym-node/src/error.rs @@ -2,13 +2,15 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::node::http::error::NymNodeHttpError; +use crate::node::lp::error::LpHandlerError; use crate::wireguard::error::WireguardError; use nym_http_api_client::HttpClientError; use nym_ip_packet_router::error::ClientCoreError; +use nym_kkt::keys::storage_wrappers::MalformedStoredKeyError; use nym_validator_client::ValidatorClientError; use nym_validator_client::nyxd::error::NyxdError; use std::io; -use std::net::IpAddr; +use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; use thiserror::Error; @@ -231,6 +233,18 @@ pub enum NymNodeError { #[error("failed upgrade")] FailedUpgrade, + + #[error(transparent)] + MalformedStoredKey(#[from] MalformedStoredKeyError), + + #[error("failed to bind LP to {address}: {source}")] + LpBindFailure { + address: SocketAddr, + source: io::Error, + }, + + #[error(transparent)] + LpFailure(#[from] LpHandlerError), } impl From for NymNodeError { diff --git a/nym-node/src/lib.rs b/nym-node/src/lib.rs new file mode 100644 index 0000000000..9882b20cb7 --- /dev/null +++ b/nym-node/src/lib.rs @@ -0,0 +1,10 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +// allow dead code in the lib imports +#![allow(dead_code)] + +pub mod config; +pub mod error; +pub mod node; +pub mod wireguard; diff --git a/nym-node/src/logging.rs b/nym-node/src/logging.rs index e1812059be..4ed952388d 100644 --- a/nym-node/src/logging.rs +++ b/nym-node/src/logging.rs @@ -7,6 +7,42 @@ use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{EnvFilter, Layer}; +/// Configuration for OpenTelemetry OTLP export. +#[cfg(feature = "otel")] +pub(crate) struct OtelConfig { + /// OTLP/gRPC collector endpoint, e.g. `http://localhost:4317` + /// or `https://ingest.eu.signoz.cloud:443` for SigNoz Cloud. + pub endpoint: String, + /// Service name reported to the collector (appears in SigNoz "Services" view). + pub service_name: String, + /// Optional SigNoz Cloud ingestion key for authenticated export. + /// Sent as the `signoz-ingestion-key` gRPC metadata header. + pub ingestion_key: Option, + /// Deployment environment label, e.g. `mainnet`, `sandbox`, `canary`. + /// Attached as the `deployment.environment` OTel resource attribute. + pub environment: String, + /// Trace sampling ratio in 0.0..=1.0 (e.g. 0.1 = 10% of traces). Used to limit cost. + pub sample_ratio: f64, + /// Timeout in seconds for each OTLP export batch. Prevents unbounded blocking. + pub export_timeout_secs: u64, +} + +/// Handle returned when OTel is active. Flushes pending spans on drop +/// to prevent telemetry loss during panics or early exits. +#[cfg(feature = "otel")] +pub(crate) struct OtelGuard { + pub provider: opentelemetry_sdk::trace::SdkTracerProvider, +} + +#[cfg(feature = "otel")] +impl Drop for OtelGuard { + fn drop(&mut self) { + if let Err(e) = self.provider.shutdown() { + eprintln!("OpenTelemetry shutdown error in Drop: {e}"); + } + } +} + pub(crate) fn granual_filtered_env() -> anyhow::Result { fn directive_checked(directive: impl Into) -> anyhow::Result { directive.into().parse().map_err(From::from) @@ -22,12 +58,86 @@ pub(crate) fn granual_filtered_env() -> anyhow::Result { Ok(filter) } +/// Initialise the tracing subscriber stack. +/// +/// When the `otel` feature is enabled **and** an `OtelConfig` is supplied, an +/// OTLP exporter layer is added and the returned `OtelGuard` must be used to +/// flush pending spans on shutdown. +#[cfg(feature = "otel")] +pub(crate) fn setup_tracing_logger(otel: Option) -> anyhow::Result> { + let stderr_layer = + default_tracing_fmt_layer(std::io::stderr).with_filter(granual_filtered_env()?); + + cfg_if::cfg_if! {if #[cfg(feature = "tokio-console")] { + let console_layer = console_subscriber::spawn(); + + if let Some(otel_config) = otel { + let (otel_layer, provider) = nym_bin_common::logging::init_otel_layer( + &otel_config.service_name, + &otel_config.endpoint, + otel_config.ingestion_key.as_deref(), + &otel_config.environment, + otel_config.sample_ratio, + otel_config.export_timeout_secs, + ).map_err(|e| anyhow::anyhow!( + "failed to initialise OpenTelemetry exporter (endpoint: {}, service: {}): {e}", + otel_config.endpoint, + otel_config.service_name, + ))?; + + tracing_subscriber::registry() + .with(console_layer) + .with(stderr_layer) + .with(otel_layer) + .init(); + + Ok(Some(OtelGuard { provider })) + } else { + tracing_subscriber::registry() + .with(console_layer) + .with(stderr_layer) + .init(); + + Ok(None) + } + } else { + if let Some(otel_config) = otel { + let (otel_layer, provider) = nym_bin_common::logging::init_otel_layer( + &otel_config.service_name, + &otel_config.endpoint, + otel_config.ingestion_key.as_deref(), + &otel_config.environment, + otel_config.sample_ratio, + otel_config.export_timeout_secs, + ).map_err(|e| anyhow::anyhow!( + "failed to initialise OpenTelemetry exporter (endpoint: {}, service: {}): {e}", + otel_config.endpoint, + otel_config.service_name, + ))?; + + tracing_subscriber::registry() + .with(stderr_layer) + .with(otel_layer) + .init(); + + Ok(Some(OtelGuard { provider })) + } else { + tracing_subscriber::registry() + .with(stderr_layer) + .init(); + + Ok(None) + } + }} +} + +/// Non-OTel variant -- identical subscriber stack without the OTLP layer. +#[cfg(not(feature = "otel"))] pub(crate) fn setup_tracing_logger() -> anyhow::Result<()> { let stderr_layer = default_tracing_fmt_layer(std::io::stderr).with_filter(granual_filtered_env()?); cfg_if::cfg_if! {if #[cfg(feature = "tokio-console")] { - // instrument tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time let console_layer = console_subscriber::spawn(); tracing_subscriber::registry() diff --git a/nym-node/src/main.rs b/nym-node/src/main.rs index 8c7a8aa2e2..3b83a1ee96 100644 --- a/nym-node/src/main.rs +++ b/nym-node/src/main.rs @@ -10,13 +10,13 @@ use nym_bin_common::logging::maybe_print_banner; use nym_config::defaults::setup_env; mod cli; -pub(crate) mod config; +pub mod config; mod env; -pub(crate) mod error; +pub mod error; mod logging; -pub(crate) mod node; +pub mod node; pub(crate) mod throughput_tester; -pub(crate) mod wireguard; +pub mod wireguard; fn main() -> anyhow::Result<()> { // std::env::set_var( diff --git a/nym-node/src/node/helpers.rs b/nym-node/src/node/helpers.rs index b0112a7004..dfe41d67ad 100644 --- a/nym-node/src/node/helpers.rs +++ b/nym-node/src/node/helpers.rs @@ -6,6 +6,10 @@ use crate::error::{KeyIOFailure, NymNodeError}; use crate::node::key_rotation::key::{SphinxPrivateKey, SphinxPublicKey}; use crate::node::nym_apis_client::NymApisClient; use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_kkt::keys::storage_wrappers::StorableKey; +use nym_kkt::keys::{ + DHKeyPair, DHPrivateKey, MlKem768KeyPair, MlKem768PrivateKey, MlKem768PublicKey, mceliece, +}; use nym_node_requests::api::v1::node::models::NodeDescription; use nym_pemstore::KeyPairPath; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; @@ -169,6 +173,34 @@ pub(crate) fn load_x25519_wireguard_keypair( Ok(load_keypair(paths, "x25519-wireguard")?) } +fn load_lp_key(path: P, name: &'static str) -> Result +where + P: AsRef, + S: StorableKey, +{ + let repr = load_key::<::StorableRepresentation<'_>, _>(path, name)?; + Ok(S::from_storable(repr)?) +} + +pub(crate) fn load_x25519_lp_keypair(paths: &KeyPairPath) -> Result { + let pk: DHPrivateKey = load_lp_key(&paths.private_key_path, "x25519-lp-private-key")?; + Ok(DHKeyPair::from(pk)) +} + +pub(crate) fn load_mlkem768_keypair(paths: &KeyPairPath) -> Result { + let sk: MlKem768PrivateKey = load_lp_key(&paths.private_key_path, "mlkem768-private-key")?; + let pk: MlKem768PublicKey = load_lp_key(&paths.public_key_path, "mlkem768-public-key")?; + Ok(MlKem768KeyPair::from(sk, pk)) +} + +pub(crate) fn load_mceliece_keypair( + paths: &KeyPairPath, +) -> Result { + let sk: mceliece::SecretKey = load_lp_key(&paths.private_key_path, "mceliece-private-key")?; + let pk: mceliece::PublicKey = load_lp_key(&paths.public_key_path, "mceliece-public-key")?; + Ok(mceliece::KeyPair { sk, pk }) +} + pub(crate) fn store_ed25519_identity_keypair( keys: &ed25519::KeyPair, paths: &KeyPairPath, @@ -183,6 +215,57 @@ pub(crate) fn store_x25519_noise_keypair( Ok(store_keypair(keys, paths, "x25519-noise")?) } +pub(crate) fn store_x25519_lp_keypair( + keys: &DHKeyPair, + paths: &KeyPairPath, +) -> Result<(), NymNodeError> { + store_key( + &keys.pk.to_storable(), + &paths.public_key_path, + "x25519-lp-public-key", + )?; + store_key( + &keys.sk().to_storable(), + &paths.private_key_path, + "x25519-lp-private-key", + )?; + Ok(()) +} + +pub(crate) fn store_mlkem768_keypair( + keys: &MlKem768KeyPair, + paths: &KeyPairPath, +) -> Result<(), NymNodeError> { + store_key( + &keys.public_key().to_storable(), + &paths.public_key_path, + "mlkem768-public-key", + )?; + store_key( + &keys.private_key().to_storable(), + &paths.private_key_path, + "mlkem768-private-key", + )?; + Ok(()) +} + +pub(crate) fn store_mceliece_keypair( + keys: &mceliece::KeyPair, + paths: &KeyPairPath, +) -> Result<(), NymNodeError> { + store_key( + &keys.pk.to_storable(), + &paths.public_key_path, + "mceliece-public-key", + )?; + store_key( + &keys.sk.to_storable(), + &paths.private_key_path, + "mceliece-private-key", + )?; + Ok(()) +} + pub(crate) async fn get_current_rotation_id( nym_apis: &[Url], fallback_nyxd: &[Url], 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 36d8dc785d..6e0aa38e58 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 @@ -3,13 +3,13 @@ use axum::Router; use axum::routing::get; -use nym_node_requests::api::v1::lewes_protocol::models; +use nym_node_requests::api::SignedLewesProtocol; pub mod root; -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct Config { - pub details: Option, + pub details: SignedLewesProtocol, } pub(crate) fn routes(config: Config) -> Router { diff --git a/nym-node/src/node/http/router/api/v1/lewes_protocol/root.rs b/nym-node/src/node/http/router/api/v1/lewes_protocol/root.rs index b056db5a90..06b2961717 100644 --- a/nym-node/src/node/http/router/api/v1/lewes_protocol/root.rs +++ b/nym-node/src/node/http/router/api/v1/lewes_protocol/root.rs @@ -4,30 +4,29 @@ use axum::extract::Query; use axum::http::StatusCode; use nym_http_api_common::{FormattedResponse, OutputParams}; -use nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol; +use nym_node_requests::api::{SignedLewesProtocol, SignedLewesProtocolInfo}; /// Returns root Lewes Protocol information #[utoipa::path( get, - path = "", - context_path = "/api/v1/lewes-protocol", + path = "/lewes-protocol", + context_path = "/api/v1", tag = "Lewes Protocol", responses( (status = 501, description = "the endpoint hasn't been implemented yet"), (status = 200, content( - (LewesProtocol = "application/json"), - (LewesProtocol = "application/yaml"), - (LewesProtocol = "application/bincode") + (SignedLewesProtocolInfo = "application/json"), + (SignedLewesProtocolInfo = "application/yaml"), + (SignedLewesProtocolInfo = "application/bincode") )) ), params(OutputParams) )] pub(crate) async fn root_lewes_protocol( - config: Option, + config: SignedLewesProtocol, Query(output): Query, ) -> Result { - let config = config.ok_or(StatusCode::NOT_IMPLEMENTED)?; Ok(output.to_response(config)) } -pub type LewesProtocolResponse = FormattedResponse; +pub type LewesProtocolResponse = FormattedResponse; diff --git a/nym-node/src/node/http/router/api/v1/metrics/verloc.rs b/nym-node/src/node/http/router/api/v1/metrics/verloc.rs index 8a2a046637..df8fbb5083 100644 --- a/nym-node/src/node/http/router/api/v1/metrics/verloc.rs +++ b/nym-node/src/node/http/router/api/v1/metrics/verloc.rs @@ -4,7 +4,7 @@ use axum::extract::{Query, State}; use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::metrics::models::{ - VerlocNodeResult, VerlocResult, VerlocResultData, VerlocStats, + VerlocMeasurement, VerlocNodeResult, VerlocResult, VerlocResultData, VerlocStats, }; use nym_verloc::measurements::SharedVerlocStats; @@ -43,6 +43,12 @@ async fn build_response(verloc_stats: &SharedVerlocStats) -> VerlocStats { .iter() .map(|r| VerlocNodeResult { node_identity: r.node_identity, + latest_measurement: r.latest_measurement.map(|m| VerlocMeasurement { + minimum: m.minimum, + mean: m.mean, + maximum: m.maximum, + standard_deviation: m.standard_deviation, + }), }) .collect(), } diff --git a/nym-node/src/node/http/router/api/v1/node/host_information.rs b/nym-node/src/node/http/router/api/v1/node/host_information.rs index 53427f0cdf..8e828f8590 100644 --- a/nym-node/src/node/http/router/api/v1/node/host_information.rs +++ b/nym-node/src/node/http/router/api/v1/node/host_information.rs @@ -16,7 +16,8 @@ use nym_node_requests::api::{SignedDataHostInfo, v1::node::models::SignedHostInf responses( (status = 200, content( (SignedDataHostInfo = "application/json"), - (SignedDataHostInfo = "application/yaml") + (SignedDataHostInfo = "application/yaml"), + (SignedDataHostInfo = "application/bincode") )) ), params(OutputParams) diff --git a/nym-node/src/node/http/router/mod.rs b/nym-node/src/node/http/router/mod.rs index dbaee0e8d5..4c3618c76f 100644 --- a/nym-node/src/node/http/router/mod.rs +++ b/nym-node/src/node/http/router/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::node::http::NymNodeHttpServer; +use crate::node::http::api::v1::lewes_protocol; use crate::node::http::error::NymNodeHttpError; use crate::node::http::state::AppState; use axum::Router; @@ -9,6 +10,7 @@ use axum::response::Redirect; use axum::routing::get; use nym_bin_common::bin_info_owned; use nym_http_api_common::middleware::logging; +use nym_node_requests::api::SignedLewesProtocol; 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; @@ -33,7 +35,7 @@ pub struct HttpServerConfig { } impl HttpServerConfig { - pub fn new() -> Self { + pub fn new(signed_lewes_protocol: SignedLewesProtocol) -> Self { HttpServerConfig { landing: Default::default(), api: api::Config { @@ -52,7 +54,9 @@ impl HttpServerConfig { network_requester: Default::default(), ip_packet_router: Default::default(), authenticator: Default::default(), - lewes_protocol: Default::default(), + lewes_protocol: lewes_protocol::Config { + details: signed_lewes_protocol, + }, }, }, } diff --git a/nym-node/src/node/key_rotation/key.rs b/nym-node/src/node/key_rotation/key.rs index 7d31455ae5..350cae8a7f 100644 --- a/nym-node/src/node/key_rotation/key.rs +++ b/nym-node/src/node/key_rotation/key.rs @@ -113,14 +113,11 @@ impl PemStorableKey for SphinxPrivateKey { #[cfg(test)] mod tests { use super::*; - use rand::SeedableRng; - use rand_chacha::ChaCha20Rng; + use nym_test_utils::helpers::deterministic_rng; #[test] fn private_key_bytes_convertion() { - // Set up a deterministic RNG. - let seed = [42u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); + let mut rng = deterministic_rng(); let key = SphinxPrivateKey { rotation_id: 42, diff --git a/nym-node/src/node/lp/data_handler.rs b/nym-node/src/node/lp/data_handler.rs new file mode 100644 index 0000000000..9c842fd159 --- /dev/null +++ b/nym-node/src/node/lp/data_handler.rs @@ -0,0 +1,237 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +//! LP Data Handler - UDP listener for LP data plane (port 51264) +//! +//! This module handles the data plane for LP clients that have completed registration +//! via the control plane (TCP:41264). LP-wrapped Sphinx packets arrive here, get +//! decrypted, and are forwarded into the mixnet. +//! +//! # Packet Flow +//! +//! ```text +//! LP Client → UDP:51264 → LP Data Handler → Mixnet Entry +//! LP(Sphinx) decrypt LP forward Sphinx +//! ``` +//! + +use super::LpHandlerState; +use crate::error::NymNodeError; +use crate::node::lp::error::LpHandlerError; +use nym_lp::packet::OuterHeader; +use nym_metrics::inc; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::net::UdpSocket; +use tracing::*; + +/// Maximum UDP packet size we'll accept +/// Sphinx packets are typically ~2KB, LP overhead is ~50 bytes, so 4KB is plenty +const MAX_UDP_PACKET_SIZE: usize = 4096; + +/// LP Data Handler for UDP data plane +pub struct LpDataHandler { + /// UDP socket for receiving LP-wrapped Sphinx packets + socket: Arc, + + /// Shared state with TCP control plane + #[allow(dead_code)] + state: LpHandlerState, + + /// Shutdown token + shutdown: nym_task::ShutdownToken, +} + +impl LpDataHandler { + /// Create a new LP data handler + pub async fn new( + bind_addr: SocketAddr, + state: LpHandlerState, + shutdown: nym_task::ShutdownToken, + ) -> Result { + let socket = UdpSocket::bind(bind_addr).await.map_err(|source| { + error!("Failed to bind LP data socket to {bind_addr}: {source}"); + NymNodeError::LpBindFailure { + address: bind_addr, + source, + } + })?; + + info!("LP data handler listening on UDP {bind_addr}"); + + Ok(Self { + socket: Arc::new(socket), + state, + shutdown, + }) + } + + /// Run the UDP packet receive loop + pub async fn run(self) -> Result<(), LpHandlerError> { + let mut buf = vec![0u8; MAX_UDP_PACKET_SIZE]; + + loop { + tokio::select! { + biased; + + _ = self.shutdown.cancelled() => { + info!("LP data handler: received shutdown signal"); + break; + } + + result = self.socket.recv_from(&mut buf) => { + match result { + Ok((len, src_addr)) => { + // Process packet in place (no spawn - UDP is fast) + if let Err(e) = self.handle_packet(&buf[..len], src_addr).await { + debug!("LP data packet error from {src_addr}: {e}"); + inc!("lp_data_packet_errors"); + } + } + Err(e) => { + warn!("LP data socket recv error: {e}"); + inc!("lp_data_recv_errors"); + } + } + } + } + } + + info!("LP data handler shutdown complete"); + Ok(()) + } + + /// Handle a single UDP packet + /// + /// # Packet Processing Steps + /// 1. Parse LP header to get receiver_idx (for routing) + /// 2. Look up session state machine by receiver_idx + /// 3. Process packet through state machine (handles decryption + replay protection) + /// 4. Forward decrypted Sphinx packet to mixnet + /// + /// # Security + /// The state machine's `process_input()` method handles replay protection by: + /// - Checking packet counter against receiving window + /// - Marking counter as used after successful decryption + /// + /// This prevents replay attacks where captured packets are re-sent. + async fn handle_packet( + &self, + packet: &[u8], + src_addr: SocketAddr, + ) -> Result<(), LpHandlerError> { + inc!("lp_data_packets_received"); + + let _ = OuterHeader::parse(packet)?; + trace!( + "received {} bytes from {src_addr} on the unimplemented LP Data endpoint", + packet.len() + ); + + Err(LpHandlerError::UnimplementedDataChannel) + // leave old code for future reference + + // + // // Step 1: Parse LP header (always cleartext for routing) + // let header = nym_lp::codec::parse_lp_header_only(packet).map_err(|e| { + // LpHandlerError::LpProtocolError(format!("Failed to parse LP header: {}", e)) + // })?; + // + // let receiver_idx = header.receiver_idx; + // let counter = header.counter; + // let len = packet.len(); + // + // trace!("LP data packet from {src_addr} (receiver_idx={receiver_idx}, counter={counter}, len={len})"); + // + // // Step 2: Look up session state machine by receiver_idx (mutable for state updates) + // let mut state_entry = self + // .state + // .session_states + // .get_mut(&receiver_idx) + // .ok_or_else(|| { + // inc!("lp_data_unknown_session"); + // LpHandlerError::LpProtocolError(format!( + // "Unknown session for receiver_idx {receiver_idx}" + // )) + // })?; + // + // // Update last activity timestamp + // state_entry.value().touch(); + // + // // Step 3: Get outer AEAD key for packet parsing + // let outer_key = state_entry + // .value() + // .state + // .session() + // .map_err(|e| LpHandlerError::LpProtocolError(format!("Session error: {e}")))? + // .outer_aead_key(); + // + // // Parse full packet with outer AEAD decryption + // let lp_packet = nym_lp::codec::parse_lp_packet(packet, Some(outer_key)).map_err(|e| { + // inc!("lp_data_decrypt_errors"); + // LpHandlerError::LpProtocolError(format!("Failed to decrypt LP packet: {}", e)) + // })?; + // + // // Step 4: Process packet through state machine + // // This handles: + // // - Replay protection (counter check + mark) + // // - Inner Noise decryption + // // - Subsession handling if applicable + // let state_machine = &mut state_entry.value_mut().state; + // + // let action = state_machine + // .process_input(LpInput::ReceivePacket(lp_packet)) + // .ok_or_else(|| { + // LpHandlerError::LpProtocolError("State machine returned no action".to_string()) + // })? + // .map_err(|e| { + // inc!("lp_data_state_machine_errors"); + // LpHandlerError::LpProtocolError(format!("State machine error: {}", e)) + // })?; + // + // // Release session lock before forwarding + // drop(state_entry); + // + // // Step 5: Handle the action from state machine + // match action { + // LpAction::DeliverData(data) => { + // // Decrypted application data - forward as Sphinx packet + // self.forward_sphinx_packet(&data.content).await?; + // inc!("lp_data_packets_forwarded"); + // Ok(()) + // } + // LpAction::SendPacket(_response_packet) => { + // // UDP is connectionless - we can't send responses back easily + // // For subsession rekeying, the client should use TCP control plane + // debug!( + // "Ignoring SendPacket action on UDP (receiver_idx={receiver_idx}) - use TCP for rekeying", + // ); + // inc!("lp_data_ignored_send_actions"); + // Ok(()) + // } + // other => { + // warn!( + // "Unexpected action on UDP data plane from {}: {:?}", + // src_addr, other + // ); + // inc!("lp_data_unexpected_actions"); + // Err(LpHandlerError::LpProtocolError(format!( + // "Unexpected state machine action on UDP: {:?}", + // other + // ))) + // } + // } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Sphinx packets are typically around 2KB + // LP overhead is small (~50 bytes header + AEAD tag) + // 4KB should be plenty with room to spare + const _: () = { + assert!(MAX_UDP_PACKET_SIZE >= 2048 + 100); + }; +} diff --git a/nym-node/src/node/lp/error.rs b/nym-node/src/node/lp/error.rs new file mode 100644 index 0000000000..c5291c45c9 --- /dev/null +++ b/nym-node/src/node/lp/error.rs @@ -0,0 +1,73 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::lp::LpReceiverIndex; +use nym_lp::packet::message::LpMessageType; +use nym_lp::state_machine::LpAction; +use nym_lp::transport::LpTransportError; +use nym_lp::{LpError, packet::MalformedLpPacketError}; +use std::net::SocketAddr; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum LpHandlerError { + #[error("failed to establish egress connection to {egress}: {reason}")] + ConnectionFailure { egress: SocketAddr, reason: String }, + + #[error(transparent)] + LpTransportError(#[from] LpTransportError), + + #[error("missing session state for {receiver_index} - has it been removed due to inactivity?")] + MissingLpSession { receiver_index: LpReceiverIndex }, + + #[error(transparent)] + LpProtocolError(#[from] LpError), + + #[error("the initial KKT/PSQ handshake has not been completed")] + IncompleteHandshake, + + #[error("receiver_idx mismatch: connection bound to {established}, packet has {received}")] + MismatchedReceiverIndex { + established: LpReceiverIndex, + received: LpReceiverIndex, + }, + + #[error("no action has been emitted from the LP State Machine")] + UnexpectedStateMachineHalt, + + #[error("the state machine instructed an unexpected action: {action:?}")] + UnexpectedStateMachineAction { action: LpAction }, + + #[error("received registration request was malformed: {source}")] + MalformedRegistrationRequest { source: bincode::Error }, + + #[error("received a malformed packet: {0}")] + MalformedLpPacket(#[from] MalformedLpPacketError), + + #[error("received payload type of an unexpected type: {typ:?}")] + UnexpectedLpPayload { typ: LpMessageType }, + + #[error("timed out while attempting to send to/receive from the connection")] + ConnectionTimeout, + + #[error("data channel is not yet implemented")] + UnimplementedDataChannel, + + #[error("{0}")] + Other(String), +} + +impl LpHandlerError { + pub fn is_connection_closed(&self) -> bool { + match self { + LpHandlerError::LpTransportError(transport_err) => { + matches!(transport_err, LpTransportError::ConnectionClosed) + } + _ => false, + } + } + + pub fn other(msg: impl Into) -> Self { + LpHandlerError::Other(msg.into()) + } +} diff --git a/nym-node/src/node/lp/handler.rs b/nym-node/src/node/lp/handler.rs new file mode 100644 index 0000000000..e8c655d435 --- /dev/null +++ b/nym-node/src/node/lp/handler.rs @@ -0,0 +1,823 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use super::{LpHandlerState, LpReceiverIndex, TimestampedState}; +use crate::node::lp::error::LpHandlerError; +use dashmap::mapref::one::RefMut; +use nym_lp::packet::message::LpMessageType; +use nym_lp::packet::{EncryptedLpPacket, ForwardPacketData, LpMessage}; +use nym_lp::state_machine::{LpAction, LpInput}; +use nym_lp::transport::LpHandshakeChannel; +use nym_lp::transport::traits::LpTransportChannel; +use nym_lp::{LpSession, LpStateMachine, packet::message::ExpectedResponseSize}; +use nym_metrics::{add_histogram_obs, inc}; +use nym_registration_common::{LpRegistrationRequest, RegistrationStatus}; +use std::net::SocketAddr; +use std::time::Duration; +use tokio::net::TcpStream; +use tokio::time::timeout; +use tracing::*; + +// Histogram buckets for LP operation duration (legacy - used by unused forwarding methods) +const LP_DURATION_BUCKETS: &[f64] = &[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]; + +// Timeout for forward I/O operations (send + receive on exit stream) +// Must be long enough to cover exit gateway processing time +const FORWARD_IO_TIMEOUT_SECS: u64 = 30; + +// Histogram buckets for LP connection lifecycle duration +// LP connections can be very short (registration only: ~1s) or very long (dVPN sessions: hours/days) +// Covers full range from seconds to 24 hours +const LP_CONNECTION_DURATION_BUCKETS: &[f64] = &[ + 1.0, // 1 second + 5.0, // 5 seconds + 10.0, // 10 seconds + 30.0, // 30 seconds + 60.0, // 1 minute + 300.0, // 5 minutes + 600.0, // 10 minutes + 1800.0, // 30 minutes + 3600.0, // 1 hour + 7200.0, // 2 hours + 14400.0, // 4 hours + 28800.0, // 8 hours + 43200.0, // 12 hours + 86400.0, // 24 hours +]; + +/// Connection lifecycle statistics tracking +struct ConnectionStats { + /// When the connection started + start_time: std::time::Instant, + /// Total bytes received (including protocol framing) + bytes_received: u64, + /// Total bytes sent (including protocol framing) + bytes_sent: u64, +} + +impl ConnectionStats { + fn new() -> Self { + Self { + start_time: std::time::Instant::now(), + bytes_received: 0, + bytes_sent: 0, + } + } + + fn record_bytes_received(&mut self, bytes: usize) { + self.bytes_received += bytes as u64; + } + + fn record_bytes_sent(&mut self, bytes: usize) { + self.bytes_sent += bytes as u64; + } +} + +pub struct LpConnectionHandler { + stream: S, + remote_addr: SocketAddr, + state: LpHandlerState, + stats: ConnectionStats, + + // /// Flag indicating whether this is a connection from an entry gateway serving as a proxy + // forwarded_connection: bool, + /// Bound receiver_idx for this connection (set after first packet). + /// All subsequent packets on this connection must use this receiver_idx. + /// Set from ClientHello's proposed receiver_index, or from header for non-bootstrap packets. + bound_receiver_idx: Option, + + /// Persistent connection to exit gateway for forwarding. + /// Opened on first forward, reused for subsequent forwards, closed when client disconnects. + /// Tuple contains (stream, target_address) to verify subsequent forwards go to same exit. + exit_stream: Option<(S, SocketAddr)>, +} + +impl LpConnectionHandler +where + S: LpTransportChannel + LpHandshakeChannel + Unpin, +{ + pub fn new( + stream: S, + // forwarded_connection: bool, + remote_addr: SocketAddr, + state: LpHandlerState, + ) -> Self { + Self { + stream, + remote_addr, + // forwarded_connection, + state, + stats: ConnectionStats::new(), + bound_receiver_idx: None, + exit_stream: None, + } + } + + /// Get the mutable reference to the state machine associated with this client. + /// It is vital it's never held across await points or this might lead to a deadlock. + fn state_entry_mut( + &self, + ) -> Result>, LpHandlerError> { + let receiver_index = self.bound_receiver_index()?; + self.state + .session_states + .get_mut(&receiver_index) + .ok_or_else(|| LpHandlerError::MissingLpSession { receiver_index }) + } + + /// AIDEV-NOTE: Stream-oriented packet loop + /// This handler processes multiple packets on a single TCP connection. + /// Connection lifecycle: handshake + registration, then client closes. + /// First packet binds the connection to a receiver_idx (session-affine). + /// Binding is set by handle_client_hello() from payload's receiver_index, + /// or by validate_or_set_binding() for non-bootstrap first packets. + pub async fn handle(mut self) -> Result<(), LpHandlerError> { + debug!("Handling LP connection from {}", self.remote_addr); + + // Track total LP connections handled + inc!("lp_connections_total"); + + // ============================================================ + // STREAM-ORIENTED PROCESSING: Loop until connection closes + // State persists in LpHandlerState maps across packets + // ============================================================ + + // 1. complete KKT/PSQ handshake before doing anything else. + // bail if it takes too long + let timeout = self.state.lp_config.debug.handshake_ttl; + let local_peer = self.state.local_lp_peer.clone(); + let stream = &mut self.stream; + + let session = match tokio::time::timeout(timeout, async move { + LpSession::psq_handshake_responder(stream, local_peer) + .complete_handshake() + .await + }) + .await + { + Err(_timeout) => { + debug!( + "timed out attempting to complete KTT/PSQ handshake with {}", + self.remote_addr + ); + self.emit_lifecycle_metrics(false); + return Ok(()); + } + Ok(Err(handshake_failure)) => { + debug!( + "failed to complete KKT/PSQ handshake with {}: {handshake_failure}", + self.remote_addr + ); + self.emit_lifecycle_metrics(false); + return Ok(()); + } + Ok(Ok(session)) => session, + }; + let receiver_idx = session.receiver_index(); + + // 2. insert the state machine into the shared state + let state_machine = LpStateMachine::new(session); + self.state + .session_states + .insert(receiver_idx, TimestampedState::new(state_machine)); + self.bound_receiver_idx = Some(receiver_idx); + + // 3. handle any new incoming packet + loop { + // Step 1: Receive raw packet bytes and parse header only (for routing) + let encrypted_packet = match self.receive_raw_packet().await { + Ok(result) => result, + Err(err) => { + if err.is_connection_closed() { + // Graceful EOF - client closed connection + trace!("Connection closed by {} (EOF)", self.remote_addr); + break; + } else { + inc!("lp_errors_receive_packet"); + self.emit_lifecycle_metrics(false); + return Err(err); + } + } + }; + + let receiver_idx = encrypted_packet.outer_header().receiver_idx; + + // Step 2: Validate the binding + if let Err(e) = self.validate_binding(receiver_idx) { + self.emit_lifecycle_metrics(false); + return Err(e); + } + + // Step 3: Process the packet + if let Err(e) = self.process_packet(encrypted_packet).await { + self.emit_lifecycle_metrics(false); + return Err(e); + } + } + + self.emit_lifecycle_metrics(true); + Ok(()) + } + + fn bound_receiver_index(&self) -> Result { + self.bound_receiver_idx + .ok_or_else(|| LpHandlerError::IncompleteHandshake) + } + + /// Validate that the receiver_idx matches the bound session. + fn validate_binding(&self, receiver_idx: LpReceiverIndex) -> Result<(), LpHandlerError> { + let bound_receiver_idx = self.bound_receiver_index()?; + + if bound_receiver_idx != receiver_idx { + warn!( + "Receiver_idx mismatch from {}: expected {bound_receiver_idx}, got {receiver_idx}", + self.remote_addr + ); + inc!("lp_errors_receiver_idx_mismatch"); + return Err(LpHandlerError::MismatchedReceiverIndex { + established: bound_receiver_idx, + received: receiver_idx, + }); + } + + Ok(()) + } + + /// Process a single packet: lookup session, parse, route to handler. + /// Individual handlers do NOT emit lifecycle metrics - the main loop handles that. + /// + /// This handles packets on established sessions, which can be either: + /// EncryptedData containing LpRegistrationRequest or ForwardPacketData + /// + /// We process all transport packets through the state machine. + /// The state machine returns appropriate actions: + /// - DeliverData: decrypted application data to process + /// - SendPacket: response to send + async fn process_packet( + &mut self, + encrypted_packet: EncryptedLpPacket, + ) -> Result<(), LpHandlerError> { + let receiver_index = encrypted_packet.outer_header().receiver_idx; + + let mut state_entry = self.state_entry_mut()?; + + // Update last activity timestamp + state_entry.value().touch(); + + let state_machine = &mut state_entry.value_mut().state; + + trace!( + "Received packet from {} (receiver_idx={receiver_index}, counter={})", + self.remote_addr, + encrypted_packet.outer_header().counter, + ); + + // Process packet through state machine + let action = state_machine + .process_input(LpInput::ReceivePacket(encrypted_packet)) + .ok_or(LpHandlerError::UnexpectedStateMachineHalt)??; + + drop(state_entry); + + match action { + LpAction::SendPacket(response_packet) => { + self.send_serialised_packet(&response_packet).await?; + Ok(()) + } + LpAction::DeliverData(data) => { + // Decrypted application data - process as registration/forwarding + self.handle_decrypted_payload(receiver_index, data).await + } + other @ LpAction::ConnectionClosed => { + warn!( + "Unexpected action in transport from {}: {other:?}", + self.remote_addr + ); + Err(LpHandlerError::UnexpectedStateMachineAction { action: other }) + } + } + } + + /// Handle decrypted transport payload (registration or forwarding request) + async fn handle_decrypted_payload( + &mut self, + receiver_idx: LpReceiverIndex, + decrypted_data: LpMessage, + ) -> Result<(), LpHandlerError> { + let remote = self.remote_addr; + + let header = decrypted_data.header; + let bytes = decrypted_data.content; + match header.kind { + LpMessageType::Registration => { + let request = LpRegistrationRequest::try_deserialise(&bytes) + .map_err(|source| LpHandlerError::MalformedRegistrationRequest { source })?; + + debug!( + "LP registration request from {remote} (receiver_idx={receiver_idx}): mode={:?}", + request.mode() + ); + + self.handle_registration_request(receiver_idx, request) + .await + } + LpMessageType::Forward => { + let forward_data = ForwardPacketData::decode(&bytes)?; + + self.handle_forwarding_request(receiver_idx, forward_data) + .await + } + typ @ LpMessageType::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(LpHandlerError::UnexpectedLpPayload { typ }) + } + } + } + + /// Attempt to wrap and send specified response back to the client + async fn send_response_packet( + &mut self, + serialised_response: Vec, + response_kind: LpMessageType, + ) -> Result<(), LpHandlerError> { + let mut state_entry = self.state_entry_mut()?; + + // Access session via state machine for subsession support + let state_machine = &mut state_entry.value_mut().state; + + let wrapped_lp_data = LpMessage::new(response_kind, serialised_response); + + // Process packet through state machine + let action = state_machine + .process_input(LpInput::SendData(wrapped_lp_data)) + .ok_or(LpHandlerError::UnexpectedStateMachineHalt)??; + + let packet = match action { + LpAction::SendPacket(packet) => packet, + action => return Err(LpHandlerError::UnexpectedStateMachineAction { action }), + }; + + drop(state_entry); + + self.send_serialised_packet(&packet).await?; + Ok(()) + } + + /// Handle registration request on an established session + async fn handle_registration_request( + &mut self, + receiver_idx: LpReceiverIndex, + request: LpRegistrationRequest, + ) -> Result<(), LpHandlerError> { + // Process registration (might modify state) + let response = self.state.process_registration(receiver_idx, request).await; + let response_bytes = response + .serialise() + .map_err(|source| LpHandlerError::MalformedRegistrationRequest { source })?; + + self.send_response_packet(response_bytes, LpMessageType::Registration) + .await?; + + 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(()) + } + + /// Handle forwarding request on an established session + /// + /// Entry gateway receives ForwardPacketData from client, forwards inner packet + /// to exit gateway, receives response, encrypts it, and sends back to client. + async fn handle_forwarding_request( + &mut self, + receiver_idx: LpReceiverIndex, + forward_data: ForwardPacketData, + ) -> Result<(), LpHandlerError> { + // Forward the packet to the target gateway and retrieve its response + let response_bytes = self.handle_forward_packet(forward_data).await?; + + self.send_response_packet(response_bytes, LpMessageType::Forward) + .await?; + + debug!( + "LP forwarding completed for {} (receiver_idx={receiver_idx})", + self.remote_addr + ); + + Ok(()) + } + + /// Returns reference to the established forwarding channel to the exit. + #[allow(dead_code)] + pub fn forwarding_channel(&self) -> &Option<(S, SocketAddr)> { + &self.exit_stream + } + + /// This method establishes connection to the target gateway in order to + /// forward received packets and retrieve any responses + // + // In the future it will also perform identity validation to make sure + // the target node is a valid gateway present in the network + // + // Do not manually call this function. It is only exposed for the purposes of integration tests + #[doc(hidden)] + pub async fn establish_exit_stream( + &mut self, + target_addr: SocketAddr, + ) -> Result<(), LpHandlerError> { + // Acquire semaphore permit to limit concurrent connection opens (FD exhaustion protection) + // Permit is scoped to this block - only protects the connect() call, not stream reuse + let _permit = match self.state.forward_semaphore.try_acquire() { + Ok(permit) => permit, + Err(_) => { + inc!("lp_forward_rejected"); + return Err(LpHandlerError::other("Gateway at forward capacity")); + } + }; + + // Connect to target gateway with timeout + let stream = match timeout(Duration::from_secs(5), S::connect(target_addr)).await { + Ok(Ok(stream)) => stream, + Ok(Err(e)) => { + inc!("lp_forward_failed"); + return Err(LpHandlerError::ConnectionFailure { + egress: target_addr, + reason: e.to_string(), + }); + } + Err(_) => { + inc!("lp_forward_failed"); + return Err(LpHandlerError::ConnectionFailure { + egress: target_addr, + reason: "target gateway connection timeout".to_string(), + }); + } + }; + + debug!("Opened persistent exit connection to {target_addr} for forwarding"); + self.exit_stream = Some((stream, target_addr)); + + Ok(()) + } + + /// Forward an LP packet to another gateway + /// + /// This method connects to the target gateway, forwards the inner packet bytes, + /// receives the response, and returns it. Used for telescoping (hiding client IP). + /// + /// # Arguments + /// * `forward_data` - ForwardPacketData containing target gateway info and inner packet + /// + /// # Returns + /// * `Ok(Vec)` - Raw response bytes from target gateway + /// * `Err(LpHandlerError)` - If forwarding fails + /// + /// AIDEV-NOTE: Persistent exit stream forwarding + /// Uses self.exit_stream to maintain a persistent connection to the exit gateway. + /// First forward opens the connection, subsequent forwards reuse it. + /// Connection errors clear exit_stream, causing reconnection on next forward. + /// + /// Semaphore rationale: The forward_semaphore limits concurrent connection OPENS + /// (FD exhaustion protection), not concurrent operations. Since: + /// 1. Each LpConnectionHandler owns its exit_stream exclusively + /// 2. The handler loop processes packets sequentially (no concurrent access) + /// 3. Only connection opens consume new FDs + /// + /// The semaphore is only acquired when opening a new connection, not for reuse. + async fn handle_forward_packet( + &mut self, + forward_data: ForwardPacketData, + ) -> Result, LpHandlerError> { + inc!("lp_forward_total"); + let start = std::time::Instant::now(); + + // Parse target gateway address + 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 { + Some((_, existing_addr)) if *existing_addr == target_addr => false, + Some((_, existing_addr)) => { + // Target mismatch - this shouldn't happen in normal operation + // (client should only forward to one exit gateway) + // Return error to prevent silent behavior changes that could mask bugs + inc!("lp_forward_failed"); + return Err(LpHandlerError::other(format!( + "Forward target mismatch: session bound to {existing_addr}, got request for {target_addr}" + ))); + } + None => true, + }; + + if need_new_connection { + self.establish_exit_stream(target_addr).await?; + } + + // Get mutable reference to the exit stream + #[allow(clippy::unwrap_used)] + let (target_stream, _) = self.exit_stream.as_mut().unwrap(); + + debug!( + "Forwarding packet to {} ({} bytes)", + target_addr, + forward_data.inner_packet_bytes.len() + ); + + // Wrap all I/O in timeout to prevent hanging on unresponsive exit gateway + let io_timeout = Duration::from_secs(FORWARD_IO_TIMEOUT_SECS); + let inner_bytes = &forward_data.inner_packet_bytes; + + let io_result: Result, LpHandlerError> = timeout(io_timeout, async { + // Forward inner packet bytes. + // it's up to the client to ensure correct formatting, + // i.e. relevant headers or length-prefixes + target_stream.write_all_and_flush(inner_bytes).await?; + + // attempt to read response based on the provided information + + let response = match forward_data.expected_response_size { + ExpectedResponseSize::Handshake(size) => { + // client told us exactly how many bytes to expect + target_stream.read_n_bytes(size as usize).await? + } + ExpectedResponseSize::Transport => { + // transport packets are length-prefixed + target_stream + .receive_length_prefixed_transport_bytes() + .await? + } + }; + Ok(response) + }) + .await + .map_err(|_| LpHandlerError::ConnectionTimeout)?; + + // Handle result - clear exit_stream on any error + let response_buf = match io_result { + Ok(buf) => buf, + Err(e) => { + inc!("lp_forward_failed"); + self.exit_stream = None; + return Err(e); + } + }; + + // Record metrics + let duration = start.elapsed().as_secs_f64(); + add_histogram_obs!("lp_forward_duration_seconds", duration, LP_DURATION_BUCKETS); + + inc!("lp_forward_success"); + debug!( + "Forwarding successful to {} ({} bytes response, {:.3}s)", + target_addr, + response_buf.len(), + duration + ); + + Ok(response_buf) + } + + /// Receive raw packet bytes and parse outer header only (for routing before session lookup). + /// + /// Returns the raw packet bytes and parsed outer header (receiver_idx + counter). + /// The caller should look up the session to get outer_aead_key, then call + /// `parse_lp_packet()` with the key. + async fn receive_raw_packet(&mut self) -> Result { + let packet = self + .stream + .receive_length_prefixed_transport_packet() + .await?; + + // Track bytes sent (4 byte header + packet data) + self.stats + .record_bytes_received(4 + packet.encoded_length()); + + Ok(packet) + } + + /// Send a serialised LP packet over the stream with proper length-prefixed framing. + async fn send_serialised_packet( + &mut self, + packet: &EncryptedLpPacket, + ) -> Result<(), LpHandlerError> { + self.stream + .send_length_prefixed_transport_packet(packet) + .await?; + + // Track bytes sent (4 byte header + packet data) + self.stats.record_bytes_sent(4 + packet.encoded_length()); + + Ok(()) + } + + /// Emit connection lifecycle metrics + fn emit_lifecycle_metrics(&self, graceful: bool) { + use nym_metrics::inc_by; + + // Track connection duration + let duration = self.stats.start_time.elapsed().as_secs_f64(); + add_histogram_obs!( + "lp_connection_duration_seconds", + duration, + LP_CONNECTION_DURATION_BUCKETS + ); + + // Track bytes transferred + inc_by!( + "lp_connection_bytes_received_total", + self.stats.bytes_received as i64 + ); + inc_by!( + "lp_connection_bytes_sent_total", + self.stats.bytes_sent as i64 + ); + + // Track completion type + if graceful { + inc!("lp_connections_completed_gracefully"); + } else { + inc!("lp_connections_completed_with_error"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::lp::LpDebug; + use crate::node::lp::LpConfig; + use nym_lp::peer::{KEMKeys, LpLocalPeer, generate_keypair_mceliece, generate_keypair_mlkem}; + use nym_lp::{Ciphersuite, SessionManager, sessions_for_tests}; + use nym_test_utils::helpers::{deterministic_rng, deterministic_rng_09}; + use std::sync::Arc; + // ==================== Test Helpers ==================== + + /// Create a minimal test state for handler tests + async fn create_minimal_test_state() -> LpHandlerState { + use nym_crypto::asymmetric::ed25519; + + let mut rng = deterministic_rng(); + let mut rng09 = deterministic_rng_09(); + + let lp_config = LpConfig { + debug: LpDebug { + ..Default::default() + }, + ..Default::default() + }; + let forward_semaphore = Arc::new(tokio::sync::Semaphore::new( + lp_config.debug.max_concurrent_forwards, + )); + + // 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 rng)); + let x_keys = Arc::new(id_keys.to_x25519().try_into().unwrap()); + + let kem_keys = KEMKeys::new( + generate_keypair_mceliece(&mut rng09), + generate_keypair_mlkem(&mut rng09), + ); + let lp_peer = LpLocalPeer::new(Ciphersuite::default(), x_keys).with_kem_keys(kem_keys); + + LpHandlerState { + lp_config, + local_lp_peer: lp_peer, + metrics: nym_node_metrics::NymNodeMetrics::default(), + outbound_mix_sender: mix_sender, + session_states: Arc::new(dashmap::DashMap::new()), + peer_registrator: None, + forward_semaphore, + } + } + + // ==================== Existing Tests ==================== + + // ==================== Packet I/O Tests ==================== + + #[tokio::test] + async fn test_receive_raw_packet_valid() { + use tokio::net::{TcpListener, TcpStream}; + + let (init, resp) = sessions_for_tests(); + let mut init_sm = SessionManager::new(); + let mut resp_sm = SessionManager::new(); + resp_sm.create_session_state_machine(resp).unwrap(); + let id = init_sm.create_session_state_machine(init).unwrap(); + + // Bind to localhost + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + + // Spawn server task + let server_task = tokio::spawn(async move { + let (stream, remote_addr) = listener.accept().await.unwrap(); + let state = create_minimal_test_state().await; + let mut handler = LpConnectionHandler::new(stream, remote_addr, state); + // Two-phase: receive raw bytes + header, then parse full packet + let packet = handler.receive_raw_packet().await?; + let header = packet.outer_header(); + assert_eq!(packet.outer_header().receiver_idx, id); + let Some(LpAction::DeliverData(data)) = resp_sm.receive_packet(id, packet).unwrap() + else { + panic!("illegal state") + }; + Ok::<_, LpHandlerError>((header, data)) + }); + + // Connect as client + let mut client_stream = TcpStream::connect(addr).await.unwrap(); + + // Send a valid packet from client side + let LpAction::SendPacket(packet) = init_sm + .send_data(id, LpMessage::new_opaque(b"foomp".to_vec())) + .unwrap() + else { + panic!("illegal state") + }; + + client_stream + .send_length_prefixed_transport_packet(&packet) + .await + .unwrap(); + + // Handler should receive and parse it correctly + // Note: header is OuterHeader (receiver_idx + counter only), not LpHeader + let (header, received) = server_task.await.unwrap().unwrap(); + assert_eq!(header.receiver_idx, id); + assert_eq!(header.counter, 0); + assert_eq!(received.content.as_ref(), b"foomp"); + } + + #[tokio::test] + async fn test_send_lp_packet_valid() { + use tokio::net::{TcpListener, TcpStream}; + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + + let (init, resp) = sessions_for_tests(); + let mut init_sm = SessionManager::new(); + let mut resp_sm = SessionManager::new(); + resp_sm.create_session_state_machine(resp).unwrap(); + let id = init_sm.create_session_state_machine(init).unwrap(); + + let server_task = tokio::spawn(async move { + let (mut stream, _) = listener.accept().await.unwrap(); + + let LpAction::SendPacket(packet) = resp_sm + .send_data(id, LpMessage::new_opaque(b"foomp".to_vec())) + .unwrap() + else { + panic!("illegal state") + }; + + stream + .send_length_prefixed_transport_packet(&packet) + .await + .unwrap(); + }); + + let mut client_stream = TcpStream::connect(addr).await.unwrap(); + + // Wait for server to send + server_task.await.unwrap(); + + // Client should receive it correctly + let received = client_stream + .receive_length_prefixed_transport_packet() + .await + .unwrap(); + let header = received.outer_header(); + let Some(LpAction::DeliverData(data)) = init_sm.receive_packet(id, received).unwrap() + else { + panic!("illegal state") + }; + + assert_eq!(header.receiver_idx, id); + assert_eq!(header.counter, 0); + assert_eq!(data.content.as_ref(), b"foomp"); + } +} diff --git a/gateway/src/node/lp_listener/mod.rs b/nym-node/src/node/lp/mod.rs similarity index 64% rename from gateway/src/node/lp_listener/mod.rs rename to nym-node/src/node/lp/mod.rs index 976eb2afa7..78e1eaee80 100644 --- a/gateway/src/node/lp_listener/mod.rs +++ b/nym-node/src/node/lp/mod.rs @@ -67,209 +67,30 @@ // To view metrics, the nym-metrics registry automatically collects all metrics. // 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 crate::config::lp::LpConfig; +use crate::error::NymNodeError; use dashmap::DashMap; -use nym_config::serde_helpers::de_maybe_port; -use nym_credential_verification::ecash::traits::EcashManager; -use nym_gateway_storage::GatewayStorage; +use nym_gateway::node::wireguard::PeerRegistrator; +use nym_lp::peer::LpLocalPeer; +use nym_lp::peer_config::LpReceiverIndex; use nym_lp::state_machine::LpStateMachine; +use nym_mixnet_client::forwarder::MixForwardingSender; use nym_node_metrics::NymNodeMetrics; use nym_task::ShutdownTracker; -use std::net::{IpAddr, Ipv6Addr, SocketAddr}; +use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; use tokio::net::TcpListener; use tokio::sync::Semaphore; use tracing::*; -pub use nym_lp::peer::LpLocalPeer; -pub use nym_mixnet_client::forwarder::{ - mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender, -}; -pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData}; +pub use nym_mixnet_client::forwarder::{MixForwardingReceiver, mix_forwarding_channels}; mod data_handler; +pub mod error; pub mod handler; mod registration; -pub type ReceiverIndex = u32; - -/// Configuration for LP listener -#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)] -#[serde(default)] -pub struct LpConfig { - /// Bind address for the TCP LP control traffic. - /// default: `[::]:41264` - pub control_bind_address: SocketAddr, - - /// Bind address for the UDP LP data traffic. - /// default: `[::]:51264` - pub data_bind_address: SocketAddr, - - /// Custom announced port for listening for the TCP LP control traffic. - /// If unspecified, the value from the `control_bind_address` will be used instead - /// (default: None) - #[serde(deserialize_with = "de_maybe_port")] - 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 - /// (default: None) - #[serde(deserialize_with = "de_maybe_port")] - pub announce_data_port: Option, - - /// Auxiliary configuration - #[serde(default)] - pub debug: LpDebug, -} - -#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)] -#[serde(default)] -pub struct LpDebug { - /// Maximum concurrent connections - pub max_connections: usize, - - /// Maximum acceptable age of ClientHello timestamp in seconds (default: 30) - /// - /// ClientHello messages with timestamps older than this will be rejected - /// to prevent replay attacks. Value should be: - /// - Large enough to account for clock skew and network latency - /// - Small enough to limit replay attack window - /// - /// Recommended: 30-60 seconds - #[serde(with = "humantime_serde")] - pub timestamp_tolerance: Duration, - - /// Use mock ecash manager for testing (default: false) - /// - /// When enabled, the LP listener will use a mock ecash verifier that - /// accepts any credential without blockchain verification. This is - /// useful for testing the LP protocol implementation without requiring - /// a full blockchain/contract setup. - /// - /// WARNING: Only use this for local testing! Never enable in production. - pub use_mock_ecash: bool, - - /// Maximum age of in-progress handshakes before cleanup (default: 90s) - /// - /// Handshakes should complete quickly (3-5 packets). This TTL accounts for: - /// - Network latency and retransmits - /// - Slow clients - /// - Clock skew tolerance - /// - /// Stale handshakes are removed by the cleanup task to prevent memory leaks. - #[serde(with = "humantime_serde")] - pub handshake_ttl: Duration, - - /// Maximum age of established sessions before cleanup (default: 24h) - /// - /// Sessions can be long-lived for dVPN tunnels. This TTL should be set - /// high enough to accommodate expected usage patterns: - /// - dVPN sessions: hours to days - /// - Registration: minutes - /// - /// Sessions with no activity for this duration are removed by the cleanup task. - #[serde(with = "humantime_serde")] - pub session_ttl: Duration, - - /// Maximum age of demoted (read-only) sessions before cleanup (default: 60s) - /// - /// After subsession promotion, old sessions enter ReadOnlyTransport state. - /// They only need to stay alive briefly to drain in-flight packets. - /// This shorter TTL prevents memory buildup from frequent rekeying. - #[serde(with = "humantime_serde")] - pub demoted_session_ttl: Duration, - - /// How often to run the state cleanup task (default: 5 minutes) - /// - /// The cleanup task scans for and removes stale handshakes and sessions. - /// Lower values = more frequent cleanup but higher overhead. - /// Higher values = less overhead but slower memory reclamation. - #[serde(with = "humantime_serde")] - pub state_cleanup_interval: Duration, - - /// Maximum concurrent forward connections (default: 1000) - /// - /// Limits simultaneous outbound connections when forwarding LP packets to other gateways - /// during telescope setup. This prevents file descriptor exhaustion under high load. - /// - /// When at capacity, new forward requests return an error, signaling the client - /// to choose a different gateway. - pub max_concurrent_forwards: usize, -} - -impl LpConfig { - pub const DEFAULT_CONTROL_PORT: u16 = 41264; - pub const DEFAULT_DATA_PORT: u16 = 51264; - - pub fn announced_control_port(&self) -> u16 { - self.announce_control_port - .unwrap_or(self.control_bind_address.port()) - } - - pub fn announced_data_port(&self) -> u16 { - self.announce_data_port - .unwrap_or(self.data_bind_address.port()) - } -} - -impl Default for LpConfig { - fn default() -> Self { - LpConfig { - control_bind_address: SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - Self::DEFAULT_CONTROL_PORT, - ), - data_bind_address: SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - Self::DEFAULT_DATA_PORT, - ), - announce_control_port: None, - announce_data_port: None, - debug: Default::default(), - } - } -} - -impl LpDebug { - pub const DEFAULT_MAX_CONNECTIONS: usize = 10000; - - // 30 seconds - balances security vs clock skew tolerance - pub const DEFAULT_TIMESTAMP_TOLERANCE: Duration = Duration::from_secs(30); - - // 90 seconds - handshakes should complete quickly - pub const DEFAULT_HANDSHAKE_TTL: Duration = Duration::from_secs(90); - - // 24 hours - for long-lived dVPN sessions - pub const DEFAULT_SESSION_TTL: Duration = Duration::from_secs(86400); - - // 1 minute - enough to drain in-flight packets after subsession promotion - pub const DEFAULT_DEMOTED_SESSION_TTL: Duration = Duration::from_secs(60); - - // 5 minutes - balances memory reclamation with task overhead - pub const DEFAULT_STATE_CLEANUP_INTERVAL: Duration = Duration::from_secs(300); - - // Limits concurrent outbound connections to prevent fd exhaustion - pub const DEFAULT_MAX_CONCURRENT_FORWARDS: usize = 1000; -} - -impl Default for LpDebug { - fn default() -> Self { - LpDebug { - max_connections: Self::DEFAULT_MAX_CONNECTIONS, - timestamp_tolerance: Self::DEFAULT_TIMESTAMP_TOLERANCE, - use_mock_ecash: false, - handshake_ttl: Self::DEFAULT_HANDSHAKE_TTL, - session_ttl: Self::DEFAULT_SESSION_TTL, - demoted_session_ttl: Self::DEFAULT_DEMOTED_SESSION_TTL, - state_cleanup_interval: Self::DEFAULT_STATE_CLEANUP_INTERVAL, - max_concurrent_forwards: Self::DEFAULT_MAX_CONCURRENT_FORWARDS, - } - } -} - /// Wrapper for state entries with timestamp tracking for cleanup /// /// This wrapper adds `created_at` and `last_activity` timestamps to state entries, @@ -315,6 +136,7 @@ impl TimestampedState { } /// Get age since creation + #[allow(dead_code)] pub fn age(&self) -> Duration { self.created_at.elapsed() } @@ -335,21 +157,12 @@ impl TimestampedState { /// Shared state for LP connection handlers #[derive(Clone)] pub struct LpHandlerState { - /// Ecash verifier for bandwidth credentials - pub ecash_verifier: Arc, - - /// Storage backend for persistence - pub storage: GatewayStorage, - /// Encapsulates all required key information of a local Lewes Protocol Peer. pub local_lp_peer: LpLocalPeer, /// Metrics collection pub metrics: NymNodeMetrics, - /// Active clients tracking - pub active_clients_store: ActiveClientsStore, - /// Handle registering new wireguard peers pub peer_registrator: Option, @@ -360,6 +173,7 @@ pub struct LpHandlerState { /// /// Used by the LP data handler (UDP:51264) to forward decrypted Sphinx packets /// from LP clients into the mixnet for routing. + #[allow(dead_code)] pub outbound_mix_sender: MixForwardingSender, /// Established sessions keyed by session_id @@ -374,7 +188,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 /// @@ -423,16 +237,18 @@ impl LpListener { self.handler_state.lp_config } - pub async fn run(&mut self) -> Result<(), GatewayError> { + pub async fn run(&mut self) -> Result<(), NymNodeError> { let control_bind_address = self.lp_config().control_bind_address; let data_bind_address = self.lp_config().data_bind_address; - let listener = TcpListener::bind(control_bind_address).await.map_err(|e| { - error!("Failed to bind LP listener to {control_bind_address}: {e}",); - GatewayError::ListenerBindFailure { - address: control_bind_address.to_string(), - source: Box::new(e), - } - })?; + let listener = TcpListener::bind(control_bind_address) + .await + .map_err(|source| { + error!("Failed to bind LP listener to {control_bind_address}: {source}",); + NymNodeError::LpBindFailure { + address: control_bind_address, + source, + } + })?; let shutdown_token = self.shutdown.clone_shutdown_token(); @@ -449,7 +265,6 @@ impl LpListener { loop { tokio::select! { biased; - _ = shutdown_token.cancelled() => { trace!("LP listener: received shutdown signal"); break; @@ -457,12 +272,8 @@ impl LpListener { result = listener.accept() => { match result { - Ok((stream, addr)) => { - self.handle_connection(stream, addr); - } - Err(e) => { - warn!("Failed to accept LP connection: {}", e); - } + Ok((stream, addr)) => self.handle_connection(stream, addr), + Err(e) => warn!("Failed to accept LP connection: {e}") } } } @@ -520,7 +331,7 @@ impl LpListener { /// The data handler listens on UDP port 51264 and processes LP-wrapped Sphinx packets /// from registered clients. It decrypts the LP layer and forwards the Sphinx packets /// into the mixnet. - async fn spawn_data_handler(&self) -> Result, GatewayError> { + async fn spawn_data_handler(&self) -> Result, NymNodeError> { // Create data handler let data_handler = data_handler::LpDataHandler::new( self.lp_config().data_bind_address, @@ -555,14 +366,15 @@ impl LpListener { let handshake_ttl = dbg_cfg.handshake_ttl; let session_ttl = dbg_cfg.session_ttl; - let demoted_session_ttl = dbg_cfg.demoted_session_ttl; let interval = dbg_cfg.state_cleanup_interval; let shutdown = self.shutdown.clone_shutdown_token(); let metrics = self.handler_state.metrics.clone(); info!( - "Starting LP state cleanup task (handshake_ttl={}s, session_ttl={}s, demoted_ttl={}s, interval={}s)", - handshake_ttl.as_secs(), session_ttl.as_secs(), demoted_session_ttl.as_secs(), interval.as_secs() + "Starting LP state cleanup task (handshake_ttl={}s, session_ttl={}s, interval={}s)", + handshake_ttl.as_secs(), + session_ttl.as_secs(), + interval.as_secs() ); self.shutdown.try_spawn_named( @@ -580,9 +392,9 @@ impl LpListener { } pub(crate) mod cleanup_task { - use crate::node::lp_listener::{LpDebug, TimestampedState}; + use crate::config::lp::LpDebug; + use crate::node::lp::{LpReceiverIndex, TimestampedState}; use dashmap::DashMap; - use nym_lp::state_machine::LpStateBare; use nym_lp::LpStateMachine; use nym_metrics::inc_by; use nym_node_metrics::NymNodeMetrics; @@ -590,42 +402,29 @@ pub(crate) mod cleanup_task { use tracing::{debug, info}; async fn perform_cleanup( - session_states: &Arc>>, + session_states: &Arc>>, cfg: LpDebug, ) { let session_ttl = cfg.session_ttl; - let demoted_session_ttl = cfg.demoted_session_ttl; let start = std::time::Instant::now(); let mut ss_removed = 0u64; - let mut demoted_removed = 0u64; // 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; - } + if timestamped.since_activity() > session_ttl { + ss_removed += 1; false } else { true } }); - if ss_removed > 0 || demoted_removed > 0 { + if ss_removed > 0 { let duration = start.elapsed(); info!( - "LP state cleanup: {ss_removed} sessions, {demoted_removed} demoted (took {:.3}s)", + "LP state cleanup: {ss_removed} sessions (took {:.3}s)", duration.as_secs_f64() ); @@ -633,9 +432,6 @@ pub(crate) mod cleanup_task { 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); - } } } @@ -647,7 +443,7 @@ pub(crate) mod cleanup_task { /// Demoted sessions (ReadOnlyTransport) use shorter TTL since they /// only need to drain in-flight packets after subsession promotion. pub(crate) async fn cleanup_loop( - session_states: Arc>>, + session_states: Arc>>, cfg: LpDebug, shutdown: nym_task::ShutdownToken, _metrics: NymNodeMetrics, diff --git a/gateway/src/node/lp_listener/registration.rs b/nym-node/src/node/lp/registration.rs similarity index 96% rename from gateway/src/node/lp_listener/registration.rs rename to nym-node/src/node/lp/registration.rs index 7f2e7b875c..52b6975c5b 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/nym-node/src/node/lp/registration.rs @@ -1,7 +1,7 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::lp_listener::{LpHandlerState, ReceiverIndex}; +use crate::node::lp::{LpHandlerState, LpReceiverIndex}; use nym_metrics::{add_histogram_obs, inc}; use nym_registration_common::dvpn::{ LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest, @@ -31,7 +31,7 @@ const LP_REGISTRATION_DURATION_BUCKETS: &[f64] = &[ impl LpHandlerState { async fn process_dvpn_initial_registration( &self, - sender: ReceiverIndex, + sender: LpReceiverIndex, request: LpDvpnRegistrationInitialRequest, ) -> LpRegistrationResponse { let Some(registrator) = self.peer_registrator.as_ref() else { @@ -54,7 +54,7 @@ impl LpHandlerState { async fn process_dvpn_registration_finalisation( &self, - sender: ReceiverIndex, + sender: LpReceiverIndex, request: LpDvpnRegistrationFinalisation, ) -> LpRegistrationResponse { let Some(registrator) = self.peer_registrator.as_ref() else { @@ -77,7 +77,7 @@ impl LpHandlerState { async fn process_dvpn_registration( &self, - sender: ReceiverIndex, + sender: LpReceiverIndex, request: Box, ) -> LpRegistrationResponse { // Track dVPN registration attempts @@ -108,7 +108,7 @@ impl LpHandlerState { /// Process an LP registration request pub async fn process_registration( &self, - sender: ReceiverIndex, + sender: LpReceiverIndex, request: LpRegistrationRequest, ) -> LpRegistrationResponse { let registration_start = std::time::Instant::now(); diff --git a/nym-node/src/node/mixnet/handler.rs b/nym-node/src/node/mixnet/handler.rs index a2a50aa4f2..4b193bfe87 100644 --- a/nym-node/src/node/mixnet/handler.rs +++ b/nym-node/src/node/mixnet/handler.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::node::key_rotation::active_keys::SphinxKeyGuard; use crate::node::mixnet::shared::SharedData; use futures::StreamExt; use nym_noise::connection::Connection; @@ -20,7 +21,10 @@ use std::net::SocketAddr; use tokio::net::TcpStream; use tokio::time::Instant; use tokio_util::codec::Framed; -use tracing::{debug, error, instrument, trace, warn}; +use tracing::{Span, debug, error, instrument, trace, warn}; + +/// How often (in packets) the stream-level span updates its packet count. +const SPAN_UPDATE_INTERVAL: u64 = 10_000; struct PendingReplayCheckPackets { // map of rotation id used for packet creation to the packets @@ -51,6 +55,10 @@ impl PendingReplayCheckPackets { .push(packet.packet) } + fn total_count(&self) -> usize { + self.packets.values().map(|v| v.len()).sum() + } + fn replay_tags(&self) -> HashMap> { let mut replay_tags = HashMap::with_capacity(self.packets.len()); 'outer: for (rotation_id, packets) in &self.packets { @@ -130,20 +138,54 @@ impl ConnectionHandler { Some(now + delay) } + #[instrument( + name = "mixnode.forward_packet", + skip(self, mix_packet, delay), + level = "debug", + fields( + remote_addr = %self.remote_address, + delay_ms = tracing::field::Empty, + ) + )] fn handle_forward_packet(&self, now: Instant, mix_packet: MixPacket, delay: Option) { if !self.shared.processing_config.forward_hop_processing_enabled { - trace!("this nym-node does not support forward hop packets"); + warn!( + event = "packet.dropped.forward_disabled", + remote_addr = %self.remote_address, + "dropping packet: forward hop processing disabled" + ); self.shared.dropped_forward_packet(self.remote_address.ip()); return; } let forward_instant = self.create_delay_target(now, delay); + if let Some(target) = forward_instant { + Span::current().record( + "delay_ms", + target.saturating_duration_since(now).as_millis() as u64, + ); + } self.shared.forward_mix_packet(mix_packet, forward_instant); } + #[instrument( + name = "mixnode.final_hop", + skip(self, final_hop_data), + level = "debug", + fields( + remote_addr = %self.remote_address, + client_online, + disk_fallback = false, + ack_forwarded = false, + ) + )] async fn handle_final_hop(&self, final_hop_data: ProcessedFinalHop) { if !self.shared.processing_config.final_hop_processing_enabled { - trace!("this nym-node does not support final hop packets"); + warn!( + event = "packet.dropped.final_hop_disabled", + remote_addr = %self.remote_address, + "dropping packet: final hop processing disabled" + ); self.shared .dropped_final_hop_packet(self.remote_address.ip()); return; @@ -151,11 +193,13 @@ impl ConnectionHandler { let client = final_hop_data.destination; let message = final_hop_data.message; + let has_ack = final_hop_data.forward_ack.is_some(); // if possible attempt to push message directly to the client match self.shared.try_push_message_to_client(client, message) { Err(unsent_plaintext) => { - // if that failed, store it on disk (to be 🔥 soon...) + // if that failed, store it on disk + Span::current().record("client_online", false); match self .shared .store_processed_packet_payload(client, unsent_plaintext) @@ -163,6 +207,7 @@ impl ConnectionHandler { { Err(err) => error!("Failed to store client data - {err}"), Ok(_) => { + Span::current().record("disk_fallback", true); self.shared .metrics .mixnet @@ -172,13 +217,18 @@ impl ConnectionHandler { } } } - Ok(_) => trace!("Pushed received packet to {client}"), + Ok(_) => { + Span::current().record("client_online", true); + trace!("Pushed received packet to {client}"); + } } // if we managed to either push message directly to the [online] client or store it at - // its inbox, it means that it must exist at this gateway, hence we can send the - // received ack back into the network + // disk, forward the ack self.shared.forward_ack_packet(final_hop_data.forward_ack); + if has_ack { + Span::current().record("ack_forwarded", true); + } } fn within_deferral_threshold(&self, now: Instant) -> bool { @@ -206,32 +256,86 @@ impl ConnectionHandler { if !time_threshold { warn!( - "{}: time failure - {}", + event = "replay_detection.deferral_exceeded", + threshold_type = "time", + deferred_count = self.pending_packets.total_count(), + deferral_ms = now.saturating_duration_since(self.pending_packets.last_acquired_mutex).as_millis() as u64, + remote_addr = %self.remote_address, + "{}: time deferral threshold exceeded with {} pending packets", self.remote_address, - self.pending_packets.packets.len() + self.pending_packets.total_count() ) } if !count_threshold { - warn!("{}, count failure", self.remote_address) + warn!( + event = "replay_detection.deferral_exceeded", + threshold_type = "count", + deferred_count = self.pending_packets.total_count(), + remote_addr = %self.remote_address, + "{}: count deferral threshold exceeded", + self.remote_address + ) } time_threshold && count_threshold } + /// Resolve the sphinx key for the given rotation, recording the rotation + /// label on the current tracing span. Returns `ExpiredKey` if the requested + /// odd/even key has already been rotated out. + fn resolve_rotation_key( + &self, + rotation: SphinxKeyRotation, + ) -> Result { + let rotation_label = match rotation { + SphinxKeyRotation::Unknown => "unknown", + SphinxKeyRotation::OddRotation => "odd", + SphinxKeyRotation::EvenRotation => "even", + }; + Span::current().record("key_rotation", rotation_label); + + match rotation { + SphinxKeyRotation::Unknown => Ok(self.shared.sphinx_keys.primary()), + SphinxKeyRotation::OddRotation => self.shared.sphinx_keys.odd().ok_or_else(|| { + warn!( + event = "packet.dropped.expired_key", + key_rotation = "odd", + remote_addr = %self.remote_address, + "dropping packet: odd key rotation expired" + ); + PacketProcessingError::ExpiredKey + }), + SphinxKeyRotation::EvenRotation => self.shared.sphinx_keys.even().ok_or_else(|| { + warn!( + event = "packet.dropped.expired_key", + key_rotation = "even", + remote_addr = %self.remote_address, + "dropping packet: even key rotation expired" + ); + PacketProcessingError::ExpiredKey + }), + } + } + + #[instrument( + name = "mixnode.sphinx_partial_unwrap", + skip(self, packet), + level = "debug", + fields(key_rotation, unwrap_result,) + )] fn try_partially_unwrap_packet( &self, packet: FramedNymPacket, ) -> Result { - // based on the received sphinx key rotation information, - // attempt to choose appropriate key for processing the packet - match packet.header().key_rotation { + let rotation = packet.header().key_rotation; + + let result = match rotation { SphinxKeyRotation::Unknown => { - let primary = self.shared.sphinx_keys.primary(); + // Unknown rotation: try primary, fallback to secondary + let primary = self.resolve_rotation_key(rotation)?; let primary_rotation = primary.rotation_id(); - // we have to try both keys, start with the primary as it has higher likelihood of being correct - // if let Ok(partially_unwrapped) = PartiallyUnwrappedPacket::new() match PartiallyUnwrappedPacket::new(packet, primary.inner().as_ref()) { Ok(unwrapped_packet) => { Ok(unwrapped_packet.with_key_rotation(primary_rotation)) @@ -248,25 +352,17 @@ impl ConnectionHandler { } } } - SphinxKeyRotation::OddRotation => { - let Some(odd_key) = self.shared.sphinx_keys.odd() else { - return Err(PacketProcessingError::ExpiredKey); - }; - let odd_rotation = odd_key.rotation_id(); - PartiallyUnwrappedPacket::new(packet, odd_key.inner().as_ref()) + _ => { + let key = self.resolve_rotation_key(rotation)?; + let rotation_id = key.rotation_id(); + PartiallyUnwrappedPacket::new(packet, key.inner().as_ref()) .map_err(|(_, err)| err) - .map(|p| p.with_key_rotation(odd_rotation)) + .map(|p| p.with_key_rotation(rotation_id)) } - SphinxKeyRotation::EvenRotation => { - let Some(even_key) = self.shared.sphinx_keys.even() else { - return Err(PacketProcessingError::ExpiredKey); - }; - let even_rotation = even_key.rotation_id(); - PartiallyUnwrappedPacket::new(packet, even_key.inner().as_ref()) - .map_err(|(_, err)| err) - .map(|p| p.with_key_rotation(even_rotation)) - } - } + }; + + Span::current().record("unwrap_result", if result.is_ok() { "ok" } else { "err" }); + result } async fn handle_received_packet_with_replay_detection( @@ -280,6 +376,12 @@ impl ConnectionHandler { Ok(unwrapped) => unwrapped, Err(err) => { trace!("failed to process received mix packet: {err}"); + warn!( + event = "packet.dropped.malformed", + error = %err, + remote_addr = %self.remote_address, + "dropping malformed packet" + ); self.shared .metrics .mixnet @@ -316,7 +418,9 @@ impl ConnectionHandler { // 3. forward the packet to the relevant sink (if enabled) match unwrapped_packet { - Err(err) => trace!("failed to process received mix packet: {err}"), + Err(err) => { + trace!("failed to process received mix packet: {err}"); + } Ok(processed_packet) => match processed_packet.processing_data { MixProcessingResultData::ForwardHop { packet, delay } => { self.handle_forward_packet(now, packet, delay); @@ -334,6 +438,7 @@ impl ConnectionHandler { packets: HashMap>, replay_check_results: HashMap>, ) { + let mut replays_detected: u64 = 0; for (rotation_id, packets) in packets { let Some(replay_checks) = replay_check_results.get(&rotation_id) else { // this should never happen, but if we messed up, and it does, don't panic, just drop the packets @@ -342,6 +447,13 @@ impl ConnectionHandler { }; for (packet, &replayed) in packets.into_iter().zip(replay_checks) { let unwrapped_packet = if replayed { + replays_detected += 1; + warn!( + event = "packet.dropped.replay", + remote_addr = %self.remote_address, + rotation_id, + "dropping replayed packet" + ); Err(PacketProcessingError::PacketReplay) } else { packet.finalise_unwrapping() @@ -350,6 +462,13 @@ impl ConnectionHandler { self.handle_unwrapped_packet(now, unwrapped_packet).await; } } + if replays_detected > 0 { + debug!( + replays_detected, + remote_addr = %self.remote_address, + "replay detection batch completed with replays" + ); + } } async fn handle_pending_packets_batch_no_locking(&mut self, now: Instant) -> bool { @@ -379,13 +498,22 @@ impl ConnectionHandler { true } + #[instrument( + name = "mixnode.replay_check_batch", + skip(self), + level = "debug", + fields(batch_size, mutex_wait_ms,) + )] async fn handle_pending_packets_batch(&mut self, now: Instant) { - let batch = self.pending_packets.reset(now); let replay_tags = self.pending_packets.replay_tags(); if replay_tags.is_empty() { return; } + let batch_size = self.pending_packets.total_count(); + Span::current().record("batch_size", batch_size as u64); + + let mutex_start = Instant::now(); let Ok(replay_check_results) = self .shared .replay_protection_filter @@ -396,37 +524,25 @@ impl ConnectionHandler { self.shared.shutdown_token.cancel(); return; }; + Span::current().record("mutex_wait_ms", mutex_start.elapsed().as_millis() as u64); + let batch = self.pending_packets.reset(now); self.handle_post_replay_detection_packets(now, batch, replay_check_results) .await; } + #[instrument( + name = "mixnode.sphinx_full_unwrap", + skip(self, packet), + level = "debug", + fields(key_rotation) + )] fn try_full_unwrap_packet( &self, packet: FramedNymPacket, ) -> Result { - // based on the received sphinx key rotation information, - // attempt to choose appropriate key for processing the packet - // NOTE: due to the function signatures, outfox packets will **only** attempt primary key - // if no rotation information is available (but that's fine given outfox is not really in use, - // and by the time we need it, the rotation info should be present) - match packet.header().key_rotation { - SphinxKeyRotation::Unknown => { - process_framed_packet(packet, self.shared.sphinx_keys.primary().inner().as_ref()) - } - SphinxKeyRotation::OddRotation => { - let Some(odd_key) = self.shared.sphinx_keys.odd() else { - return Err(PacketProcessingError::ExpiredKey); - }; - process_framed_packet(packet, odd_key.inner().as_ref()) - } - SphinxKeyRotation::EvenRotation => { - let Some(even_key) = self.shared.sphinx_keys.even() else { - return Err(PacketProcessingError::ExpiredKey); - }; - process_framed_packet(packet, even_key.inner().as_ref()) - } - } + let key = self.resolve_rotation_key(packet.header().key_rotation)?; + process_framed_packet(packet, key.inner().as_ref()) } async fn handle_received_packet_with_no_replay_detection( @@ -456,23 +572,36 @@ impl ConnectionHandler { } #[instrument( - skip(self), + name = "mixnode.connection", + skip(self, socket), level = "debug", fields( - remote = %self.remote_address + remote = %self.remote_address, + noise_handshake_ms = tracing::field::Empty, ) )] pub(crate) async fn handle_connection(&mut self, socket: TcpStream) { + let handshake_start = Instant::now(); let noise_stream = match upgrade_noise_responder(socket, &self.shared.noise_config).await { Ok(noise_stream) => noise_stream, Err(err) => { - error!( - "Failed to perform Noise handshake with {:?} - {err}", - self.remote_address + Span::current().record( + "noise_handshake_ms", + handshake_start.elapsed().as_millis() as u64, + ); + warn!( + event = "connection.failed.noise", + remote_addr = %self.remote_address, + error = %err, + "Noise responder handshake failed" ); return; } }; + Span::current().record( + "noise_handshake_ms", + handshake_start.elapsed().as_millis() as u64, + ); debug!( "Noise responder handshake completed for {:?}", self.remote_address @@ -481,26 +610,58 @@ impl ConnectionHandler { .await } + #[instrument( + name = "mixnode.stream", + skip(self, mixnet_connection), + level = "debug", + fields( + remote = %self.remote_address, + packets_processed = 0u64, + exit_reason, + ) + )] pub(crate) async fn handle_stream( &mut self, mut mixnet_connection: Framed, NymCodec>, ) { + let mut packets_processed: u64 = 0; loop { tokio::select! { biased; _ = self.shared.shutdown_token.cancelled() => { trace!("connection handler: received shutdown"); + Span::current().record("exit_reason", "shutdown"); break } maybe_framed_nym_packet = mixnet_connection.next() => { match maybe_framed_nym_packet { - Some(Ok(packet)) => self.handle_received_nym_packet(packet).await, + Some(Ok(packet)) => { + self.handle_received_nym_packet(packet).await; + packets_processed += 1; + if packets_processed.is_multiple_of(SPAN_UPDATE_INTERVAL) { + Span::current().record("packets_processed", packets_processed); + } + } Some(Err(err)) => { - debug!("connection got corrupted with: {err}"); + warn!( + event = "connection.corrupted", + remote_addr = %self.remote_address, + error = %err, + packets_processed, + "connection stream corrupted" + ); + Span::current().record("exit_reason", "corrupted"); + Span::current().record("packets_processed", packets_processed); return } None => { - debug!("connection got closed by the remote"); + debug!( + remote_addr = %self.remote_address, + packets_processed, + "connection closed by remote" + ); + Span::current().record("exit_reason", "closed_by_remote"); + Span::current().record("packets_processed", packets_processed); return } } @@ -508,6 +669,7 @@ impl ConnectionHandler { } } + Span::current().record("packets_processed", packets_processed); debug!("exiting and closing connection"); } } diff --git a/nym-node/src/node/mixnet/packet_forwarding/mod.rs b/nym-node/src/node/mixnet/packet_forwarding/mod.rs index 09055daacd..a6a5b32d73 100644 --- a/nym-node/src/node/mixnet/packet_forwarding/mod.rs +++ b/nym-node/src/node/mixnet/packet_forwarding/mod.rs @@ -56,11 +56,17 @@ impl PacketForwarder { if let Err(err) = self.mixnet_client.send_without_response(packet) { if err.kind() == io::ErrorKind::WouldBlock { // we only know for sure if we dropped a packet if our sending queue was full - // in any other case the connection might still be re-established (or created for the first time) - // and the packet might get sent, but we won't know about it + warn!( + event = "packet.dropped.buffer_full", + next_hop = %next_hop, + "dropping packet: egress connection buffer full (WouldBlock)" + ); self.metrics.mixnet.egress_dropped_forward_packet(next_hop) } else if err.kind() == io::ErrorKind::NotConnected { - // let's give the benefit of the doubt and assume we manage to establish connection + debug!( + next_hop = %next_hop, + "packet queued for not-yet-connected peer" + ); self.metrics.mixnet.egress_sent_forward_packet(next_hop) } } else { @@ -86,7 +92,11 @@ impl PacketForwarder { let next_hop = new_packet.packet.next_hop(); if !self.routing_filter.should_route(next_hop.as_ref().ip()) { - debug!("dropping packet as the egress address does not belong to any known node"); + warn!( + event = "packet.dropped.routing_filter", + next_hop = %next_hop, + "dropping packet: egress address does not belong to any known node" + ); self.metrics .mixnet .egress_dropped_forward_packet(next_hop.into()); @@ -125,7 +135,7 @@ impl PacketForwarder { C: SendWithoutResponse, F: RoutingFilter, { - let mut processed = 0; + let mut processed: u64 = 0; trace!("starting PacketForwarder"); loop { tokio::select! { @@ -145,11 +155,29 @@ impl PacketForwarder { #[allow(clippy::unwrap_used)] self.handle_new_packet(new_packet.unwrap()); let channel_len = self.packet_sender.len(); - if processed % 1000 == 0 { + let delay_queue_len = self.delay_queue.len(); + if processed.is_multiple_of(1000) { match channel_len { - n if n > 1000 => error!("there are currently {n} mix packets waiting to get forwarded - the node seems to be significantly overloaded!"), - n if n > 500 => warn!("there are currently {n} mix packets waiting to get forwarded - is the node overloaded?"), - n => trace!("there are currently {n} mix packets waiting to get forwarded"), + n if n > 1000 => error!( + event = "forwarder.queue_overload", + channel_depth = n, + delay_queue_depth = delay_queue_len, + packets_processed = processed, + "there are currently {n} mix packets waiting to get forwarded - the node seems to be significantly overloaded!" + ), + n if n > 500 => warn!( + event = "forwarder.queue_high", + channel_depth = n, + delay_queue_depth = delay_queue_len, + packets_processed = processed, + "there are currently {n} mix packets waiting to get forwarded - is the node overloaded?" + ), + n => trace!( + channel_depth = n, + delay_queue_depth = delay_queue_len, + packets_processed = processed, + "forwarder queue status" + ), } } self.update_channel_size_metric(channel_len); diff --git a/nym-node/src/node/mixnet/shared/final_hop.rs b/nym-node/src/node/mixnet/shared/final_hop.rs index cb6bc0ea64..2a9d102e51 100644 --- a/nym-node/src/node/mixnet/shared/final_hop.rs +++ b/nym-node/src/node/mixnet/shared/final_hop.rs @@ -5,7 +5,8 @@ use nym_gateway::node::{ ActiveClientsStore, GatewayStorage, GatewayStorageError, InboxGatewayStorage, }; use nym_sphinx_types::DestinationAddressBytes; -use tracing::debug; +use tokio::time::Instant; +use tracing::{debug, warn}; #[derive(Clone)] pub(crate) struct SharedFinalHopData { @@ -27,14 +28,37 @@ impl SharedFinalHopData { message: Vec, ) -> Result<(), Vec> { match self.active_clients.get_sender(client_address) { - None => Err(message), + None => { + debug!( + event = "gateway.push_to_client", + client_found = false, + send_result = "client_not_found", + "client {client_address} not found in active clients" + ); + Err(message) + } Some(sender_channel) => { + let send_start = Instant::now(); if let Err(unsent) = sender_channel.unbounded_send(vec![message]) { + warn!( + event = "gateway.push_to_client", + client_found = true, + send_result = "channel_closed", + send_us = send_start.elapsed().as_micros() as u64, + "client {client_address} channel closed, message not delivered" + ); // the unwrap here is fine as the original message got returned; // plus we're only ever sending 1 message at the time (for now) #[allow(clippy::unwrap_used)] Err(unsent.into_inner().pop().unwrap()) } else { + debug!( + event = "gateway.push_to_client", + client_found = true, + send_result = "ok", + send_us = send_start.elapsed().as_micros() as u64, + "pushed message to client {client_address}" + ); Ok(()) } } @@ -46,8 +70,21 @@ impl SharedFinalHopData { client_address: DestinationAddressBytes, message: Vec, ) -> Result<(), GatewayStorageError> { + let start = Instant::now(); debug!("Storing received message for {client_address} on the disk...",); - - self.storage.store_message(client_address, message).await + let result = self.storage.store_message(client_address, message).await; + let store_us = start.elapsed().as_micros() as u64; + if result.is_ok() { + debug!( + event = "gateway.disk_store", + store_us, "stored message for {client_address} on disk in {store_us}us" + ); + } else { + warn!( + event = "gateway.disk_store_failed", + store_us, "failed to store message for {client_address} on disk after {store_us}us" + ); + } + result } } diff --git a/nym-node/src/node/mixnet/shared/mod.rs b/nym-node/src/node/mixnet/shared/mod.rs index ac8dd9ecc7..19b15d9116 100644 --- a/nym-node/src/node/mixnet/shared/mod.rs +++ b/nym-node/src/node/mixnet/shared/mod.rs @@ -185,6 +185,7 @@ impl SharedData { } pub(super) fn forward_mix_packet(&self, packet: MixPacket, delay_until: Option) { + let has_delay = delay_until.is_some(); if self .mixnet_forwarder .forward_packet(PacketToForward::new(packet, delay_until)) @@ -192,6 +193,8 @@ impl SharedData { && !self.shutdown_token.is_cancelled() { error!( + event = "forwarder.channel_send_failed", + has_delay, "failed to forward sphinx packet on the channel while the process is not going through the shutdown!" ); self.shutdown_token.cancel(); diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 460889d3db..59213d0a3b 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -10,7 +10,9 @@ use crate::error::{EntryGatewayError, NymNodeError, ServiceProvidersError}; use crate::node::description::{load_node_description, save_node_description}; use crate::node::helpers::{ DisplayDetails, get_current_rotation_id, load_ed25519_identity_keypair, load_key, + load_mceliece_keypair, load_mlkem768_keypair, load_x25519_lp_keypair, load_x25519_noise_keypair, store_ed25519_identity_keypair, store_key, store_keypair, + store_mceliece_keypair, store_mlkem768_keypair, store_x25519_lp_keypair, store_x25519_noise_keypair, }; use crate::node::http::api::api_requests; @@ -20,6 +22,7 @@ use crate::node::http::{HttpServerConfig, NymNodeHttpServer, NymNodeRouter}; use crate::node::key_rotation::active_keys::ActiveSphinxKeys; use crate::node::key_rotation::controller::KeyRotationController; use crate::node::key_rotation::manager::SphinxKeyManager; +use crate::node::lp::{LpHandlerState, LpListener}; use crate::node::metrics::aggregator::MetricsAggregator; use crate::node::metrics::console_logger::ConsoleLogger; use crate::node::metrics::handler::client_sessions::GatewaySessionStatsHandler; @@ -41,7 +44,14 @@ use crate::node::shared_network::{ use nym_bin_common::bin_info; use nym_credential_verification::UpgradeModeState; use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_gateway::node::{ActiveClientsStore, GatewayTasksBuilder, UpgradeModeCheckRequestSender}; +use nym_gateway::node::wireguard::PeerRegistrator; +use nym_gateway::node::{GatewayTasksBuilder, UpgradeModeCheckRequestSender}; +use nym_kkt::key_utils::{ + generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519, +}; +use nym_kkt::keys::{DHKeyPair, KEMKeys}; +use nym_lp::Ciphersuite; +use nym_lp::peer::LpLocalPeer; use nym_mixnet_client::client::ActiveConnections; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_requester::{ @@ -50,6 +60,8 @@ use nym_network_requester::{ }; use nym_node_metrics::NymNodeMetrics; use nym_node_metrics::events::MetricEventsSender; +use nym_node_requests::api::SignedData; +use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM, LewesProtocol}; use nym_node_requests::api::v1::node::models::{AnnouncePorts, NodeDescription}; use nym_noise::config::{NoiseConfig, NoiseNetworkView}; use nym_noise_keys::VersionedNoiseKeyV1; @@ -62,19 +74,25 @@ use nym_verloc::{self, measurements::VerlocMeasurer}; use nym_wireguard::{WireguardGatewayData, peer_controller::PeerControlRequest}; use rand::rngs::OsRng; use rand::{CryptoRng, RngCore}; +use rand09::SeedableRng; +use std::collections::BTreeMap; use std::net::SocketAddr; use std::ops::Deref; use std::path::Path; use std::sync::Arc; -use tokio::sync::mpsc; +use tokio::sync::{Semaphore, mpsc}; use tracing::{debug, info, trace}; use zeroize::Zeroizing; +pub use nym_gateway::node::ActiveClientsStore; +pub use nym_gateway::node::GatewayStorage; + pub mod bonding_information; pub mod description; pub mod helpers; pub(crate) mod http; pub(crate) mod key_rotation; +pub mod lp; pub(crate) mod metrics; pub(crate) mod mixnet; mod nym_apis_client; @@ -84,8 +102,7 @@ mod shared_network; pub struct GatewayTasksData { mnemonic: Arc>, - psq_kem_key: Arc, - client_storage: nym_gateway::node::GatewayStorage, + client_storage: GatewayStorage, stats_storage: nym_gateway::node::PersistentStatsStorage, } @@ -106,12 +123,8 @@ impl GatewayTasksData { Ok(()) } - 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( + async fn new(config: &GatewayTasksConfig) -> Result { + let client_storage = GatewayStorage::init( &config.storage_paths.clients_storage, config.debug.message_retrieval_limit, ) @@ -125,7 +138,6 @@ 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, }) @@ -373,7 +385,7 @@ impl From for nym_wireguard::WireguardData { } } -pub(crate) struct NymNode { +pub struct NymNode { config: Config, accepted_operator_terms_and_conditions: bool, shutdown_manager: ShutdownManager, @@ -396,8 +408,10 @@ pub(crate) struct NymNode { ed25519_identity_keys: Arc, sphinx_key_manager: Option, - // to be used when noise is integrated x25519_noise_keys: Arc, + + psq_kem_keys: KEMKeys, + x25519_lp_keys: Arc, } impl NymNode { @@ -405,12 +419,19 @@ impl NymNode { config: &Config, custom_mnemonic: Option>, ) -> Result<(), NymNodeError> { - debug!("initialising nym-node with id: {}", config.id); + info!("initialising nym-node with id: {}", config.id); let mut rng = OsRng; + let mut rng09 = rand09::rngs::StdRng::from_os_rng(); // global initialisation + info!("generating new node keys (this might take a while)"); let ed25519_identity_keys = ed25519::KeyPair::new(&mut rng); let x25519_noise_keys = x25519::KeyPair::new(&mut rng); + + let x25519_lp_keys = generate_lp_keypair_x25519(&mut rng09); + let mlkem = generate_keypair_mlkem(&mut rng09); + let mceliece = generate_keypair_mceliece(&mut rng09); + let current_rotation_id = get_current_rotation_id(&config.mixnet.nym_api_urls, &config.mixnet.nyxd_urls).await?; let _ = SphinxKeyManager::initialise_new( @@ -432,6 +453,18 @@ impl NymNode { &config.storage_paths.keys.x25519_noise_storage_paths(), )?; + trace!("attempting to x25519 lp keypair"); + store_x25519_lp_keypair( + &x25519_lp_keys, + &config.storage_paths.keys.x25519_lp_key_paths(), + )?; + + trace!("attempting to mlkem768 keypair"); + store_mlkem768_keypair(&mlkem, &config.storage_paths.keys.mlkem768_key_paths())?; + + trace!("attempting to mceliece keypair"); + store_mceliece_keypair(&mceliece, &config.storage_paths.keys.mceliece_key_paths())?; + trace!("creating description file"); save_node_description( &config.storage_paths.description, @@ -454,6 +487,30 @@ impl NymNode { config.save() } + pub async fn build_lp_listener( + &mut self, + peer_registrator: Option, + mix_packet_sender: MixForwardingSender, + ) -> Result { + let handler_state = LpHandlerState { + local_lp_peer: LpLocalPeer::new(Ciphersuite::default(), self.x25519_lp_keys.clone()) + .with_kem_keys(self.psq_kem_keys.clone()), + metrics: self.metrics.clone(), + peer_registrator, + lp_config: self.config.lp, + outbound_mix_sender: mix_packet_sender, + session_states: Arc::new(dashmap::DashMap::new()), + forward_semaphore: Arc::new(Semaphore::new( + self.config.lp.debug.max_concurrent_forwards, + )), + }; + + Ok(LpListener::new( + handler_state, + self.shutdown_manager.shutdown_tracker().clone(), + )) + } + pub(crate) async fn new(config: Config) -> Result { let wireguard_data = WireguardData::new(&config.wireguard)?; let current_rotation_id = @@ -462,8 +519,12 @@ impl NymNode { 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?; + let entry_gateway = GatewayTasksData::new(&config.gateway_tasks).await?; + let x25519_lp_keys = + load_x25519_lp_keypair(&config.storage_paths.keys.x25519_lp_key_paths())?; + let mlkem = load_mlkem768_keypair(&config.storage_paths.keys.mlkem768_key_paths())?; + let mceliece = load_mceliece_keypair(&config.storage_paths.keys.mceliece_key_paths())?; + let psq_kem_keys = KEMKeys::new(mceliece, mlkem); Ok(NymNode { ed25519_identity_keys: Arc::new(ed25519_identity_keys), @@ -475,6 +536,7 @@ impl NymNode { x25519_noise_keys: Arc::new(load_x25519_noise_keypair( &config.storage_paths.keys.x25519_noise_storage_paths(), )?), + psq_kem_keys, description: load_node_description(&config.storage_paths.description)?, metrics: NymNodeMetrics::new(), verloc_stats: Default::default(), @@ -488,6 +550,7 @@ impl NymNode { accepted_operator_terms_and_conditions: false, shutdown_manager: ShutdownManager::build_new_default() .map_err(|source| NymNodeError::ShutdownSignalFailure { source })?, + x25519_lp_keys: Arc::new(x25519_lp_keys), }) } @@ -640,15 +703,14 @@ 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, + mix_packet_sender.clone(), metrics_sender, self.metrics.clone(), self.entry_gateway.mnemonic.clone(), Self::user_agent(), self.upgrade_mode_state.clone(), + self.config.lp.debug.use_mock_ecash, self.shutdown_tracker().clone(), ); @@ -706,16 +768,13 @@ impl NymNode { // 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, + self.config.lp.control_bind_address, self.config.lp.data_bind_address, ); - let lp_listener = gateway_tasks_builder - .build_lp_listener(wg_peer_registrator.clone(), active_clients_store.clone()) + let mut lp_listener = self + .build_lp_listener(wg_peer_registrator.clone(), mix_packet_sender) .await?; - // 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"); + 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"); } @@ -791,40 +850,24 @@ 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 - // } + + fn compute_kem_key_hashes(&self) -> BTreeMap> { + let digests = self.psq_kem_keys.encapsulation_keys_digests(); + + // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` + digests + .into_iter() + .map(|(kem, kem_digests)| { + ( + kem.into(), + kem_digests + .into_iter() + .map(|(f, digest)| (f.into(), hex::encode(&digest))) + .collect(), + ) + }) + .collect() + } pub(crate) async fn build_http_server(&self) -> Result { let auxiliary_details = api_requests::v1::node::models::AuxiliaryDetails { @@ -899,7 +942,21 @@ impl NymNode { policy: None, }; - let mut config = HttpServerConfig::new() + let lewes_protocol = LewesProtocol { + enabled: self.modes().entry, + control_port: self.config.lp.announced_control_port(), + data_port: self.config.lp.announced_data_port(), + x25519: self.x25519_lp_keys.pk, + kem_keys: self.compute_kem_key_hashes(), + }; + + // SAFETY: the only way for this call to fail is if serialisation of LewesProtocol fails. + // however, that conversion is stable and infallible + #[allow(clippy::unwrap_used)] + let signed_lewes_protocol = + SignedData::new(lewes_protocol, self.ed25519_identity_keys.private_key()).unwrap(); + + let mut config = HttpServerConfig::new(signed_lewes_protocol) .with_landing_page_assets(self.config.http.landing_page_assets_path.as_ref()) .with_mixnode_details(mixnode_details) .with_gateway_details(gateway_details) @@ -1345,7 +1402,7 @@ impl NymNode { Ok(self.shutdown_manager) } - pub(crate) async fn run(mut self) -> Result<(), NymNodeError> { + pub async fn run(mut self) -> Result<(), NymNodeError> { let mut shutdown_signals = self.shutdown_manager.detach_shutdown_signals(); // listen for shutdown signal in case we received it when attempting to spawn all the tasks diff --git a/nym-node/src/wireguard/mod.rs b/nym-node/src/wireguard/mod.rs index 03d1b32e4b..46db2413ef 100644 --- a/nym-node/src/wireguard/mod.rs +++ b/nym-node/src/wireguard/mod.rs @@ -5,3 +5,6 @@ // but let's start putting everything in here pub mod error; + +pub use nym_gateway::node::wireguard::{PeerManager, PeerRegistrator}; +pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData}; diff --git a/nym-outfox/Cargo.toml b/nym-outfox/Cargo.toml index 8d7855bc48..d3ea59291d 100644 --- a/nym-outfox/Cargo.toml +++ b/nym-outfox/Cargo.toml @@ -10,18 +10,13 @@ repository = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rayon = { workspace = true } blake3 = { workspace = true } zeroize = { workspace = true } chacha20 = { workspace = true, features = ["std"] } x25519-dalek = { workspace = true } chacha20poly1305 = { workspace = true } -getrandom = { workspace = true, features = ["js"] } thiserror = { workspace = true } sphinx-packet = { workspace = true } -rand = { workspace = true } -log = { workspace = true } [dev-dependencies] -criterion = { workspace = true } fastrand = { workspace = true } diff --git a/nym-registration-client/Cargo.toml b/nym-registration-client/Cargo.toml index 3209fc7e47..a1c3aa335a 100644 --- a/nym-registration-client/Cargo.toml +++ b/nym-registration-client/Cargo.toml @@ -14,26 +14,28 @@ publish = false workspace = true [dependencies] +bincode.workspace = true bytes.workspace = true futures.workspace = true -rand.workspace = true +rand09.workspace = true thiserror.workspace = true tokio.workspace = true tokio-util.workspace = true tracing.workspace = true typed-builder.workspace = true -url.workspace = true - 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-crypto = { workspace = true, features = ["asymmetric", "libcrux_x25519"] } nym-ip-packet-client = { workspace = true } nym-lp = { path = "../common/nym-lp" } -nym-lp-transport = { path = "../common/nym-lp-transport" } nym-registration-common = { workspace = true } nym-sdk = { workspace = true } nym-validator-client = { workspace = true } nym-wireguard-types = { path = "../common/wireguard-types" } + +[dev-dependencies] +nym-kkt.workspace = true +nym-test-utils.workspace = true \ No newline at end of file diff --git a/nym-registration-client/src/clients/lp.rs b/nym-registration-client/src/clients/lp.rs index 3f3ce0abbc..3b6131db76 100644 --- a/nym-registration-client/src/clients/lp.rs +++ b/nym-registration-client/src/clients/lp.rs @@ -13,10 +13,10 @@ 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 nym_lp::peer::DHKeyPair; +use rand09::{CryptoRng, RngCore}; use std::sync::Arc; use tokio::net::TcpStream; use tokio_util::sync::CancellationToken; @@ -54,19 +54,22 @@ impl LpBasedRegistrationClient { let entry_lp_protocol = entry_lp_data.lp_protocol_version; let exit_lp_protocol = exit_lp_data.lp_protocol_version; + let entry_ciphersuite = entry_lp_data.ciphersuite; + let exit_ciphersuite = exit_lp_data.ciphersuite; + 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)); + // Generate fresh x25519 keypairs for LP registration + // TODO: persist them for the duration of the sessions + let entry_lp_keypair = Arc::new(DHKeyPair::new(&mut rand09::rng())); + let exit_lp_keypair = Arc::new(DHKeyPair::new(&mut rand09::rng())); - 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); + let entry_peer = to_lp_remote_peer(entry_lp_data); + let exit_peer = to_lp_remote_peer(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. @@ -76,6 +79,7 @@ impl LpBasedRegistrationClient { entry_lp_keypair.clone(), entry_peer, entry_address, + entry_ciphersuite, entry_lp_protocol, self.config.lp_registration_config, ); @@ -94,8 +98,13 @@ impl LpBasedRegistrationClient { // 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); + let mut nested_session = NestedLpSession::new( + exit_address, + exit_lp_keypair, + exit_peer, + exit_ciphersuite, + exit_lp_protocol, + ); // Perform handshake and registration with exit gateway (all via entry forwarding) let exit_gateway_data = nested_session @@ -149,7 +158,7 @@ impl LpBasedRegistrationClient { } async fn register_wg(self) -> Result { - let mut rng = rand::rngs::OsRng; + let mut rng = rand09::rng(); self.register_wg_with_rng(&mut rng).await } diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index 25f3265878..81d6c5f67d 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -5,29 +5,26 @@ 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::helpers::{LpDataDeliverExt, LpDataSendExt}; use crate::lp_client::nested_session::connection::NestedConnection; use crate::lp_client::state_machine_helpers::{extract_forwarded_response, prepare_send_packet}; -use bytes::BytesMut; 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::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::{LpPacket, LpSession}; -use nym_lp_transport::traits::LpTransport; +use nym_lp::LpSession; +use nym_lp::peer::{DHKeyPair, LpLocalPeer, LpRemotePeer}; +use nym_lp::peer_config::LpReceiverIndex; +use nym_lp::state_machine::LpStateMachine; +use nym_lp::transport::traits::LpTransportChannel; +use nym_lp::transport::{LpHandshakeChannel, LpTransportError}; +use nym_lp::{Ciphersuite, packet::EncryptedLpPacket, packet::version}; use nym_registration_common::dvpn::LpDvpnRegistrationResponseMessageContent; use nym_registration_common::{ LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, WireguardRegistrationData, }; use nym_wireguard_types::PeerPublicKey; -use rand::{CryptoRng, RngCore}; +use rand09::{CryptoRng, Rng, RngCore}; use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; @@ -62,7 +59,7 @@ pub struct LpRegistrationClient { gateway_supported_lp_protocol_version: u8, /// LP state machine for managing connection lifecycle. - /// Created during handshake initiation. Persists across packet-per-connection calls. + /// Created during handshake initiation. state_machine: Option, /// Configuration for timeouts and TCP parameters. @@ -75,23 +72,25 @@ pub struct LpRegistrationClient { impl LpRegistrationClient where - S: LpTransport + Unpin, + S: LpTransportChannel + LpHandshakeChannel + Unpin, { /// Creates a new LP registration client. /// /// # Arguments - /// * `local_ed25519_keypair` - Client's Ed25519 identity keypair + /// * `local_x25519_keypair` - Client's x25519 keypair /// * `gateway_lp_peer` - Encapsulates all the gateway keys needed for the Lewes Protocol /// * `gateway_lp_address` - Gateway's LP listener socket address + /// * `ciphersuite` - the set of cryptographic protocols to use when negotiating the session with the node /// * `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, + local_x25519_keypair: Arc, gateway_lp_peer: LpRemotePeer, gateway_lp_address: SocketAddr, + ciphersuite: Ciphersuite, gateway_supported_lp_protocol_version: u8, config: LpRegistrationConfig, ) -> Self { @@ -105,8 +104,7 @@ where 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)); + let lp_local_peer = LpLocalPeer::new(ciphersuite, local_x25519_keypair); Self { lp_local_peer, gateway_lp_peer, @@ -119,13 +117,8 @@ where } /// Attempt to use this `LpRegistrationClient` as transport for `NestedSession` - pub fn as_nested_connection( - &mut self, - exit_identity: ed25519::PublicKey, - exit_address: SocketAddr, - ) -> NestedConnection<'_, S> { + pub fn as_nested_connection(&mut self, exit_address: SocketAddr) -> NestedConnection<'_, S> { NestedConnection { - exit_identity, exit_address, outer_client: self, } @@ -134,41 +127,46 @@ where /// Creates a new LP registration client with default configuration. /// /// # Arguments - /// * `local_ed25519_keypair` - Client's Ed25519 identity keypair + /// * `local_x25519_keypair` - Client's x25519 keypair /// * `gateway_lp_peer` - Encapsulates all the gateway keys needed for the Lewes Protocol /// * `gateway_lp_address` - Gateway's LP listener socket address + /// * `ciphersuite` - the set of cryptographic protocols to use when negotiating the session with the node /// * `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_config( - local_ed25519_keypair: Arc, + local_x25519_keypair: Arc, gateway_lp_peer: LpRemotePeer, gateway_lp_address: SocketAddr, + ciphersuite: Ciphersuite, gateway_supported_lp_protocol_version: u8, ) -> Self { Self::new( - local_ed25519_keypair, + local_x25519_keypair, gateway_lp_peer, gateway_lp_address, + ciphersuite, gateway_supported_lp_protocol_version, LpRegistrationConfig::default(), ) } + pub(crate) fn state_machine(&self) -> Result<&LpStateMachine> { + self.state_machine + .as_ref() + .ok_or(LpClientError::IncompleteHandshake) + } + pub(crate) 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?", - ) - }) + self.state_machine + .as_mut() + .ok_or(LpClientError::IncompleteHandshake) } fn stream_mut(&mut self) -> Result<&mut S> { - self.stream - .as_mut() - .ok_or_else(|| LpClientError::transport("Cannot send: not connected")) + self.stream.as_mut().ok_or(LpClientError::NotConnected) } /// Returns whether the client has completed the handshake and is ready for registration. @@ -216,10 +214,10 @@ where .await .map_err(|_| LpClientError::TcpConnection { address: self.gateway_lp_address.to_string(), - source: std::io::Error::new( - std::io::ErrorKind::TimedOut, - format!("Connection timeout after {:?}", self.config.connect_timeout), - ), + source: LpTransportError::ConnectionFailure(format!( + "Connection timeout after {:?}", + self.config.connect_timeout + )), })? .map_err(|source| LpClientError::TcpConnection { address: self.gateway_lp_address.to_string(), @@ -253,7 +251,10 @@ where /// /// # Errors /// Returns an error if not connected or if send or receive fails. - async fn send_and_receive_packet(&mut self, packet: &LpPacket) -> Result { + async fn send_and_receive_packet( + &mut self, + packet: &EncryptedLpPacket, + ) -> Result { self.try_send_packet(packet).await?; self.try_receive_packet().await } @@ -262,22 +263,21 @@ where /// 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` + /// Both packets are going to be encrypted /// /// # Arguments - /// * `packet` - The LP packet to send + /// * `packet` - The encrypted 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( + async fn send_and_receive_data_packet_with_timeout( &mut self, - packet: &LpPacket, + packet: &EncryptedLpPacket, timeout: Duration, - ) -> Result { + ) -> Result { tokio::time::timeout(timeout, self.send_and_receive_packet(packet)) .await - .map_err(|_| LpClientError::ResponseReceiveTimeout { timeout })? + .map_err(|_| LpClientError::ConnectionTimeout)? } /// Sends an LP packet on the persistent stream. @@ -287,42 +287,25 @@ where /// /// # Errors /// Returns an error if not connected or if send fails. - pub(crate) async fn try_send_packet(&mut self, packet: &LpPacket) -> Result<()> { + pub(crate) async fn try_send_packet(&mut self, packet: &EncryptedLpPacket) -> Result<()> { // can't use getters due to borrow checker (i.e. requiring full borrows for function calls) - let stream = self - .stream - .as_mut() - .ok_or_else(|| LpClientError::transport("Cannot send: not connected"))?; - - let state_machine = self.state_machine.as_ref().ok_or_else(|| { - LpClientError::transport( - "State machine not available - has the handshake been completed?", - ) - })?; - - let outer_key = state_machine.session()?.outer_aead_key(); - Self::send_packet_with_key(stream, packet, outer_key).await + self.stream_mut()? + .send_length_prefixed_transport_packet(packet) + .await?; + Ok(()) } /// Receives an LP packet from the persistent stream. /// /// # Errors /// Returns an error if not connected or if receive fails. - pub(crate) async fn try_receive_packet(&mut self) -> Result { - let stream = self - .stream - .as_mut() - .ok_or_else(|| LpClientError::transport("Cannot send: not connected"))?; + pub(crate) async fn try_receive_packet(&mut self) -> Result { + let encrypted_packet = self + .stream_mut()? + .receive_length_prefixed_transport_packet() + .await?; - let state_machine = self.state_machine.as_ref().ok_or_else(|| { - LpClientError::transport( - "State machine not available - has the handshake been completed?", - ) - })?; - - let outer_key = state_machine.session()?.outer_aead_key(); - - Self::receive_packet_with_key(stream, outer_key).await + Ok(encrypted_packet) } /// Closes the persistent connection. @@ -372,7 +355,7 @@ where /// /// The connection remains open after handshake for registration/forwarding. pub async fn perform_handshake(&mut self) -> Result<()> { - // Apply handshake timeout (nym-102) + // Apply handshake timeout let result = tokio::time::timeout( self.config.handshake_timeout, self.perform_handshake_inner(), @@ -388,10 +371,7 @@ where } Err(_) => { self.close(); - Err(LpClientError::Transport(format!( - "Handshake timeout after {:?}", - self.config.handshake_timeout - ))) + Err(LpClientError::HandshakeTimeout) } } } @@ -413,15 +393,13 @@ where let connection = self.stream_mut()?; // TODO: - let ciphersuite = LpSession::default_ciphersuite(); - let session = LpSession::complete_as_initiator( + let session = LpSession::psq_handshake_initiator( connection, - ciphersuite, local_peer, remote_peer, protocol_version, ) - .complete_as_initiator() + .complete_handshake() .await?; // Store the state machine (with established session) for later use @@ -429,57 +407,6 @@ where Ok(()) } - /// Sends an LP packet over a TCP stream with length-prefixed framing. - /// - /// Format: 4-byte big-endian u32 length + packet bytes - /// - /// # Arguments - /// * `stream` - TCP stream to send on - /// * `packet` - The LP packet to send - /// * `outer_key` - Optional outer AEAD key for encryption - /// - /// # Errors - /// Returns an error if serialization or network transmission fails. - async fn send_packet_with_key( - stream: &mut S, - packet: &LpPacket, - outer_key: &OuterAeadKey, - ) -> Result<()> { - let mut packet_buf = BytesMut::new(); - serialize_lp_packet(packet, &mut packet_buf, Some(outer_key)) - .map_err(|e| LpClientError::Transport(format!("Failed to serialize packet: {e}")))?; - - stream - .send_serialised_packet(&packet_buf) - .await - .map_err(|err| LpClientError::Transport(err.to_string())) - } - - /// Receives an LP packet from a TCP stream with length-prefixed framing. - /// - /// Format: 4-byte big-endian u32 length + packet bytes - /// - /// # Arguments - /// * `stream` - TCP stream to receive from - /// * `outer_key` - Optional outer AEAD key for decryption - /// - /// # Errors - /// Returns an error if: - /// - Network read fails - /// - Packet size exceeds maximum (64KB) - /// - Packet parsing/decryption fails - async fn receive_packet_with_key(stream: &mut S, outer_key: &OuterAeadKey) -> Result { - let packet_buf = stream - .receive_raw_packet() - .await - .map_err(|err| LpClientError::transport(err.to_string()))?; - - let packet = parse_lp_packet(&packet_buf, Some(outer_key)) - .map_err(|e| LpClientError::Transport(format!("Failed to parse packet: {e}")))?; - - Ok(packet) - } - /// 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. /// @@ -539,7 +466,10 @@ where // 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) + .send_and_receive_data_packet_with_timeout( + &request_packet, + self.config.registration_timeout, + ) .await?; // 6. Decrypt via state machine (re-borrow) @@ -623,7 +553,10 @@ where // 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) + .send_and_receive_data_packet_with_timeout( + &request_packet, + self.config.registration_timeout, + ) .await?; // 5. Decrypt via state machine (re-borrow) @@ -721,7 +654,7 @@ where 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 jitter_ms: u64 = rand09::rng().random_range(0..(base_delay_ms / 4 + 1)); let delay = std::time::Duration::from_millis(base_delay_ms + jitter_ms); tracing::info!("Retrying registration (attempt {attempt_display}) after {delay:?}"); tokio::time::sleep(delay).await; @@ -764,174 +697,9 @@ where } } - Err(last_error - .unwrap_or_else(|| LpClientError::transport("Registration failed after all retries"))) - } - - /// Sends a ForwardPacket message to the entry gateway for forwarding to the exit gateway. - /// - /// This method constructs a ForwardPacket containing the target gateway's identity, - /// address, and the inner LP packet bytes, encrypts it through the outer session - /// (client-entry), and receives the response from the exit gateway via the entry gateway. - /// - /// Uses the persistent TCP connection established during handshake. - /// Multiple forward packets can be sent on the same connection. - /// - /// # Arguments - /// * `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 - /// - /// # Errors - /// Returns an error if: - /// - Handshake has not been completed - /// - Serialization fails - /// - Encryption or network transmission fails - /// - Response decryption fails - /// - /// # Example Flow - /// ```ignore - /// // Construct inner packet for exit gateway (ClientHello, handshake, etc.) - /// let inner_packet = LpPacket::new(...); - /// let inner_bytes = serialize_lp_packet(&inner_packet, &mut BytesMut::new())?; - /// - /// // Forward through entry gateway - /// let response_bytes = client.send_forward_packet( - /// exit_identity, - /// "2.2.2.2:41264".to_string(), - /// inner_bytes.to_vec(), - /// ).await?; - /// ``` - pub async fn send_forward_packet_with_response( - &mut self, - forward_data: ForwardPacketData, - ) -> Result> { - let target_address = forward_data.target_lp_address; - - tracing::debug!( - "Sending ForwardPacket to {target_address} ({} inner bytes, persistent connection)", - forward_data.inner_packet_bytes.len() - ); - - // 1. Serialize the ForwardPacketData - let input = convert_forward_data(forward_data)?; - - // 2. Encrypt and prepare packet via state machine - let state_machine = self.state_machine_mut()?; - - let action = state_machine - .process_input(input) - .ok_or_else(|| LpClientError::transport("State machine returned no action"))? - .map_err(|e| { - LpClientError::Transport(format!("Failed to encrypt ForwardPacket: {e}")) - })?; - - let forward_packet = match action { - LpAction::SendPacket(packet) => packet, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when sending ForwardPacket: {:?}", - other - ))); - } - }; - - // 3. Send and receive on persistent connection with timeout - let response_packet = tokio::time::timeout(self.config.forward_timeout, async { - self.try_send_packet(&forward_packet).await?; - self.try_receive_packet().await - }) - .await - .map_err(|_| { - LpClientError::Transport(format!( - "Forward packet timeout after {:?}", - self.config.forward_timeout - )) - })??; - tracing::trace!("Received response packet from entry gateway"); - - // 4. 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::Transport(format!("Failed to decrypt forward response: {e}")) - })?; - - // 5. Extract decrypted response data - let response_data = try_convert_forward_response(action)?; - - tracing::debug!( - "Successfully received forward response from {target_address} ({} bytes)", - response_data.len() - ); - - Ok(response_data) - } - - /// Wrap data in an LP packet for UDP transmission to the data plane (port 51264). - /// - /// This method encrypts the provided data using the established LP session - /// and returns serialized LP packet bytes ready to send over UDP. - /// - /// # Prerequisites - /// - Handshake must be completed (`perform_handshake()`) - /// - /// # Arguments - /// * `data` - Raw application data to wrap (e.g., Sphinx packet bytes) - /// - /// # Returns - /// * `Ok(Vec)` - Serialized LP packet bytes (outer header + encrypted payload) - /// - /// # Wire Format - /// The returned bytes are in LP wire format: - /// - Outer header (12B): receiver_idx(4) + counter(8) - always cleartext - /// - Encrypted payload: proto(1) + reserved(3) + msg_type(4) + content + tag(16) - /// - /// # Usage - /// After LP handshake, wrap Sphinx packets before sending to gateway's LP data port: - /// ```ignore - /// client.perform_handshake().await?; - /// let sphinx_bytes = build_sphinx_packet(...); - /// let lp_bytes = client.wrap_data(&sphinx_bytes)?; - /// socket.send_to(&lp_bytes, gateway_lp_data_address).await?; // UDP:51264 - /// ``` - pub fn wrap_data(&mut self, data: &[u8]) -> Result> { - let state_machine = self - .state_machine - .as_mut() - .ok_or_else(|| LpClientError::transport("Cannot wrap data: handshake not completed"))?; - - // Process data through state machine to create LP packet - let action = state_machine - .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}")))?; - - let packet = match action { - LpAction::SendPacket(packet) => packet, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when wrapping data: {:?}", - other - ))); - } - }; - - // Get outer AEAD key for encryption - let outer_key = state_machine.session()?.outer_aead_key(); - - // Serialize the packet with outer AEAD encryption - let mut buf = BytesMut::new(); - serialize_lp_packet(&packet, &mut buf, Some(outer_key)) - .map_err(|e| LpClientError::Transport(format!("Failed to serialize LP packet: {e}")))?; - - Ok(buf.to_vec()) + Err(last_error.unwrap_or(LpClientError::RegistrationFailure { + message: "Registration failed after all retries".to_string(), + })) } /// Get the LP session ID (receiver_idx) for this client. @@ -940,41 +708,39 @@ where /// the gateway to look up the session for decryption. /// /// # Returns - /// * `Ok(u32)` - The session ID + /// * `Ok(LpReceiverIndex)` - The session ID /// /// # Errors /// Returns an error if handshake has not been completed. - pub fn session_id(&self) -> Result { - let state_machine = self.state_machine.as_ref().ok_or_else(|| { - LpClientError::transport("Cannot get session ID: handshake not completed") - })?; - - state_machine + pub fn session_id(&self) -> Result { + self.state_machine()? .session() - .map(|s| s.id()) - .map_err(|e| LpClientError::Transport(format!("Failed to get session: {e}"))) + .map(|s| s.receiver_index()) + .map_err(Into::into) } } #[cfg(test)] mod tests { use super::*; + use nym_kkt::key_utils::generate_lp_keypair_x25519; use nym_lp::packet::version; + use nym_test_utils::helpers::deterministic_rng_09; #[test] fn test_client_creation() { - let mut rng = rand::thread_rng(); - let keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); - 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 mut rng09 = deterministic_rng_09(); + let keypair = Arc::new(generate_lp_keypair_x25519(&mut rng09)); + + let gateway_x_keys = generate_lp_keypair_x25519(&mut rng09); + let gateway_peer = LpRemotePeer::from(gateway_x_keys.pk); let address = "127.0.0.1:41264".parse().unwrap(); let client = LpRegistrationClient::::new_with_default_config( keypair, gateway_peer, address, + Ciphersuite::default(), version::CURRENT, ); diff --git a/nym-registration-client/src/lp_client/error.rs b/nym-registration-client/src/lp_client/error.rs index 32443f4e01..16bd1d66c3 100644 --- a/nym-registration-client/src/lp_client/error.rs +++ b/nym-registration-client/src/lp_client/error.rs @@ -4,9 +4,10 @@ //! Error types for LP (Lewes Protocol) client operations. use nym_lp::LpError; -use nym_registration_common::BincodeError; -use std::io; -use std::time::Duration; +use nym_lp::packet::MalformedLpPacketError; +use nym_lp::packet::message::LpMessageType; +use nym_lp::state_machine::LpAction; +use nym_lp::transport::LpTransportError; use thiserror::Error; /// Errors that can occur during LP client operations. @@ -17,12 +18,41 @@ pub enum LpClientError { TcpConnection { address: String, #[source] - source: io::Error, + source: LpTransportError, }, - /// Failed during LP handshake - #[error("LP handshake failed: {0}")] - HandshakeFailed(#[from] LpError), + #[error(transparent)] + LpTransportError(#[from] LpTransportError), + + #[error("the client has not opened a connection to the exit")] + NotConnected, + + #[error("the KKT/PSQ handshake does not appear to have been completed")] + IncompleteHandshake, + + #[error(transparent)] + LpProtocolError(#[from] LpError), + + #[error("no action has been emitted from the LP State Machine")] + UnexpectedStateMachineHalt, + + #[error("the state machine instructed an unexpected action: {action:?}")] + UnexpectedStateMachineAction { action: LpAction }, + + #[error("received registration data was malformed: {source}")] + MalformedRegistrationData { source: bincode::Error }, + + #[error("received a malformed packet: {0}")] + MalformedLpPacket(#[from] MalformedLpPacketError), + + #[error("received payload type of an unexpected type: {typ:?}")] + UnexpectedLpPayload { typ: LpMessageType }, + + #[error("timed out while attempting to finish the KKT/PSQ handshake")] + HandshakeTimeout, + + #[error("timed out while attempting to send to/receive from the connection")] + ConnectionTimeout, /// Failed to send registration request #[error("Failed to send registration request: {0}")] @@ -36,43 +66,20 @@ 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), - - /// Invalid LP address format - #[error("Invalid LP address '{address}': {reason}")] - InvalidAddress { address: String, reason: String }, - - /// Serialization/deserialization error - #[error("Serialization error: {0}")] - Serialization(#[from] BincodeError), - - /// Connection closed unexpectedly - #[error("Connection closed unexpectedly")] - ConnectionClosed, - - /// Timeout waiting for response - #[error("Timeout waiting for {operation}")] - Timeout { operation: String }, + #[error("could not complete the registration: {message}")] + RegistrationFailure { message: String }, #[error("received an unexpected response: {message}")] UnexpectedResponse { message: String }, - /// Another uncategorized error + #[error("currently McEliece keys are not supported for nested registration")] + UnsupportedNestedMcEliece, + #[error("{0}")] Other(String), } 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(), diff --git a/nym-registration-client/src/lp_client/helpers.rs b/nym-registration-client/src/lp_client/helpers.rs index 3eb53706fd..2ed712f9a0 100644 --- a/nym-registration-client/src/lp_client/helpers.rs +++ b/nym-registration-client/src/lp_client/helpers.rs @@ -4,24 +4,24 @@ #![allow(dead_code)] use crate::LpClientError; -use nym_crypto::asymmetric::ed25519; -use nym_lp::message::ForwardPacketData; +use nym_lp::packet::message::LpMessageType; +use nym_lp::packet::{ForwardPacketData, LpMessage}; use nym_lp::peer::LpRemotePeer; -use nym_lp::state_machine::{LpAction, LpData, LpDataKind, LpInput}; +use nym_lp::state_machine::{LpAction, LpInput}; use nym_registration_common::{ LpRegistrationRequest, LpRegistrationResponse, NymNodeLPInformation, }; pub(crate) trait LpDataSendExt { - fn to_lp_data(&self) -> Result; + fn to_lp_data(&self) -> Result; } pub(crate) trait LpDataDeliverExt: Sized { - fn from_lp_data(data: LpData) -> Result; + fn from_lp_data(data: LpMessage) -> Result; } impl LpDataSendExt for LpRegistrationRequest { - fn to_lp_data(&self) -> Result { + fn to_lp_data(&self) -> Result { let request_bytes = self.serialise().map_err(|e| { LpClientError::SendRegistrationRequest(format!("Failed to serialize request: {e}")) })?; @@ -31,29 +31,25 @@ impl LpDataSendExt for LpRegistrationRequest { request_bytes.len() ); - Ok(LpData::new_registration(request_bytes)) + Ok(LpMessage::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 - ))); + fn from_lp_data(data: LpMessage) -> Result { + if data.kind() != LpMessageType::Registration { + return Err(LpClientError::UnexpectedLpPayload { typ: data.kind() }); } - let response = LpRegistrationResponse::try_deserialise(&data.content).map_err(|e| { - LpClientError::Transport(format!("Failed to deserialize registration response: {e}",)) - })?; + let response = LpRegistrationResponse::try_deserialise(&data.content) + .map_err(|source| LpClientError::MalformedRegistrationData { source })?; Ok(response) } } impl LpDataSendExt for ForwardPacketData { - fn to_lp_data(&self) -> Result { + fn to_lp_data(&self) -> Result { let request_bytes = self.to_bytes(); tracing::trace!( @@ -61,7 +57,7 @@ impl LpDataSendExt for ForwardPacketData { request_bytes.len() ); - Ok(LpData::new_forward(request_bytes)) + Ok(LpMessage::new_forward(request_bytes)) } } @@ -72,30 +68,18 @@ pub(crate) fn convert_forward_data(request: ForwardPacketData) -> Result Result, LpClientError> { let response_data = match action { LpAction::DeliverData(data) => data, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when receiving forward response: {:?}", - other - ))); - } + action => return Err(LpClientError::UnexpectedStateMachineAction { action }), }; - if response_data.kind != LpDataKind::Forward { - return Err(LpClientError::Transport(format!( - "did not receive a valid forward response. got {:?} instead", - response_data.kind - ))); + if response_data.kind() != LpMessageType::Forward { + return Err(LpClientError::UnexpectedLpPayload { + typ: 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, - ) +pub(crate) fn to_lp_remote_peer(data: NymNodeLPInformation) -> LpRemotePeer { + LpRemotePeer::new(data.x25519).with_key_digests(data.expected_kem_key_hashes) } diff --git a/nym-registration-client/src/lp_client/nested_session/connection.rs b/nym-registration-client/src/lp_client/nested_session/connection.rs index fd0f673677..70432e9bb0 100644 --- a/nym-registration-client/src/lp_client/nested_session/connection.rs +++ b/nym-registration-client/src/lp_client/nested_session/connection.rs @@ -3,60 +3,79 @@ use crate::lp_client::helpers::{convert_forward_data, try_convert_forward_response}; use crate::{LpClientError, LpRegistrationClient}; -use nym_crypto::asymmetric::ed25519; -use nym_lp::message::ForwardPacketData; +use bytes::{BufMut, BytesMut}; +use nym_lp::KEM; +use nym_lp::packet::{EncryptedLpPacket, ForwardPacketData, message::ExpectedResponseSize}; use nym_lp::state_machine::{LpAction, LpInput}; -use nym_lp_transport::traits::LpTransport; +use nym_lp::transport::traits::{HandshakeMessage, LpTransportChannel}; +use nym_lp::transport::{LpHandshakeChannel, LpTransportError}; use std::io; use std::net::SocketAddr; /// Attempt to treat the inner client as a LP connection pub struct NestedConnection<'a, S> { - /// Remote Ed25519 public key - pub(crate) exit_identity: ed25519::PublicKey, - /// Exit gateway's LP address (e.g., "2.2.2.2:41264") pub(crate) exit_address: SocketAddr, + // exact mechanisms of determining this value are TBD pub(crate) outer_client: &'a mut LpRegistrationClient, } impl<'a, S> NestedConnection<'a, S> { - async fn send_serialised_packet(&mut self, packet_data: &[u8]) -> Result<(), LpClientError> + fn prepare_handshake_message( + &self, + message: M, + handshake_kem: KEM, + ) -> Result { + let Some(response_size) = message.response_size(handshake_kem) else { + // this should NEVER happen for an initiator + return Err(LpClientError::Other("unexpected empty response".into())); + }; + + let expected_size = ExpectedResponseSize::Handshake(response_size as u32); + + Ok(ForwardPacketData::new( + self.exit_address, + expected_size, + message.into_bytes(), + )) + } + + fn prepare_transport_message(&self, packet: &EncryptedLpPacket) -> ForwardPacketData { + let mut buf = BytesMut::new(); + let len = packet.encoded_length() as u32; + buf.put_u32_le(len); + packet.encode(&mut buf); + ForwardPacketData::new( + self.exit_address, + ExpectedResponseSize::Transport, + buf.freeze().into(), + ) + } + + async fn send_forward_packet(&mut self, data: ForwardPacketData) -> Result<(), LpClientError> where - S: LpTransport + Unpin, + S: LpTransportChannel + LpHandshakeChannel + Unpin, { - let forward_packet_data = - ForwardPacketData::new(self.exit_identity, self.exit_address, packet_data.to_vec()); - - let target_address = self.exit_address; - tracing::debug!( - "Sending ForwardPacket to {target_address} ({} inner bytes, persistent connection)", - forward_packet_data.inner_packet_bytes.len() + "Sending ForwardPacket to {} ({} inner bytes, persistent connection)", + data.target_lp_address, + data.inner_packet_bytes.len() ); // 1. Serialize the ForwardPacketData - let input = convert_forward_data(forward_packet_data)?; + let input = convert_forward_data(data)?; // 2. Encrypt and prepare packet via state machine let state_machine = self.outer_client.state_machine_mut()?; let action = state_machine .process_input(input) - .ok_or_else(|| LpClientError::transport("State machine returned no action"))? - .map_err(|e| { - LpClientError::Transport(format!("Failed to encrypt ForwardPacket: {e}")) - })?; + .ok_or(LpClientError::UnexpectedStateMachineHalt)??; let forward_packet = match action { LpAction::SendPacket(packet) => packet, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when sending ForwardPacket: {:?}", - other - ))); - } + action => return Err(LpClientError::UnexpectedStateMachineAction { action }), }; // 3. Send the packet with timeout @@ -65,16 +84,14 @@ impl<'a, S> NestedConnection<'a, S> { self.outer_client.try_send_packet(&forward_packet).await }) .await - .map_err(|_| { - LpClientError::Transport(format!("Forward packet timeout after {timeout:?}",)) - })??; + .map_err(|_| LpClientError::ConnectionTimeout)??; Ok(()) } - async fn receive_raw_packet(&mut self) -> Result, LpClientError> + async fn receive_forward_packet_data(&mut self) -> Result, LpClientError> where - S: LpTransport + Unpin, + S: LpTransportChannel + LpHandshakeChannel + Unpin, { // 1. Receive the packet with timeout let timeout = self.outer_client.config.forward_timeout; @@ -82,18 +99,13 @@ impl<'a, S> NestedConnection<'a, S> { self.outer_client.try_receive_packet().await }) .await - .map_err(|_| { - LpClientError::Transport(format!("Forward packet timeout after {timeout:?}",)) - })??; + .map_err(|_| LpClientError::ConnectionTimeout)??; // 2. Decrypt via state machine (re-borrow) let state_machine = self.outer_client.state_machine_mut()?; let action = state_machine .process_input(LpInput::ReceivePacket(response_packet)) - .ok_or_else(|| LpClientError::transport("State machine returned no action"))? - .map_err(|e| { - LpClientError::Transport(format!("Failed to decrypt forward response: {e}")) - })?; + .ok_or(LpClientError::UnexpectedStateMachineHalt)??; // 3. Extract decrypted response data let response_data = try_convert_forward_response(action)?; @@ -108,28 +120,78 @@ impl<'a, S> NestedConnection<'a, S> { } } -impl<'a, S> LpTransport for NestedConnection<'a, S> +impl<'a, S> LpHandshakeChannel for NestedConnection<'a, S> where - S: LpTransport + Unpin, + S: LpTransportChannel + LpHandshakeChannel + Unpin, { #[allow(clippy::unimplemented)] - async fn connect(_: SocketAddr) -> std::io::Result { + async fn write_all_and_flush(&mut self, _: &[u8]) -> Result<(), LpTransportError> { + // this is not being called instead we implement `send_handshake_message` directly + unimplemented!() + } + + #[allow(clippy::unimplemented)] + async fn read_n_bytes(&mut self, _: usize) -> Result, LpTransportError> { + // this is not being called instead we implement `receive_handshake_message` directly + unimplemented!() + } + + async fn send_handshake_message( + &mut self, + message: M, + handshake_kem: KEM, + ) -> Result<(), LpTransportError> { + let forward_data = self + .prepare_handshake_message(message, handshake_kem) + .map_err(|err| LpTransportError::TransportSendFailure(err.to_string()))?; + self.send_forward_packet(forward_data) + .await + .map_err(|err| LpTransportError::TransportSendFailure(err.to_string())) + } + + async fn receive_handshake_message( + &mut self, + _: usize, + ) -> Result { + let data = self + .receive_forward_packet_data() + .await + .map_err(|err| LpTransportError::TransportReceiveFailure(err.to_string()))?; + M::try_from_bytes(data) + } +} + +impl<'a, S> LpTransportChannel for NestedConnection<'a, S> +where + S: LpTransportChannel + LpHandshakeChannel + Unpin, +{ + #[allow(clippy::unimplemented)] + async fn connect(_: SocketAddr) -> Result { // this really breaks the pattern and should be refactored // since this function should never be called unimplemented!("cannot establish nested connection without an outer client") } - fn set_no_delay(&mut self, _: bool) -> std::io::Result<()> { + fn set_no_delay(&mut self, _: bool) -> Result<(), LpTransportError> { Ok(()) } - async fn send_serialised_packet(&mut self, packet_data: &[u8]) -> std::io::Result<()> { - self.send_serialised_packet(packet_data) + async fn send_length_prefixed_transport_packet( + &mut self, + packet: &EncryptedLpPacket, + ) -> Result<(), LpTransportError> { + let packet = self.prepare_transport_message(packet); + self.send_forward_packet(packet) .await .map_err(io::Error::other) + .map_err(LpTransportError::send_failure) } - async fn receive_raw_packet(&mut self) -> std::io::Result> { - self.receive_raw_packet().await.map_err(io::Error::other) + async fn receive_length_prefixed_transport_bytes( + &mut self, + ) -> Result, LpTransportError> { + self.receive_forward_packet_data() + .await + .map_err(|err| LpTransportError::TransportReceiveFailure(err.to_string())) } } diff --git a/nym-registration-client/src/lp_client/nested_session/mod.rs b/nym-registration-client/src/lp_client/nested_session/mod.rs index 7ee62c844c..f8150433fe 100644 --- a/nym-registration-client/src/lp_client/nested_session/mod.rs +++ b/nym-registration-client/src/lp_client/nested_session/mod.rs @@ -21,29 +21,27 @@ use super::client::LpRegistrationClient; use super::error::{LpClientError, Result}; use crate::lp_client::helpers::{LpDataDeliverExt, LpDataSendExt}; -use crate::lp_client::state_machine_helpers::{ - extract_forwarded_response, prepare_serialised_send_packet, -}; +use crate::lp_client::state_machine_helpers::{extract_forwarded_response, prepare_send_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}; -use nym_lp::message::ForwardPacketData; use nym_lp::packet::version; -use nym_lp::peer::{LpLocalPeer, LpRemotePeer}; -use nym_lp::state_machine::{LpData, LpStateMachine}; -use nym_lp::{LpPacket, LpSession}; -use nym_lp_transport::traits::LpTransport; +use nym_lp::packet::{EncryptedLpPacket, LpMessage}; +use nym_lp::peer::{DHKeyPair, LpLocalPeer, LpRemotePeer}; +use nym_lp::state_machine::LpStateMachine; +use nym_lp::transport::LpHandshakeChannel; +use nym_lp::transport::traits::LpTransportChannel; +use nym_lp::{Ciphersuite, KEM, LpSession}; use nym_registration_common::dvpn::LpDvpnRegistrationResponseMessageContent; use nym_registration_common::{ LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, WireguardRegistrationData, }; use nym_wireguard_types::PeerPublicKey; -use rand::{CryptoRng, RngCore}; +use rand09::{CryptoRng, Rng, RngCore}; use std::net::SocketAddr; use std::sync::Arc; -use tracing::warn; +use tracing::{debug, warn}; pub(crate) mod connection; @@ -90,17 +88,18 @@ impl NestedLpSession { /// /// # Arguments /// * `exit_address` - Exit gateway's LP address (e.g., "2.2.2.2:41264") - /// * `client_keypair` - Client's Ed25519 keypair + /// * `client_keypair` - Client's x25519 keypair /// * `gateway_lp_peer` - Encapsulates all the gateway keys needed for the Lewes Protocol + /// * `ciphersuite` - the set of cryptographic protocols to use when negotiating the session with the node /// * `gateway_supported_lp_protocol_version` - Gateway's LP protocol version pub fn new( exit_address: SocketAddr, - client_keypair: Arc, + client_keypair: Arc, gateway_lp_peer: LpRemotePeer, + ciphersuite: Ciphersuite, 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_local_peer = LpLocalPeer::new(ciphersuite, client_keypair); let lp_protocol = if gateway_supported_lp_protocol_version > version::CURRENT { warn!( @@ -121,44 +120,25 @@ impl NestedLpSession { } } - 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?", - ) - }) + self.state_machine + .as_mut() + .ok_or(LpClientError::IncompleteHandshake) } - /// 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 = state_machine.session()?.outer_aead_key(); - Self::parse_packet(&response_bytes, Some(outer_key)) - } - - /// Attempt to wrap the provided `LpData` into a `ForwardPacketData` + /// Attempt to wrap the provided `LpData` into a `EncryptedLpPacket` /// using the inner state machine. - fn prepare_forward_packet(&mut self, data: LpData) -> Result { + fn prepare_transport_packet(&mut self, data: LpMessage) -> 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, - )) + prepare_send_packet(data, state_machine) } - /// Attempt to recover received `LpData` from the received `LpPacket` + /// Attempt to recover received `LpData` from the received `EncryptedLpPacket` /// using the inner state machine. - fn extract_forwarded_response(&mut self, response_packet: LpPacket) -> Result { + fn extract_forwarded_response( + &mut self, + response_packet: EncryptedLpPacket, + ) -> Result { let state_machine = self.state_machine_mut()?; extract_forwarded_response(response_packet, state_machine) } @@ -167,10 +147,8 @@ impl NestedLpSession { /// through the entry gateway. /// /// This method: - /// 1. Generates ClientHello for exit gateway - /// 2. Creates LP state machine for exit handshake - /// 3. Runs handshake loop, forwarding all packets through entry gateway - /// 4. Stores established session in internal state machine + /// 1. Runs handshake loop, forwarding all packets through entry gateway + /// 2. Stores established session in internal state machine /// /// # Arguments /// * `outer_client` - Connected LP client with established outer session to entry gateway @@ -181,38 +159,40 @@ impl NestedLpSession { /// - Forwarding through entry gateway fails /// - Exit gateway handshake fails /// - Cryptographic operations fail - async fn perform_handshake( + pub async fn perform_handshake( &mut self, outer_client: &mut LpRegistrationClient, ) -> Result<()> where - S: LpTransport + Unpin, + S: LpTransportChannel + LpHandshakeChannel + Unpin, { + if self.lp_local_peer.ciphersuite().kem() == KEM::McEliece { + return Err(LpClientError::UnsupportedNestedMcEliece); + } + tracing::debug!( "Starting nested LP handshake with exit gateway {}", self.exit_address ); - let mut nested_connection = - outer_client.as_nested_connection(self.gateway_lp_peer.ed25519(), self.exit_address); + let mut nested_connection = outer_client.as_nested_connection(self.exit_address); let local_peer = self.lp_local_peer.clone(); let remote_peer = self.gateway_lp_peer.clone(); let protocol_version = self.gateway_supported_lp_protocol_version; - let ciphersuite = LpSession::default_ciphersuite(); - let session = LpSession::complete_as_initiator( + let session = LpSession::psq_handshake_initiator( &mut nested_connection, - ciphersuite, local_peer, remote_peer, protocol_version, ) - .complete_as_initiator() + .complete_handshake() .await?; // Store the state machine (with established session) for later use self.state_machine = Some(LpStateMachine::new(session)); + debug!("completed nested handshake"); Ok(()) } @@ -246,9 +226,10 @@ impl NestedLpSession { ticket_type: TicketType, ) -> Result where - S: LpTransport + Unpin, + S: LpTransportChannel + LpHandshakeChannel + Unpin, { tracing::debug!("Acquiring bandwidth credential for registration"); + let mut nested_connection = outer_client.as_nested_connection(self.exit_address); // Step 1: Get bandwidth credential from controller let credential_spending = bandwidth_controller @@ -276,18 +257,20 @@ impl NestedLpSession { 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)?; + let forward_packet = self.prepare_transport_packet(send_data)?; // Step 5: Send the encrypted packet via forwarding - let response_bytes = outer_client - .send_forward_packet_with_response(forward_packet) + nested_connection + .send_length_prefixed_transport_packet(&forward_packet) .await?; - // Step 6: Parse response bytes to LP packet - let response_packet = self.parse_received_lp_packet(response_bytes)?; + // Step 6: wait for response + let response = nested_connection + .receive_length_prefixed_transport_packet() + .await?; - // Step 7: Decrypt via state machine - let response_data = self.extract_forwarded_response(response_packet)?; + // Step 7: Process via state machine + let response_data = self.extract_forwarded_response(response)?; // Step 8: Extract decrypted data and deserialise the response let response = LpRegistrationResponse::from_lp_data(response_data)?; @@ -314,13 +297,12 @@ impl NestedLpSession { } } - /// Performs handshake and registration with the exit gateway via forwarding. + /// Performs dVPN registration with the exit gateway via forwarding. /// /// This is the main entry point for nested LP registration. It: - /// 1. Performs handshake with exit gateway (via `perform_handshake`) - /// 2. Builds and sends registration request through the forwarded connection - /// 3. Receives and processes registration response - /// 4. Returns gateway data on successful registration + /// 1. Builds and sends registration request through the forwarded connection + /// 2. Receives and processes registration response + /// 3. Returns gateway data on successful registration /// /// # Arguments /// * `outer_client` - Connected LP client with established outer session to entry gateway @@ -341,7 +323,7 @@ impl NestedLpSession { /// - Forwarding through entry gateway fails /// - Response decryption/deserialization fails /// - Gateway rejects the registration - pub async fn handshake_and_register_dvpn( + pub async fn register_dvpn( &mut self, outer_client: &mut LpRegistrationClient, rng: &mut R, @@ -351,11 +333,10 @@ impl NestedLpSession { ticket_type: TicketType, ) -> Result where - S: LpTransport + Unpin, + S: LpTransportChannel + LpHandshakeChannel + Unpin, R: RngCore + CryptoRng, { - // Step 1: Perform handshake with exit gateway via forwarding - self.perform_handshake(outer_client).await?; + let mut nested_connection = outer_client.as_nested_connection(self.exit_address); tracing::debug!("Building registration request for exit gateway"); @@ -370,20 +351,21 @@ impl NestedLpSession { 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)?; + let forward_packet = self.prepare_transport_packet(send_data)?; // Step 5: Send the encrypted packet via forwarding - let response_bytes = outer_client - .send_forward_packet_with_response(forward_packet) + nested_connection + .send_length_prefixed_transport_packet(&forward_packet) .await?; + // Step 6: wait for response + let response = nested_connection + .receive_length_prefixed_transport_packet() + .await?; tracing::trace!("Received registration response from exit gateway"); - // 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 7: Process via state machine + let response_data = self.extract_forwarded_response(response)?; // Step 8: Extract decrypted data and deserialise the response let response = LpRegistrationResponse::from_lp_data(response_data)?; @@ -426,6 +408,60 @@ impl NestedLpSession { }) } + /// Performs handshake and registration with the exit gateway via forwarding. + /// + /// This is the main entry point for nested LP registration. It: + /// 1. Performs handshake with exit gateway (via `perform_handshake`) + /// 2. Builds and sends registration request through the forwarded connection + /// 3. Receives and processes registration response + /// 4. Returns gateway data on successful registration + /// + /// # Arguments + /// * `outer_client` - Connected LP client with established outer session to entry gateway + /// * `wg_keypair` - Client's WireGuard x25519 keypair + /// * `gateway_identity` - Exit 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 + /// + /// # Errors + /// Returns an error if: + /// - Handshake fails + /// - Credential acquisition fails + /// - Request serialization/encryption fails + /// - Forwarding through entry gateway fails + /// - Response decryption/deserialization fails + /// - Gateway rejects the registration + pub(crate) 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, + ) -> Result + where + S: LpTransportChannel + LpHandshakeChannel + Unpin, + R: RngCore + CryptoRng, + { + // Step 1: Perform handshake with exit gateway via forwarding + self.perform_handshake(outer_client).await?; + + self.register_dvpn( + outer_client, + rng, + wg_keypair, + gateway_identity, + bandwidth_controller, + ticket_type, + ) + .await + } + /// Performs handshake and registration with the exit gateway via forwarding, /// with automatic retry on network failure. /// @@ -466,7 +502,7 @@ impl NestedLpSession { max_retries: u32, ) -> Result where - S: LpTransport + Unpin, + S: LpTransportChannel + LpHandshakeChannel + Unpin, R: RngCore + CryptoRng, { tracing::debug!( @@ -479,14 +515,14 @@ impl NestedLpSession { if attempt > 0 { // Verify outer session is still usable before retry if !outer_client.is_handshake_complete() { - return Err(LpClientError::Transport( + return Err(LpClientError::Other( "Outer session lost during retry - caller must re-establish entry gateway connection".to_string() )); } // 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 jitter_ms: u64 = rand09::rng().random_range(0..(base_delay_ms / 4 + 1)); let delay = std::time::Duration::from_millis(base_delay_ms + jitter_ms); tracing::info!( "Retrying exit registration (attempt {}) after {:?}", @@ -526,24 +562,8 @@ impl NestedLpSession { } } - Err(last_error.unwrap_or_else(|| { - LpClientError::Transport("Exit registration failed after all retries".to_string()) + Err(last_error.unwrap_or(LpClientError::RegistrationFailure { + message: "Exit Registration failed after all retries".to_string(), })) } - - /// Parses an LP packet from bytes. - /// - /// # Arguments - /// * `bytes` - The bytes to parse - /// - /// # Returns - /// * `Ok(LpPacket)` - Parsed LP packet - /// - /// # Errors - /// Returns an error if parsing fails - 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}"))) - } } diff --git a/nym-registration-client/src/lp_client/state_machine_helpers.rs b/nym-registration-client/src/lp_client/state_machine_helpers.rs index b8be242621..3b2a5e7f65 100644 --- a/nym-registration-client/src/lp_client/state_machine_helpers.rs +++ b/nym-registration-client/src/lp_client/state_machine_helpers.rs @@ -2,85 +2,38 @@ // 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}; - -/// 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()) -} +use nym_lp::packet::LpMessage; +use nym_lp::state_machine::{LpAction, LpInput}; +use nym_lp::{LpStateMachine, packet::EncryptedLpPacket}; /// Attempt to prepare the provided data for sending by wrapping it in appropriate `LpAction`, -/// and attempting to extract `LpPacket` from the provided srtate machine. +/// and attempting to extract `EncryptedLpPacket` from the provided state machine. pub(crate) fn prepare_send_packet( - data: LpData, + data: LpMessage, state_machine: &mut LpStateMachine, -) -> Result { +) -> 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}", - )) - })?; + .ok_or(LpClientError::UnexpectedStateMachineHalt)??; match action { LpAction::SendPacket(packet) => Ok(packet), - other => Err(LpClientError::Transport(format!( - "Unexpected action when trying to send packet data: {other:?}", - ))), + action => Err(LpClientError::UnexpectedStateMachineAction { action }), } } -/// 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 = state_machine.session()?.outer_aead_key(); - - serialize_packet(&packet, Some(send_key)) -} - /// Attempt to recover received `LpData` from the received `LpPacket` /// using the provided state machine. pub(crate) fn extract_forwarded_response( - response_packet: LpPacket, + response_packet: EncryptedLpPacket, state_machine: &mut LpStateMachine, -) -> Result { +) -> 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}")) - })?; + .ok_or(LpClientError::UnexpectedStateMachineHalt)??; match action { LpAction::DeliverData(data) => Ok(data), - other => Err(LpClientError::Transport(format!( - "Unexpected action when receiving response: {other:?}" - ))), + action => Err(LpClientError::UnexpectedStateMachineAction { action }), } } diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index dd7d19c7f7..7b049ffd8a 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -1255,6 +1255,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-models" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "hax-lib", + "pastey", + "rand 0.9.2", +] + [[package]] name = "cosmos-sdk-proto" version = "0.27.0" @@ -2601,15 +2611,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", "wasm-bindgen", ] @@ -2871,6 +2881,43 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "hax-lib" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543f93241d32b3f00569201bfce9d7a93c92c6421b23c77864ac929dc947b9fc" +dependencies = [ + "hax-lib-macros", + "num-bigint", + "num-traits", +] + +[[package]] +name = "hax-lib-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8755751e760b11021765bb04cb4a6c4e24742688d9f3aa14c2079638f537b0f" +dependencies = [ + "hax-lib-macros-types", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "hax-lib-macros-types" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f177c9ae8ea456e2f71ff3c1ea47bf4464f772a05133fcbba56cd5ba169035a2" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "uuid", +] + [[package]] name = "heck" version = "0.4.1" @@ -2943,7 +2990,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.0", + "rand 0.9.2", "ring", "rustls 0.23.36", "thiserror 2.0.12", @@ -2968,7 +3015,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.0", + "rand 0.9.2", "resolv-conf", "rustls 0.23.36", "smallvec", @@ -3659,7 +3706,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -3804,6 +3851,240 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libcrux-aesgcm" +version = "0.0.7" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-chacha20poly1305" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-poly1305", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-curve25519" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-ecdh" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-curve25519", + "libcrux-p256", + "rand 0.9.2", + "tls_codec", +] + +[[package]] +name = "libcrux-ed25519" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-sha2", + "rand_core 0.9.3", + "tls_codec", +] + +[[package]] +name = "libcrux-hacl-rs" +version = "0.0.4" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-macros", +] + +[[package]] +name = "libcrux-hkdf" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-hmac", + "libcrux-secrets", +] + +[[package]] +name = "libcrux-hmac" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-sha2", +] + +[[package]] +name = "libcrux-intrinsics" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "core-models", + "hax-lib", +] + +[[package]] +name = "libcrux-kem" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-curve25519", + "libcrux-ecdh", + "libcrux-ml-kem", + "libcrux-p256", + "libcrux-sha3", + "libcrux-traits", + "rand 0.9.2", + "tls_codec", +] + +[[package]] +name = "libcrux-macros" +version = "0.0.3" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "quote", + "syn 2.0.100", +] + +[[package]] +name = "libcrux-ml-dsa" +version = "0.0.7" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "core-models", + "hax-lib", + "libcrux-intrinsics", + "libcrux-macros", + "libcrux-platform", + "libcrux-sha3", + "tls_codec", +] + +[[package]] +name = "libcrux-ml-kem" +version = "0.0.7" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-sha3", + "libcrux-traits", + "rand 0.9.2", + "tls_codec", +] + +[[package]] +name = "libcrux-p256" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-secrets", + "libcrux-sha2", + "libcrux-traits", +] + +[[package]] +name = "libcrux-platform" +version = "0.0.3" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libc", +] + +[[package]] +name = "libcrux-poly1305" +version = "0.0.4" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", +] + +[[package]] +name = "libcrux-psq" +version = "0.0.7" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +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", + "rand 0.9.2", + "tls_codec", +] + +[[package]] +name = "libcrux-secrets" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "hax-lib", +] + +[[package]] +name = "libcrux-sha2" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-traits", +] + +[[package]] +name = "libcrux-sha3" +version = "0.0.7" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-traits", +] + +[[package]] +name = "libcrux-traits" +version = "0.0.6" +source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775" +dependencies = [ + "libcrux-secrets", + "rand 0.9.2", +] + [[package]] name = "libloading" version = "0.7.4" @@ -4356,6 +4637,8 @@ dependencies = [ "curve25519-dalek", "ed25519-dalek", "jwt-simple", + "libcrux-curve25519", + "libcrux-psq", "nym-pemstore", "rand 0.8.5", "serde", @@ -4480,6 +4763,7 @@ name = "nym-kkt-ciphersuite" version = "1.20.4" dependencies = [ "num_enum", + "semver", "strum", "strum_macros", "thiserror 2.0.12", @@ -5242,6 +5526,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + [[package]] name = "pathdiff" version = "0.2.3" @@ -5711,6 +6001,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -5807,8 +6119,8 @@ checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" dependencies = [ "aws-lc-rs", "bytes", - "getrandom 0.3.2", - "rand 0.9.0", + "getrandom 0.3.3", + "rand 0.9.2", "ring", "rustc-hash", "rustls 0.23.36", @@ -5876,13 +6188,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.24", ] [[package]] @@ -5939,7 +6250,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -7638,7 +7949,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix 1.0.5", "windows-sys 0.59.0", @@ -7881,6 +8192,27 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "tokio" version = "1.47.1" @@ -8398,7 +8730,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "serde", ] @@ -8494,11 +8826,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ - "wit-bindgen-rt", + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", ] [[package]] @@ -9408,13 +9749,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.0", -] +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "wl-clipboard-rs" diff --git a/sdk/typescript/packages/mix-fetch-node/package.json b/sdk/typescript/packages/mix-fetch-node/package.json index 6956bd22c0..93cb47e5e6 100644 --- a/sdk/typescript/packages/mix-fetch-node/package.json +++ b/sdk/typescript/packages/mix-fetch-node/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/mix-fetch-node", - "version": "1.4.1", + "version": "1.4.2", "description": "This package is a drop-in replacement for `fetch` in NodeJS to send HTTP requests over the Nym Mixnet.", "license": "Apache-2.0", "author": "Nym Technologies SA", diff --git a/sdk/typescript/packages/mix-fetch/package.json b/sdk/typescript/packages/mix-fetch/package.json index 41969de717..660ce89fba 100644 --- a/sdk/typescript/packages/mix-fetch/package.json +++ b/sdk/typescript/packages/mix-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/mix-fetch", - "version": "1.4.1", + "version": "1.4.2", "description": "This package is a drop-in replacement for `fetch` to send HTTP requests over the Nym Mixnet.", "license": "Apache-2.0", "author": "Nym Technologies SA", diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index abf59b66f3..652a870dd1 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-network-requester" license = "GPL-3.0" -version = "1.1.72" +version = "1.1.73" authors.workspace = true edition.workspace = true rust-version = "1.85" diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index bc102b46bd..e3e448a386 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.71" +version = "1.1.72" authors.workspace = true edition = "2021" license.workspace = true diff --git a/tools/nym-lp-client/Cargo.toml b/tools/nym-lp-client/Cargo.toml index d3e6b08bd5..e91e052662 100644 --- a/tools/nym-lp-client/Cargo.toml +++ b/tools/nym-lp-client/Cargo.toml @@ -15,6 +15,7 @@ anyhow = { workspace = true } bytes = { workspace = true } clap = { workspace = true, features = ["derive"] } rand = { workspace = true } +rand09 = { workspace = true } rand_chacha = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/tools/nym-lp-client/src/client.rs b/tools/nym-lp-client/src/client.rs index 3be0bc18b2..881a781392 100644 --- a/tools/nym-lp-client/src/client.rs +++ b/tools/nym-lp-client/src/client.rs @@ -20,7 +20,8 @@ use nym_sphinx_anonymous_replies::requests::{AnonymousSenderTag, RepliableMessag use nym_sphinx_anonymous_replies::{ReplySurb, SurbEncryptionKey}; use nym_sphinx_framing::codec::NymCodec; use nym_sphinx_framing::packet::FramedNymPacket; -use rand_chacha::rand_core::SeedableRng; +use rand::SeedableRng; +use rand09::SeedableRng as SeedableRng09; use rand_chacha::ChaCha8Rng; use std::net::SocketAddr; use std::sync::Arc; @@ -33,7 +34,7 @@ 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_lp::peer::{DHKeyPair, LpRemotePeer}; use nym_sphinx::forwarding::packet::MixPacket; /// Conv ID for KCP - hash of source and destination addresses @@ -51,6 +52,8 @@ pub struct SpeedtestClient { identity_keypair: Arc, /// Client's x25519 encryption keypair (for SURBs) encryption_keypair: Arc, + /// Client's LP keypair + lp_keypair: Arc, /// Target gateway gateway: GatewayInfo, /// Network topology for routing @@ -86,11 +89,14 @@ impl SpeedtestClient { pub fn new(gateway: GatewayInfo, topology: Arc) -> Self { let identity_keypair = Arc::new(ed25519::KeyPair::new(&mut rand::rngs::OsRng)); let encryption_keypair = Arc::new(x25519::KeyPair::new(&mut rand::rngs::OsRng)); + let mut rng09 = rand09::rngs::StdRng::from_os_rng(); + let lp_keypair = DHKeyPair::new(&mut rng09); let rng = ChaCha8Rng::from_entropy(); Self { identity_keypair, encryption_keypair, + lp_keypair: Arc::new(lp_keypair), gateway, topology, socket: None, @@ -118,16 +124,14 @@ impl SpeedtestClient { self.gateway.lp_address ); - 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 gw_peer = LpRemotePeer::new(self.gateway.lp_key) + .with_key_digests(self.gateway.kem_key_hashes.clone()); let mut lp_client = LpRegistrationClient::::new_with_default_config( - self.identity_keypair.clone(), + self.lp_keypair.clone(), gw_peer, self.gateway.lp_address, + self.gateway.ciphersuite, self.gateway.lp_version, ); @@ -165,16 +169,14 @@ impl SpeedtestClient { self.gateway.lp_address ); - 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 gw_peer = LpRemotePeer::new(self.gateway.lp_key) + .with_key_digests(self.gateway.kem_key_hashes.clone()); let mut lp_client = LpRegistrationClient::new_with_default_config( - self.identity_keypair.clone(), + self.lp_keypair.clone(), gw_peer, self.gateway.lp_address, + self.gateway.ciphersuite, self.gateway.lp_version, ); @@ -440,61 +442,65 @@ impl SpeedtestClient { if !self.has_lp_session() { bail!("LP session not initialized - call init_lp_session() first"); } + let _ = payload; + let _ = num_surbs; + bail!("lp transport channel is not yet implemented"); - let prepared = self.prepare_sphinx_fragments(payload, num_surbs).await?; - - // Now get mutable references after prepare_sphinx_fragments is done - let lp_client = self.lp_client.as_mut().unwrap(); // safe: checked above - let socket = self.socket.as_ref().context("socket not initialized")?; - let lp_data_address = self.gateway.lp_data_address; - - let mut total_sent = 0usize; - let fragment_count = prepared.fragments.len(); - - for fragment in prepared.fragments { - let nym_packet = NymPacket::sphinx_build( - false, - PacketSize::RegularPacket.payload_size(), - fragment.into_bytes(), - &prepared.route, - &prepared.destination, - &prepared.delays, - )?; - - // Wrap in MixPacket v2: packet_type || key_rotation || next_hop || sphinx_data - let mix_packet = MixPacket::new( - prepared.first_hop_addr, - nym_packet, - PacketType::Mix, - SphinxKeyRotation::Unknown, - ); - - let mix_bytes = mix_packet - .into_v2_bytes() - .context("failed to serialize MixPacket")?; - - // Wrap in LP for UDP data plane - let lp_packet = lp_client - .wrap_data(&mix_bytes) - .context("failed to wrap in LP")?; - - // Send to gateway's LP data port (51264) with timeout - tokio::time::timeout( - Duration::from_secs(5), - socket.send_to(&lp_packet, lp_data_address), - ) - .await - .context("UDP send timed out")? - .context("UDP send failed")?; - total_sent += lp_packet.len(); - } - - info!( - "Sent {} bytes via LP ({} fragments, {} SURBs) to {}", - total_sent, fragment_count, num_surbs, lp_data_address - ); - - Ok(prepared.encryption_keys) + // leave the code for future reference + // let prepared = self.prepare_sphinx_fragments(payload, num_surbs).await?; + // + // // Now get mutable references after prepare_sphinx_fragments is done + // let lp_client = self.lp_client.as_mut().unwrap(); // safe: checked above + // let socket = self.socket.as_ref().context("socket not initialized")?; + // let lp_data_address = self.gateway.lp_data_address; + // + // let mut total_sent = 0usize; + // let fragment_count = prepared.fragments.len(); + // + // for fragment in prepared.fragments { + // let nym_packet = NymPacket::sphinx_build( + // false, + // PacketSize::RegularPacket.payload_size(), + // fragment.into_bytes(), + // &prepared.route, + // &prepared.destination, + // &prepared.delays, + // )?; + // + // // Wrap in MixPacket v2: packet_type || key_rotation || next_hop || sphinx_data + // let mix_packet = MixPacket::new( + // prepared.first_hop_addr, + // nym_packet, + // PacketType::Mix, + // SphinxKeyRotation::Unknown, + // ); + // + // let mix_bytes = mix_packet + // .into_v2_bytes() + // .context("failed to serialize MixPacket")?; + // + // // Wrap in LP for UDP data plane + // let lp_packet = lp_client + // .wrap_data(&mix_bytes) + // .context("failed to wrap in LP")?; + // + // // Send to gateway's LP data port (51264) with timeout + // tokio::time::timeout( + // Duration::from_secs(5), + // socket.send_to(&lp_packet, lp_data_address), + // ) + // .await + // .context("UDP send timed out")? + // .context("UDP send failed")?; + // total_sent += lp_packet.len(); + // } + // + // info!( + // "Sent {} bytes via LP ({} fragments, {} SURBs) to {}", + // total_sent, fragment_count, num_surbs, lp_data_address + // ); + // + // Ok(prepared.encryption_keys) } /// Receive UDP data with timeout diff --git a/tools/nym-lp-client/src/topology.rs b/tools/nym-lp-client/src/topology.rs index 3e0f76b6ff..91fccc9b8c 100644 --- a/tools/nym-lp-client/src/topology.rs +++ b/tools/nym-lp-client/src/topology.rs @@ -10,13 +10,14 @@ 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_kkt_ciphersuite::{Ciphersuite, KEMKeyDigests, SignatureScheme, KEM}; +use nym_lp::peer::DHPublicKey; 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::collections::{BTreeMap, HashMap}; use std::net::SocketAddr; use tracing::{debug, info}; use url::Url; @@ -30,8 +31,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 lp_key: DHPublicKey, + pub kem_key_hashes: BTreeMap, + pub ciphersuite: Ciphersuite, pub sphinx_key: nym_crypto::asymmetric::x25519::PublicKey, /// Mix host (IP:port for Sphinx mixing) diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index e5a62b3d63..d03fd308c9 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nymvisor" -version = "0.1.36" +version = "0.1.37" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/wasm/client/.cargo/config.toml b/wasm/client/.cargo/config.toml index a7f24f6737..f506045a7f 100644 --- a/wasm/client/.cargo/config.toml +++ b/wasm/client/.cargo/config.toml @@ -1,6 +1,8 @@ [build] target = "wasm32-unknown-unknown" target_arch = "wasm32" +rustflags = ["--cfg=getrandom_backend=\"wasm_js\""] + [target.wasm32-unknown-unknown] runner = 'wasm-bindgen-test-runner' \ No newline at end of file diff --git a/wasm/client/Cargo.toml b/wasm/client/Cargo.toml index 3bc15e7ade..2377ddcb6d 100644 --- a/wasm/client/Cargo.toml +++ b/wasm/client/Cargo.toml @@ -44,6 +44,7 @@ nym-node-tester-wasm = { path = "../node-tester", optional = true } tokio_with_wasm = { workspace = true, features = ["full"] } + [dev-dependencies] wasm-bindgen-test = { workspace = true } diff --git a/wasm/client/internal-dev/package-lock.json b/wasm/client/internal-dev/package-lock.json index 5652306177..4a51eab2b4 100644 --- a/wasm/client/internal-dev/package-lock.json +++ b/wasm/client/internal-dev/package-lock.json @@ -1408,40 +1408,40 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1461,6 +1461,22 @@ "dev": true, "license": "MIT" }, + "node_modules/express/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/wasm/client/internal-dev/yarn.lock b/wasm/client/internal-dev/yarn.lock index 844562e88a..39801fef91 100644 --- a/wasm/client/internal-dev/yarn.lock +++ b/wasm/client/internal-dev/yarn.lock @@ -445,23 +445,23 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== +body-parser@~1.20.3: + version "1.20.4" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" + integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== dependencies: - bytes "3.1.2" + bytes "~3.1.2" content-type "~1.0.5" debug "2.6.9" depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" + destroy "~1.2.0" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + on-finished "~2.4.1" + qs "~6.14.0" + raw-body "~2.5.3" type-is "~1.6.18" - unpipe "1.0.0" + unpipe "~1.0.0" bonjour-service@^1.0.11: version "1.1.1" @@ -509,9 +509,9 @@ bytes@3.0.0: resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== -bytes@3.1.2: +bytes@~3.1.2: version "3.1.2" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: @@ -609,9 +609,9 @@ connect-history-api-fallback@^2.0.0: resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz" integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== -content-disposition@0.5.4: +content-disposition@~0.5.4: version "0.5.4" - resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: safe-buffer "5.2.1" @@ -621,15 +621,15 @@ content-type@~1.0.4, content-type@~1.0.5: resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie-signature@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454" + integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== -cookie@0.7.1: - version "0.7.1" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz" - integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== +cookie@~0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== copy-webpack-plugin@^11.0.0: version "11.0.0" @@ -683,7 +683,7 @@ define-lazy-prop@^2.0.0: resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -depd@2.0.0: +depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -693,7 +693,7 @@ depd@~1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== -destroy@1.2.0: +destroy@1.2.0, destroy@~1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== @@ -741,11 +741,6 @@ electron-to-chromium@^1.5.263: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - encodeurl@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz" @@ -852,38 +847,38 @@ execa@^5.0.0: strip-final-newline "^2.0.0" express@^4.17.3: - version "4.21.2" - resolved "https://registry.npmjs.org/express/-/express-4.21.2.tgz" - integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== + version "4.22.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069" + integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.3" - content-disposition "0.5.4" + body-parser "~1.20.3" + content-disposition "~0.5.4" content-type "~1.0.4" - cookie "0.7.1" - cookie-signature "1.0.6" + cookie "~0.7.1" + cookie-signature "~1.0.6" debug "2.6.9" depd "2.0.0" encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.3.1" - fresh "0.5.2" - http-errors "2.0.0" + finalhandler "~1.3.1" + fresh "~0.5.2" + http-errors "~2.0.0" merge-descriptors "1.0.3" methods "~1.1.2" - on-finished "2.4.1" + on-finished "~2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.12" + path-to-regexp "~0.1.12" proxy-addr "~2.0.7" - qs "6.13.0" + qs "~6.14.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.2" + send "~0.19.0" + serve-static "~1.16.2" setprototypeof "1.2.0" - statuses "2.0.1" + statuses "~2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" @@ -930,17 +925,17 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== +finalhandler@~1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88" + integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg== dependencies: debug "2.6.9" encodeurl "~2.0.0" escape-html "~1.0.3" - on-finished "2.4.1" + on-finished "~2.4.1" parseurl "~1.3.3" - statuses "2.0.1" + statuses "~2.0.2" unpipe "~1.0.0" find-up@^4.0.0: @@ -961,9 +956,9 @@ forwarded@0.2.0: resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fresh@0.5.2: +fresh@~0.5.2: version "0.5.2" - resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== fs-monkey@^1.0.3: @@ -1121,17 +1116,6 @@ http-deceiver@^1.2.7: resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - http-errors@~1.6.2: version "1.6.3" resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" @@ -1142,6 +1126,17 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-errors@~2.0.0, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + http-parser-js@>=0.5.1: version "0.5.8" resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz" @@ -1172,9 +1167,9 @@ human-signals@^2.1.0: resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -iconv-lite@0.4.24: +iconv-lite@~0.4.24: version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" @@ -1200,7 +1195,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1488,9 +1483,9 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@2.4.1: +on-finished@~2.4.1: version "2.4.1" - resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" @@ -1575,9 +1570,9 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.12: +path-to-regexp@~0.1.12: version "0.1.12" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== path-type@^4.0.0: @@ -1620,12 +1615,12 @@ punycode@^2.1.0: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== -qs@6.13.0: - version "6.13.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== +qs@~6.14.0: + version "6.14.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c" + integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== dependencies: - side-channel "^1.0.6" + side-channel "^1.1.0" queue-microtask@^1.2.2: version "1.2.3" @@ -1644,15 +1639,15 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== +raw-body@~2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" + integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + unpipe "~1.0.0" readable-stream@^2.0.1: version "2.3.8" @@ -1782,24 +1777,24 @@ selfsigned@^2.1.1: dependencies: node-forge "^1" -send@0.19.0: - version "0.19.0" - resolved "https://registry.npmjs.org/send/-/send-0.19.0.tgz" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== +send@~0.19.0, send@~0.19.1: + version "0.19.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29" + integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== dependencies: debug "2.6.9" depd "2.0.0" destroy "1.2.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" + fresh "~0.5.2" + http-errors "~2.0.1" mime "1.6.0" ms "2.1.3" - on-finished "2.4.1" + on-finished "~2.4.1" range-parser "~1.2.1" - statuses "2.0.1" + statuses "~2.0.2" serialize-javascript@^6.0.0, serialize-javascript@^6.0.2: version "6.0.2" @@ -1821,22 +1816,22 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.16.2: - version "1.16.2" - resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== +serve-static@~1.16.2: + version "1.16.3" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9" + integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== dependencies: encodeurl "~2.0.0" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.19.0" + send "~0.19.1" setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== -setprototypeof@1.2.0: +setprototypeof@1.2.0, setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== @@ -1894,9 +1889,9 @@ side-channel-weakmap@^1.0.2: object-inspect "^1.13.3" side-channel-map "^1.0.1" -side-channel@^1.0.6: +side-channel@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== dependencies: es-errors "^1.3.0" @@ -1960,16 +1955,16 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - "statuses@>= 1.4.0 < 2": version "1.5.0" resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== +statuses@~2.0.1, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -2039,9 +2034,9 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.1: +toidentifier@~1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== type-is@~1.6.18: @@ -2052,7 +2047,7 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== diff --git a/wasm/full-nym-wasm/.cargo/config.toml b/wasm/full-nym-wasm/.cargo/config.toml index bdbe517658..e9c3438b6e 100644 --- a/wasm/full-nym-wasm/.cargo/config.toml +++ b/wasm/full-nym-wasm/.cargo/config.toml @@ -1,3 +1,4 @@ [build] target = "wasm32-unknown-unknown" target_arch = "wasm32" +rustflags = ["--cfg=getrandom_backend=\"wasm_js\""] \ No newline at end of file diff --git a/wasm/mix-fetch/.cargo/config.toml b/wasm/mix-fetch/.cargo/config.toml index bdbe517658..e9c3438b6e 100644 --- a/wasm/mix-fetch/.cargo/config.toml +++ b/wasm/mix-fetch/.cargo/config.toml @@ -1,3 +1,4 @@ [build] target = "wasm32-unknown-unknown" target_arch = "wasm32" +rustflags = ["--cfg=getrandom_backend=\"wasm_js\""] \ No newline at end of file diff --git a/wasm/mix-fetch/Cargo.toml b/wasm/mix-fetch/Cargo.toml index f1d81694ad..4ff903254a 100644 --- a/wasm/mix-fetch/Cargo.toml +++ b/wasm/mix-fetch/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mix-fetch-wasm" authors = ["Jedrzej Stuczynski "] -version = "1.4.1" +version = "1.4.2" edition = "2021" keywords = ["nym", "fetch", "wasm", "webassembly", "privacy"] license = "Apache-2.0" diff --git a/wasm/mix-fetch/Makefile b/wasm/mix-fetch/Makefile index 1d6f1d01ab..66a3106377 100644 --- a/wasm/mix-fetch/Makefile +++ b/wasm/mix-fetch/Makefile @@ -10,6 +10,7 @@ build-go-opt: build-rust: taskset -c 0-11 wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/mix-fetch + # taskset -c 0-11 wasm-pack build --scope nymproject --target no-modules --out-dir ../../dist/wasm/mix-fetch taskset -c 0-11 wasm-opt -Oz -o ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm build-rust-debug: diff --git a/wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go b/wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go index 7725be27b6..617cd78842 100644 --- a/wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go +++ b/wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go @@ -142,7 +142,7 @@ func schemeFetch(req *conv.ParsedRequest) error { } } -func dialContext(_ctx context.Context, opts *types.RequestOptions, _network, addr string) (net.Conn, error) { +func dialContext(_ctx context.Context, requestURL string, _network, addr string) (net.Conn, error) { log.Debug("dialing plain connection to %s", addr) requestId, err := rust_bridge.RsStartNewMixnetRequest(addr) @@ -154,12 +154,14 @@ func dialContext(_ctx context.Context, opts *types.RequestOptions, _network, add } conn, inj := state.NewFakeConnection(requestId, addr) - state.ActiveRequests.Insert(requestId, addr, inj) + // Use requestURL (full URL) as the mapping key, meaning we can now + // have concurrent requests to different paths on the same domain. + state.ActiveRequests.Insert(requestId, requestURL, inj) return conn, nil } -func dialTLSContext(_ctx context.Context, opts *types.RequestOptions, _network, addr string) (net.Conn, error) { +func dialTLSContext(_ctx context.Context, requestURL string, _network, addr string) (net.Conn, error) { log.Debug("dialing TLS connection to %s", addr) requestId, err := rust_bridge.RsStartNewMixnetRequest(addr) @@ -171,7 +173,9 @@ func dialTLSContext(_ctx context.Context, opts *types.RequestOptions, _network, } conn, inj := state.NewFakeTlsConn(requestId, addr) - state.ActiveRequests.Insert(requestId, addr, inj) + // Use requestURL (full URL) as the mapping key, meaning we can now + // have concurrent requests to different paths on the same domain. + state.ActiveRequests.Insert(requestId, requestURL, inj) if err := conn.Handshake(); err != nil { return nil, err @@ -180,7 +184,7 @@ func dialTLSContext(_ctx context.Context, opts *types.RequestOptions, _network, return conn, nil } -func buildHttpClient(reqCtx *types.RequestContext, opts *types.RequestOptions) *http.Client { +func buildHttpClient(reqCtx *types.RequestContext, opts *types.RequestOptions, requestURL string) *http.Client { return &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return checkRedirect(reqCtx, opts, req, via) @@ -188,17 +192,19 @@ func buildHttpClient(reqCtx *types.RequestContext, opts *types.RequestOptions) * Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return dialContext(ctx, opts, network, addr) + return dialContext(ctx, requestURL, network, addr) }, DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return dialTLSContext(ctx, opts, network, addr) + return dialTLSContext(ctx, requestURL, network, addr) }, //TLSClientConfig: &tlsConfig, - DisableKeepAlives: true, - MaxIdleConns: 1, - MaxIdleConnsPerHost: 1, - MaxConnsPerHost: 1, + DisableKeepAlives: true, + // Allow multiple concurrent connections to the same host. + // Previously set to 1. + MaxIdleConns: 10, + MaxIdleConnsPerHost: 10, + MaxConnsPerHost: 10, }, Timeout: state.RequestTimeout, } @@ -270,7 +276,7 @@ func doCorsCheck(reqOpts *types.RequestOptions, resp *http.Response) error { return errors.New("failed cors check") } -func performRequest(req *conv.ParsedRequest) (*conv.ResponseWrapper, error) { +func performRequest(req *conv.ParsedRequest, requestURL string) (*conv.ResponseWrapper, error) { err := mainFetchChecks(req) if err != nil { return nil, err @@ -278,7 +284,7 @@ func performRequest(req *conv.ParsedRequest) (*conv.ResponseWrapper, error) { reqCtx := &types.RequestContext{} - reqClient := buildHttpClient(reqCtx, req.Options) + reqClient := buildHttpClient(reqCtx, req.Options, requestURL) if req.Options.ReferrerPolicy == "" { // 4.1.8 @@ -322,12 +328,15 @@ func performRequest(req *conv.ParsedRequest) (*conv.ResponseWrapper, error) { func onErrCleanup(url *url.URL) { // TODO: cancel stuff here.... somehow... - canonicalAddr := canonicalAddr(url) - id := state.ActiveRequests.GetId(canonicalAddr) + // Use full URL string to match the key used in MixFetch for request deduplication. + // Makes sure we clean up the correct request when multiple requests to + // different paths on the same domain are in process. + requestURL := url.String() + id := state.ActiveRequests.GetId(requestURL) // TODO: can we guarantee that rust is not holding any references to that id (that we don't know on this side)? if id == 0 { // if id doesn't exist it [probably] means the error was thrown before the request was properly created - log.Debug("there doesn't seem to exist a request associated with addr %s", canonicalAddr) + log.Debug("there doesn't seem to exist a request associated with URL %s", requestURL) return } state.ActiveRequests.Remove(id) @@ -341,16 +350,20 @@ func onErrCleanup(url *url.URL) { func MixFetch(request *conv.ParsedRequest) (any, error) { log.Info("_mixFetch: start") - canonical := canonicalAddr(request.Request.URL) - if state.ActiveRequests.ExistsCanonical(canonical) { - // TODO: how to deal with it to allow for concurrent requests to say `https://foo.com/index.html` and `https://foo.com/index.js`? - return nil, errors.New(fmt.Sprintf("there is already an active request for %s", canonical)) + // Use the full URL (inc path and query params) as the deduplication key. + // Allows concurrent requests to different paths on the same domain + // (e.g., foo.com/index.html and foo.com/index.js) while still preventing + // duplicate requests to the exact same URL. + requestURL := request.Request.URL.String() + + if state.ActiveRequests.ExistsCanonical(requestURL) { + return nil, errors.New(fmt.Sprintf("there is already an active request for %s", requestURL)) } resCh := make(chan *conv.ResponseWrapper) errCh := make(chan error) go func(resCh chan *conv.ResponseWrapper, errCh chan error) { - resp, err := performRequest(request) + resp, err := performRequest(request, requestURL) if err != nil { errCh <- err } else { diff --git a/wasm/mix-fetch/go-mix-conn/internal/state/state.go b/wasm/mix-fetch/go-mix-conn/internal/state/state.go index a9b99b9630..b2e8b1e32f 100644 --- a/wasm/mix-fetch/go-mix-conn/internal/state/state.go +++ b/wasm/mix-fetch/go-mix-conn/internal/state/state.go @@ -25,17 +25,23 @@ func InitialiseGlobalState() { } } +// CurrentActiveRequests tracks ongoign requests for thread-safe access. +// The AddressMapping uses full URLs (inc path and query params) as keys, +// allowing concurrent requests to different paths on the same domain while +// preventing duplicate requests to the *exact* same URL. type CurrentActiveRequests struct { sync.Mutex Requests map[types.RequestId]*ActiveRequest - AddressMapping map[string]types.RequestId + AddressMapping map[string]types.RequestId // key is full URL string } -func (ar *CurrentActiveRequests) GetId(canonicalAddr string) types.RequestId { - log.Trace("getting id associated with request for %s", canonicalAddr) +// GetId returns the request ID associated with the given URL string. +// The URL should be the full URL including path and query params. +func (ar *CurrentActiveRequests) GetId(requestURL string) types.RequestId { + log.Trace("getting id associated with request for %s", requestURL) ar.Lock() defer ar.Unlock() - return ar.AddressMapping[canonicalAddr] + return ar.AddressMapping[requestURL] } func (ar *CurrentActiveRequests) Exists(id types.RequestId) bool { @@ -46,25 +52,30 @@ func (ar *CurrentActiveRequests) Exists(id types.RequestId) bool { return exists } -func (ar *CurrentActiveRequests) ExistsCanonical(canonicalAddr string) bool { - return ar.GetId(canonicalAddr) != 0 +// ExistsCanonical checks if there's already an active request for the given URL. +// The URL should be the full URL including path and query params. +// Allows concurrent requests to different paths on the same domain. +func (ar *CurrentActiveRequests) ExistsCanonical(requestURL string) bool { + return ar.GetId(requestURL) != 0 } -func (ar *CurrentActiveRequests) Insert(id types.RequestId, canonicalAddr string, inj ConnectionInjector) { - log.Trace("inserting request %d for %s", id, canonicalAddr) +// Insert adds a new active request to the tracking maps. +// The requestURL should be the full URL including path and query params. +func (ar *CurrentActiveRequests) Insert(id types.RequestId, requestURL string, inj ConnectionInjector) { + log.Trace("inserting request %d for %s", id, requestURL) ar.Lock() defer ar.Unlock() _, exists := ar.Requests[id] if exists { panic("attempted to overwrite active connection id") } - _, exists = ar.AddressMapping[canonicalAddr] + _, exists = ar.AddressMapping[requestURL] if exists { - panic("attempted to overwrite active connection canonicalAddr") + panic("attempted to overwrite active connection for URL") } - ar.Requests[id] = &ActiveRequest{injector: inj, canonicalAddr: canonicalAddr} - ar.AddressMapping[canonicalAddr] = id + ar.Requests[id] = &ActiveRequest{injector: inj, requestURL: requestURL} + ar.AddressMapping[requestURL] = id } func (ar *CurrentActiveRequests) Remove(id types.RequestId) { @@ -75,13 +86,13 @@ func (ar *CurrentActiveRequests) Remove(id types.RequestId) { if !exists { panic("attempted to remove active connection id that doesn't exist") } - _, exists = ar.AddressMapping[req.canonicalAddr] + _, exists = ar.AddressMapping[req.requestURL] if !exists { - panic("attempted to remove active connection canonicalAddr that doesn't exist") + panic("attempted to remove active connection URL that doesn't exist") } delete(ar.Requests, id) - delete(ar.AddressMapping, req.canonicalAddr) + delete(ar.AddressMapping, req.requestURL) } func (ar *CurrentActiveRequests) InjectData(id types.RequestId, data []byte) { @@ -119,6 +130,6 @@ func (ar *CurrentActiveRequests) SendError(id types.RequestId, err error) { } type ActiveRequest struct { - injector ConnectionInjector - canonicalAddr string + injector ConnectionInjector + requestURL string // Full URL including path and query params } diff --git a/wasm/mix-fetch/internal-dev/index.html b/wasm/mix-fetch/internal-dev/index.html index ffe5047581..558751a743 100644 --- a/wasm/mix-fetch/internal-dev/index.html +++ b/wasm/mix-fetch/internal-dev/index.html @@ -1,27 +1,102 @@ - - - - + + + Nym MixFetch Demo - + - -

Mix Fetch Demo

-
- - - -
+ +

Mix Fetch Demo

+
+ MixFetch Configuration +

+ You can either use the default Gateway/NR combo run by us, or have + MixFetch choose a random one on startup. +

+
+ +
+
+ +
+ + Not started +
-
-

- -

+
- +
+ Fetch Controls +
+ + + +
+
+ + + +
+

+ Note: if you're hammering these endpoints and you start to get timeouts + (or you've been reloading the page a lot) then change the target hosts + to e.g. http://ipv4.icanhazip.com and https://ipv6.icanhazip.com/ +

+
+ +

+ This does what is says on the tin - sends 10 fetch requests to + https://jsonplaceholder.typicode.com/posts/ 1 through 10 +

+
+
+
+ + +
+
+ + +
+
+ +
+

Do a POST and get it echoed back

+
- \ No newline at end of file +
+

+ +

+ + diff --git a/wasm/mix-fetch/internal-dev/index.js b/wasm/mix-fetch/internal-dev/index.js index 20c4a6894d..e2b614ab23 100644 --- a/wasm/mix-fetch/internal-dev/index.js +++ b/wasm/mix-fetch/internal-dev/index.js @@ -25,11 +25,36 @@ class WebWorkerClient { const { rawString } = ev.data.args; displayReceivedRawString(rawString) break; + case 'Log': + const { message, level } = ev.data.args; + displayLog(message, level); + break; + case 'MixFetchReady': + onMixFetchReady(); + break; + case 'MixFetchError': + const { error } = ev.data.args; + onMixFetchError(error); + break; } } }; } + startMixFetch = (preferredGateway) => { + if (!this.worker) { + console.error('Could not send message because worker does not exist'); + return; + } + + this.worker.postMessage({ + kind: 'StartMixFetch', + args: { + preferredGateway, + }, + }); + } + doFetch = (target) => { if (!this.worker) { console.error('Could not send message because worker does not exist'); @@ -43,25 +68,140 @@ class WebWorkerClient { }, }); } + + doPost = (url, body) => { + if (!this.worker) { + console.error('Could not send message because worker does not exist'); + return; + } + + this.worker.postMessage({ + kind: 'PostPayload', + args: { + url, + body, + }, + }); + } } let client = null; +const DEFAULT_GATEWAY = "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1"; + async function main() { client = new WebWorkerClient(); - const fetchButton = document.querySelector('#fetch-button'); - fetchButton.onclick = function () { - doFetch(); + const startButton = document.querySelector('#start-mixfetch'); + startButton.onclick = function () { + const gatewayMode = document.querySelector('input[name="gateway-mode"]:checked').value; + const preferredGateway = gatewayMode === 'default' ? DEFAULT_GATEWAY : undefined; + + startButton.disabled = true; + document.querySelectorAll('input[name="gateway-mode"]').forEach(r => r.disabled = true); + updateStatus('Starting...', 'orange'); + + displayLog(`Starting MixFetch with ${gatewayMode} gateway${preferredGateway ? ` (${preferredGateway})` : ''}...`, 'info'); + client.startMixFetch(preferredGateway); + } + + const fetchButton1 = document.querySelector('#fetch-button-1'); + fetchButton1.onclick = function () { + doFetch(1); + } + + const fetchButton2 = document.querySelector('#fetch-button-2'); + fetchButton2.onclick = function () { + doFetch(2); + } + + const fetch10Button = document.querySelector('#fetch-10-concurrent'); + fetch10Button.onclick = function () { + doFetch10Concurrent(); + } + + const postButton = document.querySelector('#post-button'); + postButton.onclick = function () { + doPost(); } } +function updateStatus(text, color) { + const status = document.getElementById('mixfetch-status'); + status.textContent = text; + status.style.color = color; +} -async function doFetch() { - const payload = document.getElementById('fetch_payload').value; +function onMixFetchReady() { + updateStatus('Ready', 'green'); + document.getElementById('fetch-controls').disabled = false; + displayLog('MixFetch is ready!', 'info'); +} + +function onMixFetchError(error) { + updateStatus('Error: ' + error, 'red'); + document.querySelector('#start-mixfetch').disabled = false; + document.querySelectorAll('input[name="gateway-mode"]').forEach(r => r.disabled = false); + displayLog('MixFetch error: ' + error, 'error'); +} + + +async function doFetch(id) { + const payload = document.getElementById(`fetch_payload_${id}`).value; await client.doFetch(payload) - displaySend(`clicked the button and the payload is: ${payload}...`); + displaySend(`[${id}] clicked the button and the payload is: ${payload}...`); +} + +async function doFetch10Concurrent() { + const baseUrl = 'https://jsonplaceholder.typicode.com/posts/'; + displaySend('Starting 10 concurrent requests to posts/1-10...'); + + const requests = []; + for (let i = 1; i <= 10; i++) { + const url = `${baseUrl}${i}`; + displaySend(`[${i}] Sending request to ${url}`); + requests.push(client.doFetch(url)); + } + + await Promise.all(requests); + displaySend('All 10 concurrent requests dispatched!'); +} + +async function doPost() { + const url = document.getElementById('post_url').value; + const body = document.getElementById('post_body').value; + + displaySend(`[POST] Sending POST request to ${url}`); + displaySend(`[POST] Body: ${body}`); + + await client.doPost(url, body); +} + +/** + * Display log messages from MixFetch. Colors based on level. + * + * @param {string} message + * @param {string} level - 'info', 'error', 'warn', or 'debug' + */ +function displayLog(message, level) { + let timestamp = new Date().toISOString().substr(11, 12); + + const colors = { + info: 'gray', + error: 'red', + warn: 'orange', + debug: 'purple', + }; + + let logDiv = document.createElement('div'); + let paragraph = document.createElement('p'); + paragraph.setAttribute('style', `color: ${colors[level] || 'gray'}`); + let paragraphContent = document.createTextNode(timestamp + ' [' + level.toUpperCase() + '] ' + message); + paragraph.appendChild(paragraphContent); + + logDiv.appendChild(paragraph); + document.getElementById('output').appendChild(logDiv); } /** diff --git a/wasm/mix-fetch/internal-dev/package-lock.json b/wasm/mix-fetch/internal-dev/package-lock.json index afe79b3098..d46f8e3e42 100644 --- a/wasm/mix-fetch/internal-dev/package-lock.json +++ b/wasm/mix-fetch/internal-dev/package-lock.json @@ -18,18 +18,16 @@ "devDependencies": { "copy-webpack-plugin": "^11.0.0", "hello-wasm-pack": "^0.1.0", - "webpack": "^5.105.0", - "webpack-cli": "^4.9.2", + "webpack": "^5.98.0", + "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.2.1" } }, - "../go-mix-conn/build": { - "name": "go-mix-conn", - "version": "0.0.1" - }, + "../go-mix-conn/build": {}, "../pkg": { "name": "@nymproject/mix-fetch-wasm", - "version": "1.2.2" + "version": "1.4.2", + "license": "Apache-2.0" }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", @@ -108,17 +106,369 @@ "tslib": "2" } }, - "node_modules/@jsonjoy.com/json-pack": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", - "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz", + "integrity": "sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/base64": "^1.1.1", - "@jsonjoy.com/util": "^1.1.2", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz", + "integrity": "sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz", + "integrity": "sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-print": "4.56.10", + "@jsonjoy.com/fs-snapshot": "4.56.10", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz", + "integrity": "sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz", + "integrity": "sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz", + "integrity": "sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.56.10" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz", + "integrity": "sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.56.10", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz", + "integrity": "sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", "hyperdyperid": "^1.2.0", - "thingies": "^1.20.0" + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" }, "engines": { "node": ">=10.0" @@ -132,9 +482,30 @@ } }, "node_modules/@jsonjoy.com/util": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", - "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -155,6 +526,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -197,6 +581,165 @@ "resolved": "../pkg", "link": true }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", + "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", + "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", + "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", + "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", + "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", + "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pfx": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", + "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", + "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", + "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -269,22 +812,22 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", "dev": true, "license": "MIT", "dependencies": { @@ -302,9 +845,9 @@ "license": "MIT" }, "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "dev": true, "license": "MIT", "dependencies": { @@ -326,20 +869,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.15.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", - "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "undici-types": "~7.18.0" } }, "node_modules/@types/qs": { @@ -364,13 +900,12 @@ "license": "MIT" }, "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, @@ -385,15 +920,26 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", - "@types/send": "*" + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, "node_modules/@types/sockjs": { @@ -578,37 +1124,45 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, "license": "MIT", - "dependencies": { - "envinfo": "^7.7.3" + "engines": { + "node": ">=14.15.0" }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -644,10 +1198,20 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -671,16 +1235,16 @@ } }, "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -745,14 +1309,39 @@ "node": ">= 8" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/batch": { @@ -776,40 +1365,30 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", @@ -892,15 +1471,25 @@ } }, "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -933,9 +1522,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "version": "1.0.30001774", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", + "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", "dev": true, "funding": [ { @@ -992,9 +1581,9 @@ } }, "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "license": "MIT", "engines": { @@ -1017,9 +1606,9 @@ } }, "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, "license": "MIT" }, @@ -1044,31 +1633,24 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", @@ -1103,9 +1685,9 @@ } }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -1113,9 +1695,9 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "dev": true, "license": "MIT" }, @@ -1152,9 +1734,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -1177,9 +1759,9 @@ } }, "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "dev": true, "license": "MIT", "dependencies": { @@ -1194,9 +1776,9 @@ } }, "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "dev": true, "license": "MIT", "engines": { @@ -1296,9 +1878,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true, "license": "ISC" }, @@ -1327,9 +1909,9 @@ } }, "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", "dev": true, "license": "MIT", "bin": { @@ -1517,29 +2099,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/express/node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1548,9 +2107,9 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -1558,7 +2117,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -1577,6 +2136,23 @@ "node": ">= 6" } }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -1588,9 +2164,9 @@ } }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -1624,18 +2200,18 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -1656,10 +2232,20 @@ "node": ">=8" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -1774,6 +2360,23 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -1832,19 +2435,6 @@ "dev": true, "license": "MIT" }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1941,26 +2531,30 @@ "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", "dev": true, "license": "MIT" }, @@ -2028,9 +2622,9 @@ } }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -2038,9 +2632,9 @@ } }, "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "license": "MIT", "dependencies": { @@ -2065,19 +2659,19 @@ "license": "ISC" }, "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", "dev": true, "license": "MIT", "engines": { @@ -2098,13 +2692,16 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2169,9 +2766,9 @@ } }, "node_modules/is-network-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", - "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", "dev": true, "license": "MIT", "engines": { @@ -2218,9 +2815,9 @@ } }, "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, "license": "MIT", "dependencies": { @@ -2297,14 +2894,14 @@ } }, "node_modules/launch-editor": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", - "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.0.tgz", + "integrity": "sha512-u+9asUHMJ99lA15VRMXw5XKfySFR9dGXwgsgS14YTbUq3GITP58mIM32At90P5fZ+MUId5Yw+IwI/yKub7jnCQ==", "dev": true, "license": "MIT", "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" } }, "node_modules/loader-runner": { @@ -2355,23 +2952,33 @@ } }, "node_modules/memfs": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", - "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.56.10.tgz", + "integrity": "sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/json-pack": "^1.0.3", - "@jsonjoy.com/util": "^1.3.0", - "tree-dump": "^1.0.1", + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-fsa": "4.56.10", + "@jsonjoy.com/fs-node": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-to-fsa": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-print": "4.56.10", + "@jsonjoy.com/fs-snapshot": "4.56.10", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", "tslib": "^2.0.0" }, - "engines": { - "node": ">= 4.0.0" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, "node_modules/merge-descriptors": { @@ -2412,13 +3019,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -2490,9 +3097,9 @@ } }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true, "license": "MIT", "engines": { @@ -2506,16 +3113,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-forge": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", - "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", - "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -2567,9 +3164,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "engines": { @@ -2577,16 +3174,16 @@ } }, "node_modules/open": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", - "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", "dev": true, "license": "MIT", "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" + "wsl-utils": "^0.1.0" }, "engines": { "node": ">=18" @@ -2739,6 +3336,24 @@ "node": ">=8" } }, + "node_modules/pkijs": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", + "integrity": "sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2770,24 +3385,34 @@ "node": ">= 0.10" } }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=16.0.0" } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -2838,31 +3463,21 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -2892,18 +3507,25 @@ } }, "node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "license": "MIT", "dependencies": { - "resolve": "^1.9.0" + "resolve": "^1.20.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -2922,19 +3544,22 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2973,9 +3598,9 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -2984,9 +3609,9 @@ } }, "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, "license": "MIT", "engines": { @@ -3076,54 +3701,44 @@ "license": "MIT" }, "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", "dev": true, "license": "MIT", "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3142,22 +3757,26 @@ } }, "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.4", + "accepts": "~1.3.8", "batch": "0.6.1", "debug": "2.6.9", "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" }, "engines": { "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-index/node_modules/depd": { @@ -3171,35 +3790,22 @@ } }, "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dev": true, "license": "MIT", "dependencies": { "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.6" } }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true, - "license": "ISC" - }, "node_modules/serve-index/node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -3211,16 +3817,16 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "dev": true, "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -3437,13 +4043,13 @@ } }, "node_modules/spdy-transport/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3455,20 +4061,20 @@ } }, "node_modules/spdy-transport/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/spdy/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3480,16 +4086,16 @@ } }, "node_modules/spdy/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { @@ -3604,14 +4210,18 @@ } }, "node_modules/thingies": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", - "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", + "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", "dev": true, - "license": "Unlicense", + "license": "MIT", "engines": { "node": ">=10.18" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, "peerDependencies": { "tslib": "^2" } @@ -3647,9 +4257,9 @@ } }, "node_modules/tree-dump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", - "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3670,6 +4280,26 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -3684,6 +4314,13 @@ "node": ">= 0.6" } }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -3725,16 +4362,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -3797,9 +4424,9 @@ } }, "node_modules/webpack": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", - "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "version": "5.105.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz", + "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==", "dev": true, "license": "MIT", "dependencies": { @@ -3846,45 +4473,43 @@ } }, "node_modules/webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "license": "MIT", "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", "colorette": "^2.0.14", - "commander": "^7.0.0", + "commander": "^10.0.1", "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x" + "webpack": "5.x.x" }, "peerDependenciesMeta": { "@webpack-cli/generators": { "optional": true }, - "@webpack-cli/migrate": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -3894,25 +4519,25 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=14" } }, "node_modules/webpack-dev-middleware": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", - "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", "dev": true, "license": "MIT", "dependencies": { "colorette": "^2.0.10", - "memfs": "^4.6.0", - "mime-types": "^2.1.31", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "schema-utils": "^4.0.0" @@ -3933,16 +4558,43 @@ } } }, + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/webpack-dev-server": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.1.tgz", - "integrity": "sha512-ml/0HIj9NLpVKOMq+SuBPLHcmbG+TGIjXRHsYfZwocUBIqEvws8NnS/V9AFQ5FKP+tgn5adwVwRrTEpGL33QFQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", + "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", "dev": true, "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", - "@types/express": "^4.17.21", + "@types/express": "^4.17.25", "@types/express-serve-static-core": "^4.17.21", "@types/serve-index": "^1.9.4", "@types/serve-static": "^1.15.5", @@ -3952,17 +4604,17 @@ "bonjour-service": "^1.2.1", "chokidar": "^3.6.0", "colorette": "^2.0.10", - "compression": "^1.7.4", + "compression": "^1.8.1", "connect-history-api-fallback": "^2.0.0", - "express": "^4.21.2", + "express": "^4.22.1", "graceful-fs": "^4.2.6", - "http-proxy-middleware": "^2.0.7", + "http-proxy-middleware": "^2.0.9", "ipaddr.js": "^2.1.0", "launch-editor": "^2.6.1", "open": "^10.0.3", "p-retry": "^6.2.0", "schema-utils": "^4.2.0", - "selfsigned": "^2.4.1", + "selfsigned": "^5.5.0", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", @@ -3992,13 +4644,14 @@ } }, "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", + "flat": "^5.0.2", "wildcard": "^2.0.0" }, "engines": { @@ -4006,9 +4659,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "dev": true, "license": "MIT", "engines": { @@ -4057,16 +4710,16 @@ } }, "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true, "license": "MIT" }, "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "dev": true, "license": "MIT", "engines": { @@ -4084,6 +4737,22 @@ "optional": true } } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/wasm/mix-fetch/internal-dev/package.json b/wasm/mix-fetch/internal-dev/package.json index 6eb75e1998..7f837369f4 100644 --- a/wasm/mix-fetch/internal-dev/package.json +++ b/wasm/mix-fetch/internal-dev/package.json @@ -30,8 +30,8 @@ "devDependencies": { "copy-webpack-plugin": "^11.0.0", "hello-wasm-pack": "^0.1.0", - "webpack": "^5.105.0", - "webpack-cli": "^4.9.2", + "webpack": "^5.98.0", + "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.2.1" }, "dependencies": { diff --git a/wasm/mix-fetch/internal-dev/webpack.config.js b/wasm/mix-fetch/internal-dev/webpack.config.js index 1cf7d49e34..dbf1df203a 100644 --- a/wasm/mix-fetch/internal-dev/webpack.config.js +++ b/wasm/mix-fetch/internal-dev/webpack.config.js @@ -1,37 +1,36 @@ -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const path = require('path'); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const path = require("path"); module.exports = { - performance: { - hints: false, - maxEntrypointSize: 512000, - maxAssetSize: 512000 - }, - entry: { - bootstrap: './bootstrap.js', - worker: './worker.js', - }, - output: { - path: path.resolve(__dirname, 'dist'), - filename: '[name].js', - }, - mode: 'development', - // mode: 'production', - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - 'index.html', - { - from: '../pkg/*.(js|wasm)', - to: '[name][ext]', - }, - { - from: '../go-mix-conn/build/*.(js|wasm)', - to: '[name][ext]', - }, - ], - }), - - ], - experiments: { syncWebAssembly: true }, + performance: { + hints: false, + maxEntrypointSize: 512000, + maxAssetSize: 512000, + }, + entry: { + bootstrap: "./bootstrap.js", + worker: "./worker.js", + }, + output: { + path: path.resolve(__dirname, "dist"), + filename: "[name].js", + }, + mode: "development", + // mode: 'production', + plugins: [ + new CopyWebpackPlugin({ + patterns: [ + "index.html", + { + from: "../pkg/*.(js|wasm)", + to: "[name][ext]", + }, + { + from: "../go-mix-conn/build/*.(js|wasm)", + to: "[name][ext]", + }, + ], + }), + ], + experiments: { syncWebAssembly: true }, }; diff --git a/wasm/mix-fetch/internal-dev/worker.js b/wasm/mix-fetch/internal-dev/worker.js index b420bd8034..3ccc2cb5a6 100644 --- a/wasm/mix-fetch/internal-dev/worker.js +++ b/wasm/mix-fetch/internal-dev/worker.js @@ -43,6 +43,25 @@ let client = null; let tester = null; const go = new Go(); // Defined in wasm_exec.js var goWasm; +let mixFetchReady = false; + +function sendLog(message, level = 'info') { + self.postMessage({ + kind: 'Log', + args: { message, level }, + }); +} + +function sendReady() { + self.postMessage({ kind: 'MixFetchReady' }); +} + +function sendError(error) { + self.postMessage({ + kind: 'MixFetchError', + args: { error: String(error) }, + }); +} async function logFetchResult(res) { console.log(res) @@ -121,114 +140,117 @@ async function wasm_bindgenSetup() { // await setupMixFetchWithConfig(config, { storagePassphrase: "foomp", preferredGateway }) } -async function nativeSetup() { - const preferredGateway = "8ookuLkA9oWfRTjb7Jq4tLGcWrqoXKGQxw84MjMrv2S4"; - const validator = 'https://sandbox-nym-api1.nymtech.net/api'; +async function nativeSetup(preferredGateway) { + sendLog('Setting up MixFetch...'); + if (preferredGateway) { + sendLog(`Using preferred gateway: ${preferredGateway}`); + } else { + sendLog('Using random gateway selection'); + } - // local - // const preferredNetworkRequester= "2o47bhnXWna6VEyt4mXMGQQAbXfpKmX7BkjkxUz8uQVi.6uQGnCqSczpXwh86NdbsCoDDXuqZQM9Uwko8GE7uC9g8@6qQYb4ArXANU6HJDxzH4PFCUqYb39Dae2Gem2KpxescM"; - // const preferredNetworkRequester= "GqiGWmKRCbGQFSqH88BzLKijvZgipnqhmbNFsmkZw84t.4L8sXFuAUyUYyHZYgMdM3AtiusKnYUft6Pd8e41rrCHA@6qQYb4ArXANU6HJDxzH4PFCUqYb39Dae2Gem2KpxescM"; - - // those are just some examples, there are obviously more permutations; - // note, the extra optional argument is of the following type: - /* - export interface MixFetchOpts extends MixFetchOptsSimple { - clientId?: string; - nymApiUrl?: string; - nyxdUrl?: string; - clientOverride?: DebugWasmOverride; - mixFetchOverride?: MixFetchDebugOverride; - } - - where - export interface MixFetchDebugOverride { - requestTimeoutMs?: number; - } - - and `DebugWasmOverride` is a rather nested struct that you can look up yourself : ) - */ - - // #1 - // await setupMixFetch(preferredNetworkRequester) - // #2 - // await setupMixFetch(preferredNetworkRequester, { nymApiUrl: validator }) - // // #3 const noCoverTrafficOverride = { traffic: {disableMainPoissonPacketDistribution: true}, coverTraffic: {disableLoopCoverTrafficStream: true}, } const mixFetchOverride = { - requestTimeoutMs: 20000 + requestTimeoutMs: 60000 } - await setupMixFetch({ - // preferredNetworkRequester, - // preferredGateway: preferredGateway, - // storagePassphrase: "foomp", + const opts = { forceTls: true, - // nymApiUrl: validator, clientId: "my-client", clientOverride: noCoverTrafficOverride, mixFetchOverride, - }) + }; + + if (preferredGateway) { + opts.preferredGateway = preferredGateway; + } + + sendLog('Calling setupMixFetch...'); + await setupMixFetch(opts); + sendLog('setupMixFetch completed'); } -async function testMixFetch() { - console.log('Instantiating Mix Fetch...'); - // await wasm_bindgenSetup() +async function startMixFetch(preferredGateway) { + sendLog('Instantiating MixFetch...'); - await nativeSetup() + try { + await nativeSetup(preferredGateway); + mixFetchReady = true; + sendLog('MixFetch client running!'); + sendReady(); + } catch (e) { + sendLog('Failed to start MixFetch: ' + e, 'error'); + sendError(e); + } +} +async function handleFetchPayload(target) { + if (!mixFetchReady) { + sendLog('MixFetch not ready yet', 'error'); + return; + } - console.log('Mix Fetch client running!'); + const url = target; + const args = {mode: "unsafe-ignore-cors"}; - // Set callback to handle messages passed to the worker. + try { + sendLog(`Fetching: ${url}`); + const mixFetchRes = await mixFetch(url, args); + sendLog('Fetch completed'); + await logFetchResult(mixFetchRes); + } catch (e) { + sendLog('Fetch request failure: ' + e, 'error'); + console.error("mix fetch request failure: ", e); + } +} + +async function handlePostPayload(url, body) { + if (!mixFetchReady) { + sendLog('MixFetch not ready yet', 'error'); + return; + } + + const args = { + method: 'POST', + mode: "unsafe-ignore-cors", + headers: { + 'Content-Type': 'application/json', + }, + body: body, + }; + + try { + sendLog(`POST request to: ${url}`); + sendLog(`POST body: ${body}`); + const mixFetchRes = await mixFetch(url, args); + sendLog('POST completed'); + await logFetchResult(mixFetchRes); + } catch (e) { + sendLog('POST request failure: ' + e, 'error'); + console.error("mix fetch POST request failure: ", e); + } +} + +function setupMessageHandler() { self.onmessage = async event => { if (event.data && event.data.kind) { switch (event.data.kind) { + case 'StartMixFetch': { + const { preferredGateway } = event.data.args; + await startMixFetch(preferredGateway); + break; + } case 'FetchPayload': { - const {target} = event.data.args; - const url = target; - - // const args = { mode: "ors", redirect: "manual", signal } - const args = {mode: "unsafe-ignore-cors"} - - try { - console.log('using mixFetch...'); - const mixFetchRes = await mixFetch(url, args) - console.log(">>> MIX FETCH") - await logFetchResult(mixFetchRes) - - console.log('done') - - } catch (e) { - console.error("mix fetch request failure: ", e) - } - - // console.log("will disconnect"); - // await disconnectMixFetch() - // - // try { - // console.log('using mixFetch...'); - // const mixFetchRes = await mixFetch(url, args) - // console.log(">>> MIX FETCH") - // await logFetchResult(mixFetchRes) - // - // console.log('done') - // - // } catch(e) { - // console.error("mix fetch request failure: ", e) - // } - - - // try { - // console.log('using normal Fetch...'); - // const fetchRes = await fetch(url, args) - // console.log(">>> NORMAL FETCH") - // await logFetchResult(fetchRes) - // } catch(e) { - // console.error("fetch request failure: ", e) - // } + const { target } = event.data.args; + await handleFetchPayload(target); + break; + } + case 'PostPayload': { + const { url, body } = event.data.args; + await handlePostPayload(url, body); + break; } } } @@ -263,15 +285,17 @@ function setupRsGoBridge() { } async function main() { - console.log(">>>>>>>>>>>>>>>>>>>>> JS WORKER MAIN START"); + sendLog('Worker starting...'); // load rust WASM package + sendLog('Loading Rust WASM...'); await wasm_bindgen(RUST_WASM_URL); - console.log('Loaded RUST WASM'); + sendLog('Loaded Rust WASM'); // load go WASM package + sendLog('Loading Go WASM...'); await loadGoWasm(); - console.log("Loaded GO WASM"); + sendLog('Loaded Go WASM'); // sets up better stack traces in case of in-rust panics set_panic_hook(); @@ -280,19 +304,10 @@ async function main() { goWasmSetLogging("trace") - // test mixFetch - await testMixFetch(); - // - // // run test on simplified and dedicated tester: - // // await testWithTester() - // - // // hook-up the whole client for testing - // // await testWithNymClient() - // - // // 'Normal' client setup (to send 'normal' messages) - // // await normalNymClientUsage() - // - console.log(">>>>>>>>>>>>>>>>>>>>> JS WORKER MAIN END") + // Set up message handler (MixFetch will be started on demand) + setupMessageHandler(); + + sendLog('Worker ready - click Start MixFetch to begin'); } // Let's get started! diff --git a/wasm/mix-fetch/internal-dev/yarn.lock b/wasm/mix-fetch/internal-dev/yarn.lock index 484b776f5b..53c976c49e 100644 --- a/wasm/mix-fetch/internal-dev/yarn.lock +++ b/wasm/mix-fetch/internal-dev/yarn.lock @@ -8,74 +8,213 @@ integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== "@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + version "0.3.13" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - "@jridgewell/source-map@^0.3.3": - version "0.3.6" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" - integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + version "0.3.11" + resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz" + integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== dependencies: "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + version "0.3.31" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jsonjoy.com/base64@^1.1.1": +"@jsonjoy.com/base64@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" + resolved "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz" integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== -"@jsonjoy.com/json-pack@^1.0.3": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz#e658900e81d194903171c42546e1aa27f446846a" - integrity sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA== - dependencies: - "@jsonjoy.com/base64" "^1.1.1" - "@jsonjoy.com/util" "^1.1.2" - hyperdyperid "^1.2.0" - thingies "^1.20.0" +"@jsonjoy.com/base64@17.67.0": + version "17.67.0" + resolved "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz" + integrity sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw== -"@jsonjoy.com/util@^1.1.2", "@jsonjoy.com/util@^1.3.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.6.0.tgz#23991b2fe12cb3a006573d9dc97c768d3ed2c9f1" - integrity sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A== +"@jsonjoy.com/buffers@^1.0.0": + version "1.2.1" + resolved "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz" + integrity sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA== + +"@jsonjoy.com/buffers@^1.2.0": + version "1.2.1" + resolved "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz" + integrity sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA== + +"@jsonjoy.com/buffers@^17.65.0", "@jsonjoy.com/buffers@17.67.0": + version "17.67.0" + resolved "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz" + integrity sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw== + +"@jsonjoy.com/codegen@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz" + integrity sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g== + +"@jsonjoy.com/codegen@17.67.0": + version "17.67.0" + resolved "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz" + integrity sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q== + +"@jsonjoy.com/fs-core@4.56.10": + version "4.56.10" + resolved "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz" + integrity sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw== + dependencies: + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + thingies "^2.5.0" + +"@jsonjoy.com/fs-fsa@4.56.10": + version "4.56.10" + resolved "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz" + integrity sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + thingies "^2.5.0" + +"@jsonjoy.com/fs-node-builtins@4.56.10": + version "4.56.10" + resolved "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz" + integrity sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw== + +"@jsonjoy.com/fs-node-to-fsa@4.56.10": + version "4.56.10" + resolved "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz" + integrity sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw== + dependencies: + "@jsonjoy.com/fs-fsa" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + +"@jsonjoy.com/fs-node-utils@4.56.10": + version "4.56.10" + resolved "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz" + integrity sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg== + dependencies: + "@jsonjoy.com/fs-node-builtins" "4.56.10" + +"@jsonjoy.com/fs-node@4.56.10": + version "4.56.10" + resolved "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz" + integrity sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/fs-print" "4.56.10" + "@jsonjoy.com/fs-snapshot" "4.56.10" + glob-to-regex.js "^1.0.0" + thingies "^2.5.0" + +"@jsonjoy.com/fs-print@4.56.10": + version "4.56.10" + resolved "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz" + integrity sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw== + dependencies: + "@jsonjoy.com/fs-node-utils" "4.56.10" + tree-dump "^1.1.0" + +"@jsonjoy.com/fs-snapshot@4.56.10": + version "4.56.10" + resolved "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz" + integrity sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g== + dependencies: + "@jsonjoy.com/buffers" "^17.65.0" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/json-pack" "^17.65.0" + "@jsonjoy.com/util" "^17.65.0" + +"@jsonjoy.com/json-pack@^1.11.0": + version "1.21.0" + resolved "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz" + integrity sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg== + dependencies: + "@jsonjoy.com/base64" "^1.1.2" + "@jsonjoy.com/buffers" "^1.2.0" + "@jsonjoy.com/codegen" "^1.0.0" + "@jsonjoy.com/json-pointer" "^1.0.2" + "@jsonjoy.com/util" "^1.9.0" + hyperdyperid "^1.2.0" + thingies "^2.5.0" + tree-dump "^1.1.0" + +"@jsonjoy.com/json-pack@^17.65.0": + version "17.67.0" + resolved "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz" + integrity sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w== + dependencies: + "@jsonjoy.com/base64" "17.67.0" + "@jsonjoy.com/buffers" "17.67.0" + "@jsonjoy.com/codegen" "17.67.0" + "@jsonjoy.com/json-pointer" "17.67.0" + "@jsonjoy.com/util" "17.67.0" + hyperdyperid "^1.2.0" + thingies "^2.5.0" + tree-dump "^1.1.0" + +"@jsonjoy.com/json-pointer@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz" + integrity sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg== + dependencies: + "@jsonjoy.com/codegen" "^1.0.0" + "@jsonjoy.com/util" "^1.9.0" + +"@jsonjoy.com/json-pointer@17.67.0": + version "17.67.0" + resolved "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz" + integrity sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA== + dependencies: + "@jsonjoy.com/util" "17.67.0" + +"@jsonjoy.com/util@^1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz" + integrity sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ== + dependencies: + "@jsonjoy.com/buffers" "^1.0.0" + "@jsonjoy.com/codegen" "^1.0.0" + +"@jsonjoy.com/util@^17.65.0", "@jsonjoy.com/util@17.67.0": + version "17.67.0" + resolved "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz" + integrity sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew== + dependencies: + "@jsonjoy.com/buffers" "17.67.0" + "@jsonjoy.com/codegen" "17.67.0" "@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + version "2.0.5" + resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz" + integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== + +"@noble/hashes@1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -85,7 +224,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -99,169 +238,295 @@ fastq "^1.6.0" "@nymproject/mix-fetch-wasm@file:../pkg": - version "1.2.2" + version "1.4.2" + resolved "file:../pkg" + +"@peculiar/asn1-cms@^2.6.0", "@peculiar/asn1-cms@^2.6.1": + version "2.6.1" + resolved "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz" + integrity sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + "@peculiar/asn1-x509-attr" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-csr@^2.6.0": + version "2.6.1" + resolved "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz" + integrity sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-ecc@^2.6.0": + version "2.6.1" + resolved "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz" + integrity sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-pfx@^2.6.1": + version "2.6.1" + resolved "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz" + integrity sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw== + dependencies: + "@peculiar/asn1-cms" "^2.6.1" + "@peculiar/asn1-pkcs8" "^2.6.1" + "@peculiar/asn1-rsa" "^2.6.1" + "@peculiar/asn1-schema" "^2.6.0" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-pkcs8@^2.6.1": + version "2.6.1" + resolved "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz" + integrity sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-pkcs9@^2.6.0": + version "2.6.1" + resolved "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz" + integrity sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw== + dependencies: + "@peculiar/asn1-cms" "^2.6.1" + "@peculiar/asn1-pfx" "^2.6.1" + "@peculiar/asn1-pkcs8" "^2.6.1" + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + "@peculiar/asn1-x509-attr" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-rsa@^2.6.0", "@peculiar/asn1-rsa@^2.6.1": + version "2.6.1" + resolved "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz" + integrity sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-schema@^2.6.0": + version "2.6.0" + resolved "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz" + integrity sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg== + dependencies: + asn1js "^3.0.6" + pvtsutils "^1.3.6" + tslib "^2.8.1" + +"@peculiar/asn1-x509-attr@^2.6.1": + version "2.6.1" + resolved "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz" + integrity sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.1" + asn1js "^3.0.6" + tslib "^2.8.1" + +"@peculiar/asn1-x509@^2.6.0", "@peculiar/asn1-x509@^2.6.1": + version "2.6.1" + resolved "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz" + integrity sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA== + dependencies: + "@peculiar/asn1-schema" "^2.6.0" + asn1js "^3.0.6" + pvtsutils "^1.3.6" + tslib "^2.8.1" + +"@peculiar/x509@^1.14.2": + version "1.14.3" + resolved "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz" + integrity sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA== + dependencies: + "@peculiar/asn1-cms" "^2.6.0" + "@peculiar/asn1-csr" "^2.6.0" + "@peculiar/asn1-ecc" "^2.6.0" + "@peculiar/asn1-pkcs9" "^2.6.0" + "@peculiar/asn1-rsa" "^2.6.0" + "@peculiar/asn1-schema" "^2.6.0" + "@peculiar/asn1-x509" "^2.6.0" + pvtsutils "^1.3.6" + reflect-metadata "^0.2.2" + tslib "^2.8.1" + tsyringe "^4.10.0" "@types/body-parser@*": - version "1.19.2" - resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + version "1.19.6" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== dependencies: "@types/connect" "*" "@types/node" "*" "@types/bonjour@^3.5.13": version "3.5.13" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" + resolved "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz" integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== dependencies: "@types/node" "*" "@types/connect-history-api-fallback@^1.5.4": version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" + resolved "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz" integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== dependencies: "@types/express-serve-static-core" "*" "@types/node" "*" "@types/connect@*": - version "3.4.35" - resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + version "3.4.38" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== dependencies: "@types/node" "*" "@types/eslint-scope@^3.7.7": version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz" integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== dependencies: "@types/eslint" "*" "@types/estree" "*" "@types/eslint@*": - version "8.37.0" - resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz" - integrity sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ== + version "9.6.1" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz" + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== dependencies: "@types/estree" "*" "@types/json-schema" "*" "@types/estree@*", "@types/estree@^1.0.8": version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.21", "@types/express-serve-static-core@^4.17.33": - version "4.19.6" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" - integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== + version "4.19.8" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz" + integrity sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" "@types/send" "*" -"@types/express@*", "@types/express@^4.17.21": - version "4.17.23" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef" - integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ== +"@types/express@*", "@types/express@^4.17.13", "@types/express@^4.17.25": + version "4.17.25" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz" + integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.33" "@types/qs" "*" - "@types/serve-static" "*" + "@types/serve-static" "^1" "@types/http-errors@*": version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz" integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== "@types/http-proxy@^1.17.8": - version "1.17.10" - resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.10.tgz" - integrity sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g== + version "1.17.17" + resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz" + integrity sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw== dependencies: "@types/node" "*" "@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/mime@^1": version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== -"@types/node-forge@^1.3.0": - version "1.3.11" - resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" - integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== - dependencies: - "@types/node" "*" - "@types/node@*": - version "18.15.11" - resolved "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz" - integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + version "25.3.0" + resolved "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz" + integrity sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A== + dependencies: + undici-types "~7.18.0" "@types/qs@*": - version "6.9.7" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + version "6.14.0" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== "@types/range-parser@*": - version "1.2.4" - resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + version "1.2.7" + resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== "@types/retry@0.12.2": version "0.12.2" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" + resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz" integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== "@types/send@*": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" - integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== + version "1.2.1" + resolved "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/send@<1": + version "0.17.6" + resolved "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz" + integrity sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og== dependencies: "@types/mime" "^1" "@types/node" "*" "@types/serve-index@^1.9.4": version "1.9.4" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" + resolved "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz" integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== dependencies: "@types/express" "*" -"@types/serve-static@*", "@types/serve-static@^1.15.5": - version "1.15.8" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" - integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== +"@types/serve-static@^1", "@types/serve-static@^1.15.5": + version "1.15.10" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz" + integrity sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw== dependencies: "@types/http-errors" "*" "@types/node" "*" - "@types/send" "*" + "@types/send" "<1" "@types/sockjs@^0.3.36": version "0.3.36" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" + resolved "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz" integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== dependencies: "@types/node" "*" "@types/ws@^8.5.10": version "8.18.1" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz" integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== dependencies: "@types/node" "*" -"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": +"@webassemblyjs/ast@^1.14.1", "@webassemblyjs/ast@1.14.1": version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" + resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz" integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== dependencies: "@webassemblyjs/helper-numbers" "1.13.2" @@ -269,22 +534,22 @@ "@webassemblyjs/floating-point-hex-parser@1.13.2": version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" + resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz" integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== "@webassemblyjs/helper-api-error@1.13.2": version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz" integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== "@webassemblyjs/helper-buffer@1.14.1": version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz" integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== "@webassemblyjs/helper-numbers@1.13.2": version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz" integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== dependencies: "@webassemblyjs/floating-point-hex-parser" "1.13.2" @@ -293,12 +558,12 @@ "@webassemblyjs/helper-wasm-bytecode@1.13.2": version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz" integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== "@webassemblyjs/helper-wasm-section@1.14.1": version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz" integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== dependencies: "@webassemblyjs/ast" "1.14.1" @@ -308,26 +573,26 @@ "@webassemblyjs/ieee754@1.13.2": version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" + resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz" integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== dependencies: "@xtuc/ieee754" "^1.2.0" "@webassemblyjs/leb128@1.13.2": version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" + resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz" integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== dependencies: "@xtuc/long" "4.2.2" "@webassemblyjs/utf8@1.13.2": version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" + resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz" integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== "@webassemblyjs/wasm-edit@^1.14.1": version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz" integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== dependencies: "@webassemblyjs/ast" "1.14.1" @@ -341,7 +606,7 @@ "@webassemblyjs/wasm-gen@1.14.1": version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz" integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== dependencies: "@webassemblyjs/ast" "1.14.1" @@ -352,7 +617,7 @@ "@webassemblyjs/wasm-opt@1.14.1": version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz" integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== dependencies: "@webassemblyjs/ast" "1.14.1" @@ -360,9 +625,9 @@ "@webassemblyjs/wasm-gen" "1.14.1" "@webassemblyjs/wasm-parser" "1.14.1" -"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": +"@webassemblyjs/wasm-parser@^1.14.1", "@webassemblyjs/wasm-parser@1.14.1": version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz" integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== dependencies: "@webassemblyjs/ast" "1.14.1" @@ -374,28 +639,26 @@ "@webassemblyjs/wast-printer@1.14.1": version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz" integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== dependencies: "@webassemblyjs/ast" "1.14.1" "@xtuc/long" "4.2.2" -"@webpack-cli/configtest@^1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz" - integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== +"@webpack-cli/configtest@^2.1.1": + version "2.1.1" + resolved "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz" + integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== -"@webpack-cli/info@^1.5.0": - version "1.5.0" - resolved "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz" - integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ== - dependencies: - envinfo "^7.7.3" +"@webpack-cli/info@^2.0.2": + version "2.0.2" + resolved "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz" + integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== -"@webpack-cli/serve@^1.7.0": - version "1.7.0" - resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz" - integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== +"@webpack-cli/serve@^2.0.5": + version "2.0.5" + resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz" + integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -407,7 +670,7 @@ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: +accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -417,13 +680,13 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: acorn-import-phases@^1.0.3: version "1.0.4" - resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7" + resolved "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz" integrity sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ== -acorn@^8.15.0, acorn@^8.8.2: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== +acorn@^8.14.0, acorn@^8.15.0: + version "8.16.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== ajv-formats@^2.1.1: version "2.1.1" @@ -439,20 +702,10 @@ ajv-keywords@^5.1.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^8.0.0: - version "8.12.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ajv@^8.9.0: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== +ajv@^8.0.0, ajv@^8.8.2, ajv@^8.9.0: + version "8.18.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz" + integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== dependencies: fast-deep-equal "^3.1.3" fast-uri "^3.0.1" @@ -477,10 +730,19 @@ array-flatten@1.1.1: resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +asn1js@^3.0.6: + version "3.0.7" + resolved "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz" + integrity sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ== + dependencies: + pvtsutils "^1.3.6" + pvutils "^1.1.3" + tslib "^2.8.1" + baseline-browser-mapping@^2.9.0: - version "2.9.19" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488" - integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg== + version "2.10.0" + resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz" + integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA== batch@0.6.1: version "0.6.1" @@ -488,13 +750,13 @@ batch@0.6.1: integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + version "2.3.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== body-parser@~1.20.3: version "1.20.4" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz" integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== dependencies: bytes "~3.1.2" @@ -512,22 +774,22 @@ body-parser@~1.20.3: bonjour-service@^1.2.1: version "1.3.0" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.3.0.tgz#80d867430b5a0da64e82a8047fc1e355bdb71722" + resolved "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz" integrity sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA== dependencies: fast-deep-equal "^3.1.3" multicast-dns "^7.2.5" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: fill-range "^7.1.1" -browserslist@^4.28.1: +browserslist@^4.28.1, "browserslist@>= 4.21.0": version "4.28.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz" integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== dependencies: baseline-browser-mapping "^2.9.0" @@ -543,24 +805,24 @@ buffer-from@^1.0.0: bundle-name@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" + resolved "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz" integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== dependencies: run-applescript "^7.0.0" -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@~3.1.2: +bytes@~3.1.2, bytes@3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +bytestreamjs@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz" + integrity sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ== + call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: es-errors "^1.3.0" @@ -568,20 +830,20 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: call-bound@^1.0.2: version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== dependencies: call-bind-apply-helpers "^1.0.2" get-intrinsic "^1.3.0" caniuse-lite@^1.0.30001759: - version "1.0.30001769" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz#1ad91594fad7dc233777c2781879ab5409f7d9c2" - integrity sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg== + version "1.0.30001774" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz" + integrity sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA== chokidar@^3.6.0: version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: anymatch "~3.1.2" @@ -595,9 +857,9 @@ chokidar@^3.6.0: fsevents "~2.3.2" chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + version "1.0.4" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== clone-deep@^4.0.1: version "4.0.1" @@ -609,38 +871,38 @@ clone-deep@^4.0.1: shallow-clone "^3.0.0" colorette@^2.0.10, colorette@^2.0.14: - version "2.0.19" - resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + version "2.0.20" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== commander@^2.20.0: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^7.0.0: - version "7.2.0" - resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -compressible@~2.0.16: +compressible@~2.0.18: version "2.0.18" resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== dependencies: mime-db ">= 1.43.0 < 2" -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== +compression@^1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz" + integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" + bytes "3.1.2" + compressible "~2.0.18" debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" + negotiator "~0.6.4" + on-headers "~1.1.0" + safe-buffer "5.2.1" vary "~1.1.2" connect-history-api-fallback@^2.0.0: @@ -650,7 +912,7 @@ connect-history-api-fallback@^2.0.0: content-disposition@~0.5.4: version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: safe-buffer "5.2.1" @@ -662,12 +924,12 @@ content-type@~1.0.4, content-type@~1.0.5: cookie-signature@~1.0.6: version "1.0.7" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454" + resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz" integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== cookie@~0.7.1: version "0.7.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== copy-webpack-plugin@^11.0.0: @@ -688,14 +950,21 @@ core-util-is@~1.0.0: integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" which "^2.0.1" +debug@^4.1.0: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + debug@2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" @@ -703,42 +972,35 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@^4.1.0: - version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - default-browser-id@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" - integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== + version "5.0.1" + resolved "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz" + integrity sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q== default-browser@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" - integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== + version "5.5.0" + resolved "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz" + integrity sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw== dependencies: bundle-name "^4.1.0" default-browser-id "^5.0.0" define-lazy-prop@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz" integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== -depd@2.0.0, depd@~2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - depd@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== -destroy@1.2.0, destroy@~1.2.0: +depd@~2.0.0, depd@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@~1.2.0, destroy@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== @@ -756,15 +1018,15 @@ dir-glob@^3.0.1: path-type "^4.0.0" dns-packet@^5.2.2: - version "5.5.0" - resolved "https://registry.npmjs.org/dns-packet/-/dns-packet-5.5.0.tgz" - integrity sha512-USawdAUzRkV6xrqTjiAEp6M9YagZEzWcSUaZTcIFAiyQWW1SoI6KyId8y2+/71wbgHKQAKd+iupLv4YvEwYWvA== + version "5.6.1" + resolved "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== dependencies: "@leichtgewicht/ip-codec" "^2.0.1" dunder-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== dependencies: call-bind-apply-helpers "^1.0.1" @@ -777,53 +1039,53 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.5.263: - version "1.5.286" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" - integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== + version "1.5.302" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz" + integrity sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg== encodeurl@~2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== enhanced-resolve@^5.19.0: version "5.19.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz#6687446a15e969eaa63c2fa2694510e17ae6d97c" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz" integrity sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg== dependencies: graceful-fs "^4.2.4" tapable "^2.3.0" envinfo@^7.7.3: - version "7.8.1" - resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz" - integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== + version "7.21.0" + resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz" + integrity sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow== es-define-property@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-module-lexer@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.0.0.tgz#f657cd7a9448dcdda9c070a3cb75e5dc1e85f5b1" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz" integrity sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw== es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== dependencies: es-errors "^1.3.0" escalade@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-html@~1.0.3: @@ -871,9 +1133,9 @@ events@^3.2.0: resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -express@^4.21.2: +express@^4.22.1: version "4.22.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069" + resolved "https://registry.npmjs.org/express/-/express-4.22.1.tgz" integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g== dependencies: accepts "~1.3.8" @@ -908,26 +1170,26 @@ express@^4.21.2: utils-merge "1.0.1" vary "~1.1.2" -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.2.11, fast-glob@^3.3.0: - version "3.3.1" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + version "3.3.3" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.4" + micromatch "^4.0.8" fast-uri@^3.0.1: - version "3.0.6" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" - integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + version "3.1.0" + resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== fastest-levenshtein@^1.0.12: version "1.0.16" @@ -935,9 +1197,9 @@ fastest-levenshtein@^1.0.12: integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + version "1.20.1" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== dependencies: reusify "^1.0.4" @@ -957,7 +1219,7 @@ fill-range@^7.1.1: finalhandler@~1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz" integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg== dependencies: debug "2.6.9" @@ -976,10 +1238,15 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + follow-redirects@^1.0.0: - version "1.15.6" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + version "1.15.11" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== forwarded@0.2.0: version "0.2.0" @@ -988,27 +1255,17 @@ forwarded@0.2.0: fresh@~0.5.2: version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - function-bind@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: call-bind-apply-helpers "^1.0.2" @@ -1024,13 +1281,13 @@ get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: get-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" -glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -1044,6 +1301,18 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-to-regex.js@^1.0.0, glob-to-regex.js@^1.0.1: + version "1.2.0" + resolved "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz" + integrity sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ== + glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" @@ -1061,11 +1330,11 @@ globby@^13.1.1: slash "^4.0.0" "go-mix-conn@file:../go-mix-conn/build": - version "0.0.1" + resolved "file:../go-mix-conn/build" gopd@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: @@ -1085,19 +1354,12 @@ has-flag@^4.0.0: has-symbols@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - hasown@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" @@ -1122,19 +1384,20 @@ http-deceiver@^1.2.7: resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== +http-errors@~1.8.0: + version "1.8.1" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== dependencies: depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.1" http-errors@~2.0.0, http-errors@~2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz" integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== dependencies: depd "~2.0.0" @@ -1144,13 +1407,13 @@ http-errors@~2.0.0, http-errors@~2.0.1: toidentifier "~1.0.1" http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + version "0.5.10" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz" + integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== -http-proxy-middleware@^2.0.7: +http-proxy-middleware@^2.0.9: version "2.0.9" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef" + resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz" integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q== dependencies: "@types/http-proxy" "^1.17.8" @@ -1170,54 +1433,49 @@ http-proxy@^1.18.1: hyperdyperid@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" + resolved "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz" integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== iconv-lite@~0.4.24: version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" ignore@^5.2.4: - version "5.2.4" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + version "5.3.2" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + version "3.2.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== dependencies: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@~2.0.4: +inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@~2.0.4, inherits@2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz" - integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== + +ipaddr.js@^2.1.0: + version "2.3.0" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz" + integrity sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg== ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -ipaddr.js@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" - integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" @@ -1225,16 +1483,16 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== +is-core-module@^2.16.1: + version "2.16.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: - has "^1.0.3" + hasown "^2.0.2" is-docker@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + resolved "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz" integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== is-extglob@^2.1.1: @@ -1251,15 +1509,15 @@ is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: is-inside-container@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + resolved "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz" integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== dependencies: is-docker "^3.0.0" is-network-error@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.1.0.tgz#d26a760e3770226d11c169052f266a4803d9c997" - integrity sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g== + version "1.3.0" + resolved "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz" + integrity sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw== is-number@^7.0.0: version "7.0.0" @@ -1279,9 +1537,9 @@ is-plain-object@^2.0.4: isobject "^3.0.1" is-wsl@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" - integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + version "3.1.1" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz" + integrity sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw== dependencies: is-inside-container "^1.0.0" @@ -1325,16 +1583,16 @@ kind-of@^6.0.2: integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== launch-editor@^2.6.1: - version "2.10.0" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.10.0.tgz#5ca3edfcb9667df1e8721310f3a40f1127d4bc42" - integrity sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA== + version "2.13.0" + resolved "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.0.tgz" + integrity sha512-u+9asUHMJ99lA15VRMXw5XKfySFR9dGXwgsgS14YTbUq3GITP58mIM32At90P5fZ+MUId5Yw+IwI/yKub7jnCQ== dependencies: - picocolors "^1.0.0" - shell-quote "^1.8.1" + picocolors "^1.1.1" + shell-quote "^1.8.3" loader-runner@^4.3.1: version "4.3.1" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3" + resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz" integrity sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q== locate-path@^5.0.0: @@ -1346,7 +1604,7 @@ locate-path@^5.0.0: math-intrinsics@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== media-typer@0.3.0: @@ -1354,19 +1612,29 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -memfs@^4.6.0: - version "4.17.2" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.17.2.tgz#1f71a6d85c8c53b4f1b388234ed981a690c7e227" - integrity sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg== +memfs@^4.43.1: + version "4.56.10" + resolved "https://registry.npmjs.org/memfs/-/memfs-4.56.10.tgz" + integrity sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w== dependencies: - "@jsonjoy.com/json-pack" "^1.0.3" - "@jsonjoy.com/util" "^1.3.0" - tree-dump "^1.0.1" + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-fsa" "4.56.10" + "@jsonjoy.com/fs-node" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-to-fsa" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/fs-print" "4.56.10" + "@jsonjoy.com/fs-snapshot" "4.56.10" + "@jsonjoy.com/json-pack" "^1.11.0" + "@jsonjoy.com/util" "^1.9.0" + glob-to-regex.js "^1.0.1" + thingies "^2.5.0" + tree-dump "^1.0.3" tslib "^2.0.0" merge-descriptors@1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz" integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== merge-stream@^2.0.0: @@ -1384,26 +1652,38 @@ methods@~1.1.2: resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== +micromatch@^4.0.2, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +"mime-db@>= 1.43.0 < 2", mime-db@1.52.0: version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" +mime-types@^3.0.1: + version "3.0.2" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" + mime@1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" @@ -1414,16 +1694,16 @@ minimalistic-assert@^1.0.0: resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + ms@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - ms@2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" @@ -1437,6 +1717,11 @@ multicast-dns@^7.2.5: dns-packet "^5.2.2" thunky "^1.0.2" +negotiator@~0.6.4: + version "0.6.4" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + negotiator@0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" @@ -1447,14 +1732,9 @@ neo-async@^2.6.2: resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -node-forge@^1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.3.tgz#0ad80f6333b3a0045e827ac20b7f735f93716751" - integrity sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg== - node-releases@^2.0.27: version "2.0.27" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz" integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== normalize-path@^3.0.0, normalize-path@~3.0.0: @@ -1464,7 +1744,7 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: object-inspect@^1.13.3: version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== obuf@^1.0.0, obuf@^1.1.2: @@ -1479,20 +1759,20 @@ on-finished@^2.4.1, on-finished@~2.4.1: dependencies: ee-first "1.1.1" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== +on-headers@~1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz" + integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== open@^10.0.3: - version "10.1.2" - resolved "https://registry.yarnpkg.com/open/-/open-10.1.2.tgz#d5df40984755c9a9c3c93df8156a12467e882925" - integrity sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw== + version "10.2.0" + resolved "https://registry.npmjs.org/open/-/open-10.2.0.tgz" + integrity sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA== dependencies: default-browser "^5.2.1" define-lazy-prop "^3.0.0" is-inside-container "^1.0.0" - is-wsl "^3.1.0" + wsl-utils "^0.1.0" p-limit@^2.2.0: version "2.3.0" @@ -1510,7 +1790,7 @@ p-locate@^4.1.0: p-retry@^6.2.0: version "6.2.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.1.tgz#81828f8dc61c6ef5a800585491572cc9892703af" + resolved "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz" integrity sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ== dependencies: "@types/retry" "0.12.2" @@ -1522,7 +1802,7 @@ p-try@^2.0.0: resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -1544,7 +1824,7 @@ path-parse@^1.0.7: path-to-regexp@~0.1.12: version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz" integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== path-type@^4.0.0: @@ -1552,14 +1832,9 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - picocolors@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: @@ -1574,6 +1849,18 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkijs@^3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz" + integrity sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw== + dependencies: + "@noble/hashes" "1.4.0" + asn1js "^3.0.6" + bytestreamjs "^2.0.1" + pvtsutils "^1.3.6" + pvutils "^1.1.3" + tslib "^2.8.1" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" @@ -1587,15 +1874,22 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +pvtsutils@^1.3.6: + version "1.3.6" + resolved "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz" + integrity sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg== + dependencies: + tslib "^2.8.1" + +pvutils@^1.1.3: + version "1.1.5" + resolved "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz" + integrity sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA== qs@~6.14.0: - version "6.14.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159" - integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ== + version "6.14.2" + resolved "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz" + integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== dependencies: side-channel "^1.1.0" @@ -1618,7 +1912,7 @@ range-parser@^1.2.1, range-parser@~1.2.1: raw-body@~2.5.3: version "2.5.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz" integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== dependencies: bytes "~3.1.2" @@ -1655,12 +1949,17 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -rechoir@^0.7.0: - version "0.7.1" - resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz" - integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== dependencies: - resolve "^1.9.0" + resolve "^1.20.0" + +reflect-metadata@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== require-from-string@^2.0.2: version "2.0.2" @@ -1684,12 +1983,12 @@ resolve-from@^5.0.0: resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve@^1.9.0: - version "1.22.1" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== +resolve@^1.20.0: + version "1.22.11" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -1699,14 +1998,14 @@ retry@^0.13.1: integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + version "1.1.0" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== run-applescript@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" - integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== + version "7.1.0" + resolved "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz" + integrity sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q== run-parallel@^1.1.9: version "1.2.0" @@ -1715,16 +2014,16 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@^5.1.0, safe-buffer@>=5.1.0, safe-buffer@~5.2.0, safe-buffer@5.2.1: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" @@ -1732,7 +2031,7 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: schema-utils@^4.0.0, schema-utils@^4.2.0, schema-utils@^4.3.0, schema-utils@^4.3.3: version "4.3.3" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz" integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== dependencies: "@types/json-schema" "^7.0.9" @@ -1745,17 +2044,17 @@ select-hose@^2.0.0: resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz" integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== -selfsigned@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" - integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== +selfsigned@^5.5.0: + version "5.5.0" + resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz" + integrity sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew== dependencies: - "@types/node-forge" "^1.3.0" - node-forge "^1" + "@peculiar/x509" "^1.14.2" + pkijs "^3.3.3" send@~0.19.0, send@~0.19.1: version "0.19.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29" + resolved "https://registry.npmjs.org/send/-/send-0.19.2.tgz" integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== dependencies: debug "2.6.9" @@ -1772,36 +2071,29 @@ send@~0.19.0, send@~0.19.1: range-parser "~1.2.1" statuses "~2.0.2" -serialize-javascript@^6.0.0: - version "6.0.1" - resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz" - integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== - dependencies: - randombytes "^2.1.0" - -serialize-javascript@^6.0.2: +serialize-javascript@^6.0.0, serialize-javascript@^6.0.2: version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz" integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + version "1.9.2" + resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz" + integrity sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ== dependencies: - accepts "~1.3.4" + accepts "~1.3.8" batch "0.6.1" debug "2.6.9" escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" + http-errors "~1.8.0" + mime-types "~2.1.35" + parseurl "~1.3.3" serve-static@~1.16.2: version "1.16.3" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz" integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== dependencies: encodeurl "~2.0.0" @@ -1809,12 +2101,7 @@ serve-static@~1.16.2: parseurl "~1.3.3" send "~0.19.1" -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0, setprototypeof@~1.2.0: +setprototypeof@~1.2.0, setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== @@ -1838,14 +2125,14 @@ shebang-regex@^3.0.0: resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.8.1: +shell-quote@^1.8.3: version "1.8.3" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" + resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz" integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== side-channel-list@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz" integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: es-errors "^1.3.0" @@ -1853,7 +2140,7 @@ side-channel-list@^1.0.0: side-channel-map@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz" integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== dependencies: call-bound "^1.0.2" @@ -1863,7 +2150,7 @@ side-channel-map@^1.0.1: side-channel-weakmap@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz" integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== dependencies: call-bound "^1.0.2" @@ -1874,7 +2161,7 @@ side-channel-weakmap@^1.0.2: side-channel@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== dependencies: es-errors "^1.3.0" @@ -1933,14 +2220,14 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" -"statuses@>= 1.4.0 < 2": +"statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== statuses@~2.0.1, statuses@~2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz" integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== string_decoder@^1.1.1: @@ -1971,12 +2258,12 @@ supports-preserve-symlinks-flag@^1.0.0: tapable@^2.3.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz" integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== terser-webpack-plugin@^5.3.16: version "5.3.16" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz#741e448cc3f93d8026ebe4f7ef9e4afacfd56330" + resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz" integrity sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q== dependencies: "@jridgewell/trace-mapping" "^0.3.25" @@ -1986,19 +2273,19 @@ terser-webpack-plugin@^5.3.16: terser "^5.31.1" terser@^5.31.1: - version "5.39.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.39.0.tgz#0e82033ed57b3ddf1f96708d123cca717d86ca3a" - integrity sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw== + version "5.46.0" + resolved "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz" + integrity sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg== dependencies: "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" + acorn "^8.15.0" commander "^2.20.0" source-map-support "~0.5.20" -thingies@^1.20.0: - version "1.21.0" - resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" - integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g== +thingies@^2.5.0: + version "2.5.0" + resolved "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz" + integrity sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw== thunky@^1.0.2: version "1.1.0" @@ -2012,21 +2299,33 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@~1.0.1: +toidentifier@~1.0.1, toidentifier@1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tree-dump@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.3.tgz#2f0e42e77354714418ed7ab44291e435ccdb0f80" - integrity sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg== +tree-dump@^1.0.3, tree-dump@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz" + integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA== -tslib@^2.0.0: +tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2, tslib@^2.0.0, tslib@^2.8.1, tslib@2: version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tsyringe@^4.10.0: + version "4.10.0" + resolved "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz" + integrity sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw== + dependencies: + tslib "^1.9.3" + type-is@~1.6.18: version "1.6.18" resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" @@ -2035,6 +2334,11 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +undici-types@~7.18.0: + version "7.18.2" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz" + integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== + unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" @@ -2042,19 +2346,12 @@ unpipe@~1.0.0: update-browserslist-db@^1.2.0: version "1.2.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz" integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== dependencies: escalade "^3.2.0" picocolors "^1.1.1" -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" @@ -2077,7 +2374,7 @@ vary@~1.1.2: watchpack@^2.5.1: version "2.5.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.1.tgz#dd38b601f669e0cbf567cb802e75cead82cde102" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz" integrity sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg== dependencies: glob-to-regexp "^0.4.1" @@ -2090,44 +2387,45 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -webpack-cli@^4.9.2: - version "4.10.0" - resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz" - integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== +webpack-cli@^5.1.4, webpack-cli@5.x.x: + version "5.1.4" + resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz" + integrity sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg== dependencies: "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^1.2.0" - "@webpack-cli/info" "^1.5.0" - "@webpack-cli/serve" "^1.7.0" + "@webpack-cli/configtest" "^2.1.1" + "@webpack-cli/info" "^2.0.2" + "@webpack-cli/serve" "^2.0.5" colorette "^2.0.14" - commander "^7.0.0" + commander "^10.0.1" cross-spawn "^7.0.3" + envinfo "^7.7.3" fastest-levenshtein "^1.0.12" import-local "^3.0.2" - interpret "^2.2.0" - rechoir "^0.7.0" + interpret "^3.1.1" + rechoir "^0.8.0" webpack-merge "^5.7.3" webpack-dev-middleware@^7.4.2: - version "7.4.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz#40e265a3d3d26795585cff8207630d3a8ff05877" - integrity sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA== + version "7.4.5" + resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz" + integrity sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA== dependencies: colorette "^2.0.10" - memfs "^4.6.0" - mime-types "^2.1.31" + memfs "^4.43.1" + mime-types "^3.0.1" on-finished "^2.4.1" range-parser "^1.2.1" schema-utils "^4.0.0" webpack-dev-server@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.1.tgz#049072d6e19cbda8cf600b9e364e6662d61218ba" - integrity sha512-ml/0HIj9NLpVKOMq+SuBPLHcmbG+TGIjXRHsYfZwocUBIqEvws8NnS/V9AFQ5FKP+tgn5adwVwRrTEpGL33QFQ== + version "5.2.3" + resolved "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz" + integrity sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ== dependencies: "@types/bonjour" "^3.5.13" "@types/connect-history-api-fallback" "^1.5.4" - "@types/express" "^4.17.21" + "@types/express" "^4.17.25" "@types/express-serve-static-core" "^4.17.21" "@types/serve-index" "^1.9.4" "@types/serve-static" "^1.15.5" @@ -2137,17 +2435,17 @@ webpack-dev-server@^5.2.1: bonjour-service "^1.2.1" chokidar "^3.6.0" colorette "^2.0.10" - compression "^1.7.4" + compression "^1.8.1" connect-history-api-fallback "^2.0.0" - express "^4.21.2" + express "^4.22.1" graceful-fs "^4.2.6" - http-proxy-middleware "^2.0.7" + http-proxy-middleware "^2.0.9" ipaddr.js "^2.1.0" launch-editor "^2.6.1" open "^10.0.3" p-retry "^6.2.0" schema-utils "^4.2.0" - selfsigned "^2.4.1" + selfsigned "^5.5.0" serve-index "^1.9.1" sockjs "^0.3.24" spdy "^4.0.2" @@ -2155,22 +2453,23 @@ webpack-dev-server@^5.2.1: ws "^8.18.0" webpack-merge@^5.7.3: - version "5.8.0" - resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz" - integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== + version "5.10.0" + resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== dependencies: clone-deep "^4.0.1" + flat "^5.0.2" wildcard "^2.0.0" webpack-sources@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" - integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== + version "3.3.4" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz" + integrity sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q== -webpack@^5.105.0: - version "5.105.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.0.tgz#38b5e6c5db8cbe81debbd16e089335ada05ea23a" - integrity sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw== +webpack@^5.0.0, webpack@^5.1.0, webpack@^5.98.0, webpack@5.x.x: + version "5.105.2" + resolved "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz" + integrity sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw== dependencies: "@types/eslint-scope" "^3.7.7" "@types/estree" "^1.0.8" @@ -2198,7 +2497,7 @@ webpack@^5.105.0: watchpack "^2.5.1" webpack-sources "^3.3.3" -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: +websocket-driver@^0.7.4, websocket-driver@>=0.5.1: version "0.7.4" resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== @@ -2220,11 +2519,18 @@ which@^2.0.1: isexe "^2.0.0" wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + version "2.0.1" + resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== ws@^8.18.0: - version "8.18.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.2.tgz#42738b2be57ced85f46154320aabb51ab003705a" - integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== + version "8.19.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz" + integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== + +wsl-utils@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz" + integrity sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw== + dependencies: + is-wsl "^3.1.0" diff --git a/wasm/mix-fetch/src/client.rs b/wasm/mix-fetch/src/client.rs index 6fd5d642ac..7c685f5b75 100644 --- a/wasm/mix-fetch/src/client.rs +++ b/wasm/mix-fetch/src/client.rs @@ -249,6 +249,8 @@ impl MixFetchClient { request_id: RequestId, target: RemoteAddress, ) -> Result<(), MixFetchError> { + console_log!(">>>>>>>>>>> SENDING CONNECT"); + let raw_conn_req = socks5_connect_request(request_id, target, self.self_address); let lane = TransmissionLane::ConnectionId(request_id); let input = InputMessage::new_regular( diff --git a/wasm/mix-fetch/src/helpers.rs b/wasm/mix-fetch/src/helpers.rs index 9476d61715..719ab64559 100644 --- a/wasm/mix-fetch/src/helpers.rs +++ b/wasm/mix-fetch/src/helpers.rs @@ -25,6 +25,7 @@ pub(crate) async fn get_network_requester( url::Url::parse(&nym_api_url.unwrap_or(NYM_API_URL.to_string()))?, None, ); + #[allow(deprecated)] let nodes = client.get_all_described_nodes().await?; let providers: Vec<_> = nodes .iter() diff --git a/wasm/node-tester/.cargo/config.toml b/wasm/node-tester/.cargo/config.toml index bdbe517658..e9c3438b6e 100644 --- a/wasm/node-tester/.cargo/config.toml +++ b/wasm/node-tester/.cargo/config.toml @@ -1,3 +1,4 @@ [build] target = "wasm32-unknown-unknown" target_arch = "wasm32" +rustflags = ["--cfg=getrandom_backend=\"wasm_js\""] \ No newline at end of file diff --git a/wasm/node-tester/internal-dev/yarn.lock b/wasm/node-tester/internal-dev/yarn.lock index 18e3c69291..7f8cb8f0f3 100644 --- a/wasm/node-tester/internal-dev/yarn.lock +++ b/wasm/node-tester/internal-dev/yarn.lock @@ -7,45 +7,39 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== +"@jridgewell/source-map@^0.3.3": + version "0.3.11" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba" + integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" @@ -106,10 +100,10 @@ dependencies: "@types/node" "*" -"@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== dependencies: "@types/eslint" "*" "@types/estree" "*" @@ -122,15 +116,10 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== - -"@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@*", "@types/estree@^1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": version "4.17.33" @@ -158,10 +147,10 @@ dependencies: "@types/node" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/mime@*": version "3.0.1" @@ -217,125 +206,125 @@ dependencies: "@types/node" "*" -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== +"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" + integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-numbers" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== +"@webassemblyjs/floating-point-hex-parser@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" + integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== +"@webassemblyjs/helper-api-error@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" + integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== +"@webassemblyjs/helper-buffer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" + integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== +"@webassemblyjs/helper-numbers@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" + integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/floating-point-hex-parser" "1.13.2" + "@webassemblyjs/helper-api-error" "1.13.2" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== +"@webassemblyjs/helper-wasm-bytecode@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" + integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== +"@webassemblyjs/helper-wasm-section@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" + integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/wasm-gen" "1.14.1" -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== +"@webassemblyjs/ieee754@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" + integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== +"@webassemblyjs/leb128@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" + integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== +"@webassemblyjs/utf8@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" + integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== +"@webassemblyjs/wasm-edit@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" + integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/helper-wasm-section" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-opt" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + "@webassemblyjs/wast-printer" "1.14.1" -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== +"@webassemblyjs/wasm-gen@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" + integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== +"@webassemblyjs/wasm-opt@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" + integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== +"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" + integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-api-error" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== +"@webassemblyjs/wast-printer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" + integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== dependencies: - "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/ast" "1.14.1" "@xtuc/long" "4.2.2" "@webpack-cli/configtest@^1.2.0": @@ -373,15 +362,15 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-import-assertions@^1.7.6: - version "1.8.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" - integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== +acorn-import-phases@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7" + integrity sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ== -acorn@^8.5.0, acorn@^8.7.1: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== ajv-formats@^2.1.1: version "2.1.1" @@ -390,29 +379,14 @@ ajv-formats@^2.1.1: dependencies: ajv "^8.0.0" -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.0.0: +ajv-keywords@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== dependencies: fast-deep-equal "^3.1.3" -ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.8.0: +ajv@^8.0.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -422,6 +396,16 @@ ajv@^8.0.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + ansi-html-community@^0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" @@ -450,6 +434,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +baseline-browser-mapping@^2.9.0: + version "2.9.19" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488" + integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg== + batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -503,15 +492,16 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browserslist@^4.14.5: - version "4.21.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" - integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== +browserslist@^4.28.1: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== dependencies: - caniuse-lite "^1.0.30001449" - electron-to-chromium "^1.4.284" - node-releases "^2.0.8" - update-browserslist-db "^1.0.10" + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" buffer-from@^1.0.0: version "1.1.2" @@ -536,10 +526,10 @@ call-bind@^1.0.0: function-bind "^1.1.1" get-intrinsic "^1.0.2" -caniuse-lite@^1.0.30001449: - version "1.0.30001474" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001474.tgz#13b6fe301a831fe666cce8ca4ef89352334133d5" - integrity sha512-iaIZ8gVrWfemh5DG3T9/YqarVZoYf0r188IjaGwx68j4Pf0SGY6CQkmJUIE+NZHkkecQGohzXmBGEwWDr9aM3Q== +caniuse-lite@^1.0.30001759: + version "1.0.30001769" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz#1ad91594fad7dc233777c2781879ab5409f7d9c2" + integrity sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg== chokidar@^3.5.3: version "3.5.3" @@ -733,38 +723,38 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.284: - version "1.4.352" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.352.tgz#be96bd7c2f4b980deebc9338a49a67430a33ed73" - integrity sha512-ikFUEyu5/q+wJpMOxWxTaEVk2M1qKqTGKKyfJmod1CPZxKfYnxVS41/GCBQg21ItBpZybyN8sNpRqCUGm+Zc4Q== +electron-to-chromium@^1.5.263: + version "1.5.286" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" + integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -enhanced-resolve@^5.10.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" - integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== +enhanced-resolve@^5.17.4: + version "5.19.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz#6687446a15e969eaa63c2fa2694510e17ae6d97c" + integrity sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg== dependencies: graceful-fs "^4.2.4" - tapable "^2.2.0" + tapable "^2.3.0" envinfo@^7.7.3: version "7.8.1" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== -es-module-lexer@^0.9.0: - version "0.9.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" - integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-module-lexer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.0.0.tgz#f657cd7a9448dcdda9c070a3cb75e5dc1e85f5b1" + integrity sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw== -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-html@~1.0.3: version "1.0.3" @@ -879,10 +869,10 @@ fast-glob@^3.2.11, fast-glob@^3.3.0: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== fastest-levenshtein@^1.0.12: version "1.0.16" @@ -1022,7 +1012,7 @@ globby@^13.1.1: merge2 "^1.4.1" slash "^4.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -1267,11 +1257,6 @@ json-parse-even-better-errors@^2.3.1: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" @@ -1290,10 +1275,10 @@ launch-editor@^2.6.0: picocolors "^1.0.0" shell-quote "^1.7.3" -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== +loader-runner@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3" + integrity sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q== locate-path@^5.0.0: version "5.0.0" @@ -1414,10 +1399,10 @@ node-forge@^1: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== -node-releases@^2.0.8: - version "2.0.10" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" - integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -1543,6 +1528,11 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -1713,24 +1703,15 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -schema-utils@^3.1.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" - integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== +schema-utils@^4.0.0, schema-utils@^4.3.0, schema-utils@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" + integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== dependencies: "@types/json-schema" "^7.0.9" - ajv "^8.8.0" + ajv "^8.9.0" ajv-formats "^2.1.1" - ajv-keywords "^5.0.0" + ajv-keywords "^5.1.0" select-hose@^2.0.0: version "2.0.0" @@ -1763,13 +1744,20 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" -serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: +serialize-javascript@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== dependencies: randombytes "^2.1.0" +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -1932,29 +1920,29 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tapable@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" + integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== -terser-webpack-plugin@^5.1.3: - version "5.3.7" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz#ef760632d24991760f339fe9290deb936ad1ffc7" - integrity sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw== +terser-webpack-plugin@^5.3.16: + version "5.3.16" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz#741e448cc3f93d8026ebe4f7ef9e4afacfd56330" + integrity sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q== dependencies: - "@jridgewell/trace-mapping" "^0.3.17" + "@jridgewell/trace-mapping" "^0.3.25" jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.1" - terser "^5.16.5" + schema-utils "^4.3.0" + serialize-javascript "^6.0.2" + terser "^5.31.1" -terser@^5.16.5: - version "5.16.8" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.8.tgz#ccde583dabe71df3f4ed02b65eb6532e0fae15d5" - integrity sha512-QI5g1E/ef7d+PsDifb+a6nnVgC4F22Bg6T0xrBrz6iloVB4PUkkunp6V8nzoOOZJIzjWVdAGqCdlKlhLq/TbIA== +terser@^5.31.1: + version "5.46.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.46.0.tgz#1b81e560d584bbdd74a8ede87b4d9477b0ff9695" + integrity sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg== dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" + "@jridgewell/source-map" "^0.3.3" + acorn "^8.15.0" commander "^2.20.0" source-map-support "~0.5.20" @@ -1988,13 +1976,13 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.2.0" + picocolors "^1.1.1" uri-js@^4.2.2: version "4.4.1" @@ -2023,10 +2011,10 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== +watchpack@^2.4.4: + version "2.5.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.1.tgz#dd38b601f669e0cbf567cb802e75cead82cde102" + integrity sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -2111,40 +2099,41 @@ webpack-merge@^5.7.3: clone-deep "^4.0.1" wildcard "^2.0.0" -webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== +webpack-sources@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" + integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== webpack@^5.70.0: - version "5.77.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.77.0.tgz#dea3ad16d7ea6b84aa55fa42f4eac9f30e7eb9b4" - integrity sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q== + version "5.104.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.104.1.tgz#94bd41eb5dbf06e93be165ba8be41b8260d4fb1a" + integrity sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA== dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.7.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.8" + "@types/json-schema" "^7.0.15" + "@webassemblyjs/ast" "^1.14.1" + "@webassemblyjs/wasm-edit" "^1.14.1" + "@webassemblyjs/wasm-parser" "^1.14.1" + acorn "^8.15.0" + acorn-import-phases "^1.0.3" + browserslist "^4.28.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.10.0" - es-module-lexer "^0.9.0" + enhanced-resolve "^5.17.4" + es-module-lexer "^2.0.0" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" + graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" + loader-runner "^4.3.1" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.1.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.4.0" - webpack-sources "^3.2.3" + schema-utils "^4.3.3" + tapable "^2.3.0" + terser-webpack-plugin "^5.3.16" + watchpack "^2.4.4" + webpack-sources "^3.3.3" websocket-driver@>=0.5.1, websocket-driver@^0.7.4: version "0.7.4" diff --git a/wasm/zknym-lib/.cargo/config.toml b/wasm/zknym-lib/.cargo/config.toml index bdbe517658..e9c3438b6e 100644 --- a/wasm/zknym-lib/.cargo/config.toml +++ b/wasm/zknym-lib/.cargo/config.toml @@ -1,3 +1,4 @@ [build] target = "wasm32-unknown-unknown" target_arch = "wasm32" +rustflags = ["--cfg=getrandom_backend=\"wasm_js\""] \ No newline at end of file diff --git a/wasm/zknym-lib/Cargo.toml b/wasm/zknym-lib/Cargo.toml index b9d086535e..385ed467f1 100644 --- a/wasm/zknym-lib/Cargo.toml +++ b/wasm/zknym-lib/Cargo.toml @@ -16,31 +16,21 @@ crate-type = ["cdylib", "rlib"] [dependencies] async-trait.workspace = true -bs58.workspace = true -getrandom = { workspace = true, features = ["js"] } js-sys.workspace = true wasm-bindgen.workspace = true serde = { workspace = true, features = ["derive"] } thiserror.workspace = true tsify = { workspace = true, features = ["js"] } uuid = { workspace = true, features = ["serde"] } -reqwest = { workspace = true } wasmtimer = { workspace = true } zeroize.workspace = true -rand = { workspace = true } - - 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 } -tokio = { workspace = true, features = ["full"] } [package.metadata.wasm-pack.profile.release] diff --git a/yarn.lock b/yarn.lock index 1023b90e99..52463b02af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8625,14 +8625,14 @@ bluebird@^3.5.5: integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.8, bn.js@^4.11.9: - version "4.12.2" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.2.tgz#3d8fed6796c24e177737f7cc5172ee04ef39ec99" - integrity sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw== + version "4.12.3" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.3.tgz#2cc2c679188eb35b006f2d0d4710bed8437a769e" + integrity sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g== bn.js@^5.2.0, bn.js@^5.2.1, bn.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.2.tgz#82c09f9ebbb17107cd72cb7fd39bd1f9d0aaa566" - integrity sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw== + version "5.2.3" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.3.tgz#16a9e409616b23fef3ccbedb8d42f13bff80295e" + integrity sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w== body-parser@1.20.3: version "1.20.3" @@ -13282,9 +13282,9 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react- react-is "^16.7.0" hono@^4.11.4: - version "4.11.9" - resolved "https://registry.yarnpkg.com/hono/-/hono-4.11.9.tgz#4e0dec5309a8f05216c777cbd93131a41cf28eb9" - integrity sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ== + version "4.12.0" + resolved "https://registry.yarnpkg.com/hono/-/hono-4.12.0.tgz#18f96a2962274398efc57a1a79e22b3daad2ca1c" + integrity sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA== hosted-git-info@^2.1.4: version "2.8.9"