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