Compare commits
169 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a8c8be7ae | |||
| 9ac7f1261f | |||
| f95fe55967 | |||
| d03262f590 | |||
| cd2fdd8747 | |||
| df61de715b | |||
| b39625dc72 | |||
| a3ff215b0d | |||
| 6e81dfeabc | |||
| a26e9c9975 | |||
| 3fbf739466 | |||
| 04ad1acd9f | |||
| 5000e8ae39 | |||
| 17b9fa4dd5 | |||
| 22793bc45e | |||
| 37f3ef58a3 | |||
| 1a4d64a0e5 | |||
| 4dcc568ec2 | |||
| 468835e3a2 | |||
| 28a866e26d | |||
| 350d244032 | |||
| 17ca000782 | |||
| aac983d922 | |||
| 577675bab3 | |||
| ec015618cd | |||
| fa40acbeca | |||
| 386e1790dd | |||
| d07f9c8fad | |||
| 0dc071daeb | |||
| babf113fe5 | |||
| 10951d4cd3 | |||
| 872c25bfcc | |||
| 5acce42c64 | |||
| 4848d081d0 | |||
| b3452ede77 | |||
| a44819b14c | |||
| 5455110810 | |||
| a0178d28f7 | |||
| 3e42160426 | |||
| 2f752a6c42 | |||
| 806f807f02 | |||
| 1400db6156 | |||
| 673a3e45d3 | |||
| d9c2f6ebda | |||
| e24e094711 | |||
| 0d7487f530 | |||
| 378f32e6d7 | |||
| 3e9b8d237f | |||
| f5a4dbc555 | |||
| 4480534e4d | |||
| d355f9d752 | |||
| 9f3a370496 | |||
| e4adc5d954 | |||
| 00373b70e2 | |||
| 65f2017422 | |||
| 192f258463 | |||
| a5eee7444b | |||
| 6abd7e7352 | |||
| 3306ca5357 | |||
| 9c2ccead0e | |||
| b7aeb51362 | |||
| e9e725defe | |||
| c74494a21d | |||
| 54f6c98c22 | |||
| 846484bbb4 | |||
| fb3f5501ba | |||
| e8a607f520 | |||
| f5f6df9eaf | |||
| c647ab5605 | |||
| 416c21a42e | |||
| fd5a95fa4d | |||
| c61df79182 | |||
| 08559a7660 | |||
| 6dce55a99b | |||
| bc0b89b31c | |||
| 67c32faa11 | |||
| aa0d15ee67 | |||
| 4f0974fcf1 | |||
| bd2174641e | |||
| 59b62fabc9 | |||
| e6f4bae895 | |||
| d71742af32 | |||
| 3b7c07e249 | |||
| 3b429dde69 | |||
| 3a29c296da | |||
| 8544c54f8f | |||
| 9f9639950b | |||
| 111a0b20b6 | |||
| 67b300d0b8 | |||
| 88c4e0ce6c | |||
| f6800aff0a | |||
| 0c7c927ca2 | |||
| a69c8b1660 | |||
| f3ea310a46 | |||
| 92f9ff035d | |||
| 5a817e1df1 | |||
| a07a24db00 | |||
| a0cb812eff | |||
| 923c1fa184 | |||
| 35ea7e4926 | |||
| d1cb9afaf0 | |||
| 79d4b4b2e3 | |||
| 8460b33946 | |||
| ae6539e07c | |||
| 18cebdfedc | |||
| c448ec823a | |||
| a266137278 | |||
| 6f4dfd1dab | |||
| 57719787db | |||
| 29a57bf172 | |||
| 17d11f201e | |||
| fef7e42eb4 | |||
| ceeeb6211b | |||
| cd77b1032f | |||
| 6bbb14f12f | |||
| de8030d85a | |||
| e18e64bf21 | |||
| a50c9ac3fb | |||
| db813b6e3e | |||
| 1be5ba310a | |||
| 41ff3f7824 | |||
| c9d4d62446 | |||
| e839a0d80e | |||
| cd61f930bf | |||
| 0674f31227 | |||
| 3e4f563dce | |||
| edcf2b1204 | |||
| b07fb18113 | |||
| 017dea4afd | |||
| 5a9ce13beb | |||
| 514cf25c68 | |||
| 49ee0636e4 | |||
| bb971ce99c | |||
| 54de369c1e | |||
| 6d6ce284df | |||
| 56ad1c6c8e | |||
| 10b4a288c8 | |||
| bbbb9486ce | |||
| 16e86e1a07 | |||
| ca0c9898f0 | |||
| 8b73d4e615 | |||
| 6a9a767ab4 | |||
| e03a9fa16f | |||
| a0fbd57d5b | |||
| 9856198356 | |||
| 5c33846e57 | |||
| cfa7635ae1 | |||
| 5d45544c27 | |||
| aa6a79cb3e | |||
| b3a940770a | |||
| e980f76a81 | |||
| 9b38fef28f | |||
| 43910ca635 | |||
| d3ccd7575a | |||
| 422f889df7 | |||
| c9e96edc35 | |||
| 7768317046 | |||
| 0ebbb1a540 | |||
| 827c13b69e | |||
| 18ff09608c | |||
| 8cc996bc0d | |||
| 83a598907f | |||
| bb06a1b7a8 | |||
| e783a5fced | |||
| 8a24b45b5d | |||
| 10e4eba727 | |||
| 8ebf482f36 | |||
| 6940ca427e | |||
| 24f877fda5 |
@@ -27,10 +27,10 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
@@ -43,10 +43,10 @@ jobs:
|
||||
run: sudo apt-get update && sudo apt-get -y install jq vim libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
continue-on-error: true
|
||||
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
|
||||
- name: Branch name
|
||||
run: echo running on branch ${GITHUB_REF##*/}
|
||||
|
||||
@@ -46,10 +46,10 @@ jobs:
|
||||
run: |
|
||||
echo "RUSTFLAGS=--cfg tokio_unstable" >> $GITHUB_ENV
|
||||
echo "CARGO_FEATURES=--features tokio-console" >> $GITHUB_ENV
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -8,10 +8,13 @@ on:
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-authenticator-client/**'
|
||||
- 'nym-credential-proxy/**'
|
||||
- 'nym-ip-packet-client/**'
|
||||
- 'nym-network-monitor/**'
|
||||
- 'nym-node/**'
|
||||
- 'nym-node-status-api/**'
|
||||
- 'nym-registration-client/**'
|
||||
- 'nym-statistics-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'nym-validator-rewarder/**'
|
||||
@@ -57,7 +60,7 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ env:
|
||||
|
||||
jobs:
|
||||
check-if-tag-exists:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
runs-on: arc-linux-latest-dind
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
|
||||
- name: Generate the schema
|
||||
run: make contract-schema
|
||||
|
||||
@@ -34,10 +34,10 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
@@ -6,16 +6,14 @@ on:
|
||||
paths:
|
||||
- "ts-packages/**"
|
||||
- "sdk/typescript/**"
|
||||
- "nym-connect/desktop/src/**"
|
||||
- "nym-connect/desktop/package.json"
|
||||
- "nym-wallet/src/**"
|
||||
- "nym-wallet/package.json"
|
||||
- "explorer/**"
|
||||
- "explorer-v2/**"
|
||||
- ".github/workflows/ci-lint-typescript.yml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: arc-linux-latest
|
||||
env:
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
@@ -25,26 +23,25 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- name: Install wasm-opt
|
||||
uses: ./.github/actions/install-wasm-opt
|
||||
with:
|
||||
version: '116'
|
||||
run: cargo install wasm-opt
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.23.7"
|
||||
go-version: "1.24.6"
|
||||
|
||||
- name: Install
|
||||
run: yarn
|
||||
@@ -52,7 +49,11 @@ jobs:
|
||||
- name: Build packages
|
||||
run: yarn build:ci
|
||||
|
||||
- name: Install again
|
||||
run: yarn
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
- name: Typecheck with tsc
|
||||
run: yarn tsc
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-linux
|
||||
runs-on: arc-linux-latest-dind
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -25,10 +25,10 @@ jobs:
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -8,6 +8,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/first-interaction@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: 'Thank you for raising this issue'
|
||||
pr-message: 'Thank you for making this first PR'
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue_message: 'Thank you for raising this issue'
|
||||
pr_message: 'Thank you for making this first PR'
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
- name: Install cargo deny
|
||||
run: cargo install --locked cargo-deny
|
||||
- name: Run cargo deny
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
override: true
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -53,10 +53,10 @@ jobs:
|
||||
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
|
||||
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
override: true
|
||||
|
||||
- name: Build all binaries
|
||||
|
||||
@@ -11,9 +11,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ jobs:
|
||||
with:
|
||||
node-version: 21
|
||||
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
|
||||
- name: Add Rust target for x86_64-apple-darwin
|
||||
run: rustup target add x86_64-apple-darwin
|
||||
|
||||
@@ -33,10 +33,10 @@ jobs:
|
||||
node-version: 21
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
|
||||
- name: Install project dependencies
|
||||
shell: bash
|
||||
|
||||
@@ -29,10 +29,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust stable
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
|
||||
- name: Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -17,10 +17,13 @@ jobs:
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Install Rust stable
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
@@ -29,9 +32,9 @@ jobs:
|
||||
run: cargo install wasm-opt
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.23.7"
|
||||
go-version: "1.24.6"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
@@ -3,11 +3,6 @@ name: Build and upload Node Status agent container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gateway_probe_git_ref:
|
||||
type: string
|
||||
default: nym-vpn-core-v1.4.0
|
||||
required: true
|
||||
description: Which gateway probe git ref to build the image with
|
||||
release_image:
|
||||
description: 'Tag image as a release'
|
||||
required: true
|
||||
@@ -43,16 +38,6 @@ jobs:
|
||||
VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml)
|
||||
echo "result=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: cleanup-gateway-probe-ref
|
||||
id: cleanup_gateway_probe_ref
|
||||
run: |
|
||||
GATEWAY_PROBE_GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }}
|
||||
GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}"
|
||||
echo "git_ref=${GIT_REF_SLUG}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set GIT_TAG variable
|
||||
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Initialize RELEASE_TAG
|
||||
run: echo "RELEASE_TAG=" >> $GITHUB_ENV
|
||||
|
||||
@@ -61,24 +46,12 @@ jobs:
|
||||
run: echo "RELEASE_TAG=golden-" >> $GITHUB_ENV
|
||||
|
||||
- name: Set IMAGE_NAME_AND_TAGS variable
|
||||
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
|
||||
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
|
||||
|
||||
- name: New env vars
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
|
||||
# - name: Remove existing tag if exists
|
||||
# run: |
|
||||
# if git rev-parse $${{ env.GIT_TAG }} >/dev/null 2>&1; then
|
||||
# git push --delete origin $${{ env.GIT_TAG }}
|
||||
# git tag -d $${{ env.GIT_TAG }}
|
||||
# fi
|
||||
|
||||
# - name: Create tag
|
||||
# run: |
|
||||
# git tag -a $${{ env.GIT_TAG }} -m "Version ${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}"
|
||||
# git push origin $${{ env.GIT_TAG }}
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
@@ -4,6 +4,100 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.21-mozzarella] (2025-11-25)
|
||||
|
||||
- [bugfix] Tunnel not waiting on MixnetClient to shut down cleanly ([#6225])
|
||||
- bugfix: fix credential proxy upgrade mode attestation url arg ([#6202])
|
||||
- HTTP API resilience enable & domain rotation conditions ([#6200])
|
||||
- Remove debug feature from http-macro spec in gateway probe ([#6195])
|
||||
- DNS relibility and troubleshooting ([#6179])
|
||||
- [bugfix] Distinguish authenticator errors by credential spent ([#6176])
|
||||
- Typescript SDK 1.4.1 ([#6146])
|
||||
- Enable URL rotation and retries for mixnet gateway init ([#6126])
|
||||
- Feature/credential proxy jwt ([#5957])
|
||||
|
||||
[#6225]: https://github.com/nymtech/nym/pull/6225
|
||||
[#6202]: https://github.com/nymtech/nym/pull/6202
|
||||
[#6200]: https://github.com/nymtech/nym/pull/6200
|
||||
[#6195]: https://github.com/nymtech/nym/pull/6195
|
||||
[#6179]: https://github.com/nymtech/nym/pull/6179
|
||||
[#6176]: https://github.com/nymtech/nym/pull/6176
|
||||
[#6146]: https://github.com/nymtech/nym/pull/6146
|
||||
[#6126]: https://github.com/nymtech/nym/pull/6126
|
||||
[#5957]: https://github.com/nymtech/nym/pull/5957
|
||||
|
||||
## [2025.20-leerdammer] (2025-11-12)
|
||||
|
||||
- Max/tweak ts sdk actions ([#6185])
|
||||
- chore: resolve clippy 1.91 warnings ([#6168])
|
||||
- [chore] Remove unused dependencies ([#6151])
|
||||
- Use typed-builder for registration client builder config ([#6150])
|
||||
- tommy is too quick ([#6149])
|
||||
- configurable mixnet client startup timeout ([#6148])
|
||||
- [Feature/operators]: QUIC bridge deployment script v2 ([#6145])
|
||||
- Bugfix: Add circuit breaker ([#6143])
|
||||
- bugfix: update internal owner address in transferred share ([#6139])
|
||||
- Update quic_bridge_deployment.sh for IPv4 and .deb package ([#6138])
|
||||
- feat: expose more explicit new_with_fronted_urls builder for http API client ([#6136])
|
||||
- bugfix: update stored epoch share when changing ownership ([#6135])
|
||||
- Domain fronting ([#6134])
|
||||
- bugfix: update stored epoch share when changing announce address ([#6131])
|
||||
|
||||
[#6185]: https://github.com/nymtech/nym/pull/6185
|
||||
[#6168]: https://github.com/nymtech/nym/pull/6168
|
||||
[#6151]: https://github.com/nymtech/nym/pull/6151
|
||||
[#6150]: https://github.com/nymtech/nym/pull/6150
|
||||
[#6149]: https://github.com/nymtech/nym/pull/6149
|
||||
[#6148]: https://github.com/nymtech/nym/pull/6148
|
||||
[#6145]: https://github.com/nymtech/nym/pull/6145
|
||||
[#6143]: https://github.com/nymtech/nym/pull/6143
|
||||
[#6139]: https://github.com/nymtech/nym/pull/6139
|
||||
[#6138]: https://github.com/nymtech/nym/pull/6138
|
||||
[#6136]: https://github.com/nymtech/nym/pull/6136
|
||||
[#6135]: https://github.com/nymtech/nym/pull/6135
|
||||
[#6134]: https://github.com/nymtech/nym/pull/6134
|
||||
[#6131]: https://github.com/nymtech/nym/pull/6131
|
||||
|
||||
## [2025.19-kase] (2025-10-30)
|
||||
|
||||
- update ns agent workflow ([#6154])
|
||||
- Cherry pick - request #6143 from nymtech/bugfix/mix-tx-closed-v2 ([#6153])
|
||||
- bugfix: nym-credential-proxy query params parsing regression ([#6121])
|
||||
- bugfix: revert some dep updates introduced in #6043 ([#6120])
|
||||
- Skip ipv6 metadata endpoint request ([#6118])
|
||||
- update to no longer use 1mb files ([#6117])
|
||||
- chore: restore pending dkg contract state migration ([#6116])
|
||||
- Revert "Propagate cancel token to mixnet client" ([#6115])
|
||||
- Update dirs to 6.0 ([#6109])
|
||||
- Propagate cancel token to mixnet client ([#6105])
|
||||
- bugfix: retrieve and update ticketbook in the same query ([#6101])
|
||||
- bugfix: include network name in the default gateway probe config path ([#6100])
|
||||
- Bugfix/incompatibility fixes ([#6099])
|
||||
- [DOCs/operators] QUIC deployment script & docs ([#6098])
|
||||
- bugfix: testnet manager 02sql migration ([#6096])
|
||||
- feat: move gateway probe to monorepo (and update to rust edition 2024) ([#6094])
|
||||
- bugfix: use custom topology provider for list of init gateways ([#6092])
|
||||
- Max/fix wasm client + build commands ([#6043])
|
||||
|
||||
[#6154]: https://github.com/nymtech/nym/pull/6154
|
||||
[#6153]: https://github.com/nymtech/nym/pull/6153
|
||||
[#6121]: https://github.com/nymtech/nym/pull/6121
|
||||
[#6120]: https://github.com/nymtech/nym/pull/6120
|
||||
[#6118]: https://github.com/nymtech/nym/pull/6118
|
||||
[#6117]: https://github.com/nymtech/nym/pull/6117
|
||||
[#6116]: https://github.com/nymtech/nym/pull/6116
|
||||
[#6115]: https://github.com/nymtech/nym/pull/6115
|
||||
[#6109]: https://github.com/nymtech/nym/pull/6109
|
||||
[#6105]: https://github.com/nymtech/nym/pull/6105
|
||||
[#6101]: https://github.com/nymtech/nym/pull/6101
|
||||
[#6100]: https://github.com/nymtech/nym/pull/6100
|
||||
[#6099]: https://github.com/nymtech/nym/pull/6099
|
||||
[#6098]: https://github.com/nymtech/nym/pull/6098
|
||||
[#6096]: https://github.com/nymtech/nym/pull/6096
|
||||
[#6094]: https://github.com/nymtech/nym/pull/6094
|
||||
[#6092]: https://github.com/nymtech/nym/pull/6092
|
||||
[#6043]: https://github.com/nymtech/nym/pull/6043
|
||||
|
||||
## [2025.18-jarlsberg] (2025-10-14)
|
||||
|
||||
- ns-api: add descriptions to dVPN gateway responses ([#6102])
|
||||
|
||||
Generated
+27
-16
@@ -2579,7 +2579,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "extension-storage"
|
||||
version = "1.4.0-rc.0"
|
||||
version = "1.4.1"
|
||||
dependencies = [
|
||||
"bip39",
|
||||
"console_error_panic_hook",
|
||||
@@ -4478,7 +4478,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mix-fetch-wasm"
|
||||
version = "1.4.0-rc.0"
|
||||
version = "1.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
@@ -4488,6 +4488,7 @@ dependencies = [
|
||||
"nym-ordered-buffer",
|
||||
"nym-service-providers-common",
|
||||
"nym-socks5-requests",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
@@ -4824,7 +4825,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.67"
|
||||
version = "1.1.70"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5050,7 +5051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.64"
|
||||
version = "1.1.67"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -5133,7 +5134,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.67"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -5250,8 +5251,8 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"cosmrs",
|
||||
"nym-crypto",
|
||||
"nym-gateway-client",
|
||||
"nym-gateway-requests",
|
||||
"serde",
|
||||
"sqlx",
|
||||
@@ -5283,7 +5284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client-wasm"
|
||||
version = "1.4.0-rc.0"
|
||||
version = "1.4.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -5462,8 +5463,11 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-ecash-signer-check",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-network-defaults",
|
||||
"nym-pemstore",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.22",
|
||||
@@ -5534,6 +5538,7 @@ dependencies = [
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-serde-helpers",
|
||||
"nym-upgrade-mode-check",
|
||||
"reqwest 0.12.22",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
@@ -5661,6 +5666,7 @@ dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"aes-gcm-siv",
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"blake3",
|
||||
"bs58",
|
||||
@@ -5674,10 +5680,12 @@ dependencies = [
|
||||
"jwt-simple",
|
||||
"nym-pemstore",
|
||||
"nym-sphinx-types",
|
||||
"nym-test-utils",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"subtle-encoding",
|
||||
"thiserror 2.0.12",
|
||||
@@ -6050,6 +6058,7 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"wasmtimer",
|
||||
]
|
||||
@@ -6354,7 +6363,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.65"
|
||||
version = "1.1.68"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -6404,7 +6413,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.19.0"
|
||||
version = "1.22.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -6642,7 +6651,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-tester-wasm"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"js-sys",
|
||||
@@ -6931,7 +6940,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.67"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -7191,7 +7200,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-statistics-api"
|
||||
version = "0.2.1"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -7205,7 +7214,6 @@ dependencies = [
|
||||
"nym-statistics-common",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"time",
|
||||
@@ -7378,18 +7386,21 @@ dependencies = [
|
||||
"jwt-simple",
|
||||
"nym-crypto",
|
||||
"nym-http-api-client",
|
||||
"nym-test-utils",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tracing",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bip32",
|
||||
@@ -7670,7 +7681,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.29"
|
||||
version = "0.1.32"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7824,9 +7835,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.109"
|
||||
version = "0.9.110"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
||||
checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
||||
+7
-2
@@ -150,7 +150,7 @@ members = [
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
"tools/internal/mixnet-connectivity-check",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
@@ -264,7 +264,7 @@ generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
handlebars = "3.5.5"
|
||||
hex = "0.4.3"
|
||||
hickory-resolver = "0.25"
|
||||
hickory-resolver = "0.25.2"
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
http = "1"
|
||||
@@ -453,6 +453,11 @@ opt-level = 'z'
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
suspicious = "deny"
|
||||
complexity = "deny"
|
||||
perf = "deny"
|
||||
style = "deny"
|
||||
|
||||
unwrap_used = "deny"
|
||||
expect_used = "deny"
|
||||
todo = "deny"
|
||||
|
||||
@@ -107,16 +107,16 @@ sdk-wasm-build:
|
||||
$(MAKE) -C nym-browser-extension/storage wasm-pack
|
||||
$(MAKE) -C wasm/client
|
||||
$(MAKE) -C wasm/node-tester
|
||||
# $(MAKE) -C wasm/mix-fetch
|
||||
$(MAKE) -C wasm/mix-fetch
|
||||
$(MAKE) -C wasm/zknym-lib
|
||||
# $(MAKE) -C wasm/full-nym-wasm
|
||||
|
||||
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
|
||||
sdk-typescript-build:
|
||||
npx lerna run --scope @nymproject/sdk build --stream
|
||||
# npx lerna run --scope @nymproject/mix-fetch build --stream
|
||||
# npx lerna run --scope @nymproject/node-tester build --stream
|
||||
# yarn --cwd sdk/typescript/codegen/contract-clients build
|
||||
npx lerna run --scope @nymproject/mix-fetch build --stream
|
||||
npx lerna run --scope @nymproject/node-tester build --stream
|
||||
yarn --cwd sdk/typescript/codegen/contract-clients build
|
||||
|
||||
# NOTE: These targets are part of the main workspace (but not as wasm32-unknown-unknown)
|
||||
WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.67"
|
||||
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.64"
|
||||
version = "1.1.67"
|
||||
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"
|
||||
|
||||
+2
-2
@@ -6,14 +6,14 @@
|
||||
{
|
||||
"name": "exists",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int"
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "06e743d143fcc4be20ca2af5e99b19f15d22fff72490473587a14cdc046fda32"
|
||||
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT * FROM remote_gateway_details WHERE gateway_id_bs58 = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "gateway_id_bs58",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "gateway_owner_address",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "gateway_listener",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "derived_aes128_ctr_blake3_hmac_keys_bs58",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "derived_aes256_gcm_siv_key",
|
||||
"ordinal": 4,
|
||||
"type_info": "Blob"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "0e85ec18da67cf4e3df04ad80136571f6e920eb2290f20b1b8c5b0ab4b489985"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE remote_gateway_details\n SET\n derived_aes128_ctr_blake3_hmac_keys_bs58 = ?,\n derived_aes256_gcm_siv_key = ?\n WHERE gateway_id_bs58 = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0f1dfb89f1eb39f4a58787af0f53a7a93afb7e4d2e54e2d38fd79d31c8575a54"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO custom_gateway_details(gateway_id_bs58, data) \n VALUES (?, ?)\n ",
|
||||
"query": "\n INSERT INTO custom_gateway_details(gateway_id_bs58, data)\n VALUES (?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -8,5 +8,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b059bc3688b6b7f83f47048db9897720fd4e6f3211bf74030a9638f7bf6738e4"
|
||||
"hash": "2c113b37864f9fec7e64c0f8fdd38edcdf149acfd38c56a4db3bbf97bdb13210"
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n rgd.gateway_id_bs58,\n derived_aes256_gcm_siv_key,\n gateway_listener,\n fallback_listener\n FROM\n remote_gateway_details AS rgd\n INNER JOIN\n remote_gateway_shared_keys AS rgsk\n ON\n rgd.gateway_id_bs58 = rgsk.gateway_id_bs58\n WHERE\n rgd.gateway_id_bs58 = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "gateway_id_bs58",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "derived_aes256_gcm_siv_key",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "gateway_listener",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "fallback_listener",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "4b739e12ea8d917cb17580337caeabb05f0e3ddbec04fdfa111d0fc86ba75505"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO remote_gateway_shared_keys(gateway_id_bs58, derived_aes256_gcm_siv_key)\n VALUES (?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "700a75acbcd90c74baa7823c40739a8ff8a26400c1d2bd45a689970bf1ba0e66"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type) \n VALUES (?, ?, ?)\n ",
|
||||
"query": "\n INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type)\n VALUES (?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -8,5 +8,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8909fd329e7e5fb16c4989b15b3d3a12bba1569520e01f6f074178e23d6ee89e"
|
||||
"hash": "727598e516090da6d26e36d09062b60ccb76d6468f359891428c0bfb96ddd7ef"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO remote_gateway_details(gateway_id_bs58, gateway_listener, fallback_listener)\n VALUES (?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a64a557ba87d4b2c7457857afa7ebc7d4f895fc4991da18ec02c9e250bea0fe0"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener)\n VALUES (?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a6939bea03b10cde810a9a099bd597b4f51092e30a41c4085a8f8668f039f7c0"
|
||||
}
|
||||
@@ -9,7 +9,6 @@ rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
cosmrs.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
@@ -20,6 +19,7 @@ zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
|
||||
nym-crypto = { path = "../../crypto", features = ["asymmetric"] }
|
||||
nym-gateway-requests = { path = "../../gateway-requests" }
|
||||
nym-gateway-client = { path = "../../client-libs/gateway-client" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
workspace = true
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
CREATE TABLE remote_gateway_details_temp
|
||||
(
|
||||
gateway_id_bs58 TEXT NOT NULL UNIQUE PRIMARY KEY REFERENCES registered_gateway (gateway_id_bs58),
|
||||
derived_aes256_gcm_siv_key BLOB NOT NULL,
|
||||
gateway_listener TEXT NOT NULL,
|
||||
fallback_listener TEXT,
|
||||
expiration_timestamp DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- keep only registrations with a non null aes256 key
|
||||
INSERT INTO remote_gateway_details_temp SELECT gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_listener, NULL, datetime(0, 'unixepoch') FROM remote_gateway_details WHERE derived_aes256_gcm_siv_key IS NOT NULL;
|
||||
|
||||
DROP TABLE remote_gateway_details;
|
||||
ALTER TABLE remote_gateway_details_temp RENAME TO remote_gateway_details;
|
||||
|
||||
-- delete registrations with no key
|
||||
DELETE FROM registered_gateway WHERE gateway_id_bs58 NOT IN ( SELECT gateway_id_bs58 FROM remote_gateway_details);
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
types::{
|
||||
RawActiveGateway, RawCustomGatewayDetails, RawRegisteredGateway, RawRemoteGatewayDetails,
|
||||
},
|
||||
RawGatewayPublishedData,
|
||||
};
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
@@ -144,13 +145,11 @@ impl StorageManager {
|
||||
&self,
|
||||
gateway_id: &str,
|
||||
) -> Result<RawRemoteGatewayDetails, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
RawRemoteGatewayDetails,
|
||||
"SELECT * FROM remote_gateway_details WHERE gateway_id_bs58 = ?",
|
||||
gateway_id
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
// query_as! macro doesn't use fromRow
|
||||
sqlx::query_as("SELECT * FROM remote_gateway_details WHERE gateway_id_bs58 = ?")
|
||||
.bind(gateway_id)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn set_remote_gateway_details(
|
||||
@@ -159,41 +158,36 @@ impl StorageManager {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener)
|
||||
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_listener, fallback_listener, expiration_timestamp)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
"#,
|
||||
remote.gateway_id_bs58,
|
||||
remote.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
remote.derived_aes256_gcm_siv_key,
|
||||
remote.gateway_owner_address,
|
||||
remote.gateway_listener,
|
||||
remote.published_data.gateway_listener,
|
||||
remote.published_data.fallback_listener,
|
||||
remote.published_data.expiration_timestamp
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_remote_gateway_key(
|
||||
pub(crate) async fn update_remote_gateway_published_data(
|
||||
&self,
|
||||
gateway_id_bs58: &str,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: Option<&str>,
|
||||
derived_aes256_gcm_siv_key: Option<&[u8]>,
|
||||
published_data: &RawGatewayPublishedData,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE remote_gateway_details
|
||||
SET
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58 = ?,
|
||||
derived_aes256_gcm_siv_key = ?
|
||||
WHERE gateway_id_bs58 = ?
|
||||
UPDATE remote_gateway_details SET gateway_listener = ?, fallback_listener = ?, expiration_timestamp = ? WHERE gateway_id_bs58 = ?
|
||||
"#,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
published_data.gateway_listener,
|
||||
published_data.fallback_listener,
|
||||
published_data.expiration_timestamp,
|
||||
gateway_id_bs58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,18 +2,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
ActiveGateway, BadGateway, GatewayDetails, GatewayRegistration, GatewayType,
|
||||
GatewaysDetailsStore, StorageError,
|
||||
ActiveGateway, BadGateway, GatewayDetails, GatewayPublishedData, GatewayRegistration,
|
||||
GatewayType, GatewaysDetailsStore, StorageError,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use manager::StorageManager;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::SharedSymmetricKey;
|
||||
use std::path::Path;
|
||||
|
||||
pub mod error;
|
||||
mod manager;
|
||||
mod models;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OnDiskGatewaysDetails {
|
||||
@@ -134,16 +132,15 @@ impl GatewaysDetailsStore for OnDiskGatewaysDetails {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
async fn update_gateway_published_data(
|
||||
&self,
|
||||
gateway_id: ed25519::PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
gateway_id: &ed25519::PublicKey,
|
||||
published_data: &GatewayPublishedData,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.manager
|
||||
.update_remote_gateway_key(
|
||||
.update_remote_gateway_published_data(
|
||||
&gateway_id.to_base58_string(),
|
||||
None,
|
||||
Some(updated_key.as_bytes()),
|
||||
&published_data.into(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
@@ -2,10 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::{ActiveGateway, GatewayRegistration};
|
||||
use crate::{BadGateway, GatewayDetails, GatewaysDetailsStore};
|
||||
use crate::{BadGateway, GatewayDetails, GatewayPublishedData, GatewaysDetailsStore};
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_gateway_requests::{SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
@@ -96,26 +95,17 @@ impl GatewaysDetailsStore for InMemGatewaysDetails {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
async fn update_gateway_published_data(
|
||||
&self,
|
||||
gateway_id: PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
gateway_id: &ed25519::PublicKey,
|
||||
published_data: &GatewayPublishedData,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
if let Some(target) = guard.gateways.get_mut(&gateway_id.to_string()) {
|
||||
let GatewayDetails::Remote(details) = &mut target.details else {
|
||||
return Ok(());
|
||||
};
|
||||
assert_eq!(Arc::strong_count(&details.shared_key), 1);
|
||||
|
||||
// eh. that's nasty, but it's only ever used for ephemeral clients so should be fine for now...
|
||||
details.shared_key = Arc::new(SharedGatewayKey::Current(
|
||||
SharedSymmetricKey::try_from_bytes(updated_key.as_bytes()).unwrap(),
|
||||
))
|
||||
if let Some(gateway) = guard.gateways.get_mut(&gateway_id.to_base58_string()) {
|
||||
if let GatewayDetails::Remote(ref mut remote_details) = gateway.details {
|
||||
remote_details.published_data = published_data.clone();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -18,16 +18,6 @@ pub enum BadGateway {
|
||||
source: Ed25519RecoveryError,
|
||||
},
|
||||
|
||||
#[error("the account owner of gateway {gateway_id} ({raw_owner}) is malformed: {source}")]
|
||||
MalformedGatewayOwnerAccountAddress {
|
||||
gateway_id: String,
|
||||
|
||||
raw_owner: String,
|
||||
|
||||
#[source]
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
|
||||
#[error("the shared keys provided for gateway {gateway_id} are malformed: {source}")]
|
||||
MalformedSharedKeys {
|
||||
gateway_id: String,
|
||||
@@ -50,4 +40,12 @@ pub enum BadGateway {
|
||||
#[source]
|
||||
source: url::ParseError,
|
||||
},
|
||||
|
||||
#[error("the listening address ({raw_listener}) is malformed: {source}")]
|
||||
MalformedListenerNoId {
|
||||
raw_listener: String,
|
||||
|
||||
#[source]
|
||||
source: url::ParseError,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::SharedSymmetricKey;
|
||||
use std::error::Error;
|
||||
|
||||
pub mod backend;
|
||||
@@ -60,10 +59,11 @@ pub trait GatewaysDetailsStore {
|
||||
details: &GatewayRegistration,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
/// Update the gateway details
|
||||
async fn update_gateway_published_data(
|
||||
&self,
|
||||
gateway_id: ed25519::PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
gateway_id: &ed25519::PublicKey,
|
||||
published_data: &GatewayPublishedData,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
/// Remove given gateway details from the underlying store.
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::BadGateway;
|
||||
use cosmrs::AccountId;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_gateway_client::client::GatewayListeners;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub const REMOTE_GATEWAY_TYPE: &str = "remote";
|
||||
pub const CUSTOM_GATEWAY_TYPE: &str = "custom";
|
||||
const GATEWAY_DETAILS_TTL: Duration = Duration::days(7);
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ActiveGateway {
|
||||
@@ -65,15 +66,13 @@ impl From<GatewayDetails> for GatewayRegistration {
|
||||
impl GatewayDetails {
|
||||
pub fn new_remote(
|
||||
gateway_id: ed25519::PublicKey,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
gateway_owner_address: Option<AccountId>,
|
||||
gateway_listener: Url,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
published_data: GatewayPublishedData,
|
||||
) -> Self {
|
||||
GatewayDetails::Remote(RemoteGatewayDetails {
|
||||
gateway_id,
|
||||
shared_key,
|
||||
gateway_owner_address,
|
||||
gateway_listener,
|
||||
published_data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -88,13 +87,20 @@ impl GatewayDetails {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shared_key(&self) -> Option<&SharedGatewayKey> {
|
||||
pub fn shared_key(&self) -> Option<&SharedSymmetricKey> {
|
||||
match self {
|
||||
GatewayDetails::Remote(details) => Some(&details.shared_key),
|
||||
GatewayDetails::Custom(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn details_exipration(&self) -> Option<OffsetDateTime> {
|
||||
match self {
|
||||
GatewayDetails::Remote(details) => Some(details.published_data.expiration_timestamp),
|
||||
GatewayDetails::Custom(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_custom(&self) -> bool {
|
||||
matches!(self, GatewayDetails::Custom(..))
|
||||
}
|
||||
@@ -164,14 +170,78 @@ pub struct RegisteredGateway {
|
||||
pub gateway_type: GatewayType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GatewayPublishedData {
|
||||
pub listeners: GatewayListeners,
|
||||
pub expiration_timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl GatewayPublishedData {
|
||||
pub fn new(listeners: GatewayListeners) -> GatewayPublishedData {
|
||||
GatewayPublishedData {
|
||||
listeners,
|
||||
expiration_timestamp: OffsetDateTime::now_utc() + GATEWAY_DETAILS_TTL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct RawGatewayPublishedData {
|
||||
pub gateway_listener: String,
|
||||
pub fallback_listener: Option<String>,
|
||||
pub expiration_timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a GatewayPublishedData> for RawGatewayPublishedData {
|
||||
fn from(value: &'a GatewayPublishedData) -> Self {
|
||||
Self {
|
||||
gateway_listener: value.listeners.primary.to_string(),
|
||||
fallback_listener: value.listeners.fallback.as_ref().map(|uri| uri.to_string()),
|
||||
expiration_timestamp: value.expiration_timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RawGatewayPublishedData> for GatewayPublishedData {
|
||||
type Error = BadGateway;
|
||||
|
||||
fn try_from(value: RawGatewayPublishedData) -> Result<Self, Self::Error> {
|
||||
let gateway_listener: Url = Url::parse(&value.gateway_listener).map_err(|source| {
|
||||
BadGateway::MalformedListenerNoId {
|
||||
raw_listener: value.gateway_listener.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
let fallback_listener = value
|
||||
.fallback_listener
|
||||
.as_ref()
|
||||
.map(|uri| {
|
||||
Url::parse(uri).map_err(|source| BadGateway::MalformedListenerNoId {
|
||||
raw_listener: uri.to_owned(),
|
||||
source,
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
Ok(GatewayPublishedData {
|
||||
listeners: GatewayListeners {
|
||||
primary: gateway_listener,
|
||||
fallback: fallback_listener,
|
||||
},
|
||||
expiration_timestamp: value.expiration_timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct RawRemoteGatewayDetails {
|
||||
pub gateway_id_bs58: String,
|
||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||
pub gateway_owner_address: Option<String>,
|
||||
pub gateway_listener: String,
|
||||
pub derived_aes256_gcm_siv_key: Vec<u8>,
|
||||
#[zeroize(skip)]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(flatten))]
|
||||
pub published_data: RawGatewayPublishedData,
|
||||
}
|
||||
|
||||
impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
|
||||
@@ -186,81 +256,26 @@ impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
|
||||
}
|
||||
})?;
|
||||
|
||||
let shared_key =
|
||||
match (
|
||||
&value.derived_aes256_gcm_siv_key,
|
||||
&value.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
) {
|
||||
(None, None) => {
|
||||
return Err(BadGateway::MissingSharedKey {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
})
|
||||
}
|
||||
(Some(aes256gcm_siv), _) => {
|
||||
let current_key =
|
||||
SharedSymmetricKey::try_from_bytes(aes256gcm_siv).map_err(|source| {
|
||||
BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
SharedGatewayKey::Current(current_key)
|
||||
}
|
||||
(None, Some(aes128ctr_hmac)) => {
|
||||
let legacy_key = LegacySharedKeys::try_from_base58_string(aes128ctr_hmac)
|
||||
.map_err(|source| BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
source,
|
||||
})?;
|
||||
SharedGatewayKey::Legacy(legacy_key)
|
||||
}
|
||||
};
|
||||
|
||||
let gateway_owner_address = value
|
||||
.gateway_owner_address
|
||||
.as_ref()
|
||||
.map(|raw_owner| {
|
||||
AccountId::from_str(raw_owner).map_err(|source| {
|
||||
BadGateway::MalformedGatewayOwnerAccountAddress {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
raw_owner: raw_owner.clone(),
|
||||
source,
|
||||
}
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let gateway_listener = Url::parse(&value.gateway_listener).map_err(|source| {
|
||||
BadGateway::MalformedListener {
|
||||
let shared_key = SharedSymmetricKey::try_from_bytes(&value.derived_aes256_gcm_siv_key)
|
||||
.map_err(|source| BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
raw_listener: value.gateway_listener.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
})?;
|
||||
|
||||
Ok(RemoteGatewayDetails {
|
||||
gateway_id,
|
||||
shared_key: Arc::new(shared_key),
|
||||
gateway_owner_address,
|
||||
gateway_listener,
|
||||
published_data: value.published_data.clone().try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a RemoteGatewayDetails> for RawRemoteGatewayDetails {
|
||||
fn from(value: &'a RemoteGatewayDetails) -> Self {
|
||||
let (derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key) =
|
||||
match value.shared_key.deref() {
|
||||
SharedGatewayKey::Current(key) => (None, Some(key.to_bytes())),
|
||||
SharedGatewayKey::Legacy(key) => (Some(key.to_base58_string()), None),
|
||||
};
|
||||
|
||||
RawRemoteGatewayDetails {
|
||||
gateway_id_bs58: value.gateway_id.to_base58_string(),
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
gateway_owner_address: value.gateway_owner_address.as_ref().map(|o| o.to_string()),
|
||||
gateway_listener: value.gateway_listener.to_string(),
|
||||
derived_aes256_gcm_siv_key: value.shared_key.to_bytes(),
|
||||
published_data: (&value.published_data).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,11 +284,9 @@ impl<'a> From<&'a RemoteGatewayDetails> for RawRemoteGatewayDetails {
|
||||
pub struct RemoteGatewayDetails {
|
||||
pub gateway_id: ed25519::PublicKey,
|
||||
|
||||
pub shared_key: Arc<SharedGatewayKey>,
|
||||
pub shared_key: Arc<SharedSymmetricKey>,
|
||||
|
||||
pub gateway_owner_address: Option<AccountId>,
|
||||
|
||||
pub gateway_listener: Url,
|
||||
pub published_data: GatewayPublishedData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -87,6 +87,7 @@ where
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
Some(common_args.latency_based_selection),
|
||||
common_args.force_tls_gateway,
|
||||
false,
|
||||
);
|
||||
tracing::debug!("Gateway selection specification: {selection_spec:?}");
|
||||
|
||||
@@ -167,6 +168,7 @@ where
|
||||
identity: gateway_details.gateway_id,
|
||||
active: common_args.set_active,
|
||||
typ: gateway_registration.details.typ().to_string(),
|
||||
endpoint: Some(gateway_details.gateway_listener.clone()),
|
||||
endpoint: Some(gateway_details.published_data.listeners.primary.clone()),
|
||||
fallback_endpoint: gateway_details.published_data.listeners.fallback.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ where
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
Some(common_args.latency_based_selection),
|
||||
common_args.force_tls_gateway,
|
||||
false,
|
||||
);
|
||||
tracing::debug!("Gateway selection specification: {selection_spec:?}");
|
||||
|
||||
|
||||
@@ -56,7 +56,8 @@ where
|
||||
identity: remote_details.gateway_id,
|
||||
active: active_gateway == Some(remote_details.gateway_id),
|
||||
typ: GatewayType::Remote.to_string(),
|
||||
endpoint: Some(remote_details.gateway_listener),
|
||||
endpoint: Some(remote_details.published_data.listeners.primary.clone()),
|
||||
fallback_endpoint: remote_details.published_data.listeners.fallback.clone(),
|
||||
}),
|
||||
GatewayDetails::Custom(_) => info.push(GatewayInfo {
|
||||
registration: gateway.registration_timestamp,
|
||||
@@ -64,6 +65,7 @@ where
|
||||
active: active_gateway == Some(gateway.details.gateway_id()),
|
||||
typ: gateway.details.typ().to_string(),
|
||||
endpoint: None,
|
||||
fallback_endpoint: None,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct GatewayInfo {
|
||||
|
||||
pub typ: String,
|
||||
pub endpoint: Option<Url>,
|
||||
pub fallback_endpoint: Option<Url>,
|
||||
}
|
||||
|
||||
impl Display for GatewayInfo {
|
||||
@@ -30,6 +31,9 @@ impl Display for GatewayInfo {
|
||||
if let Some(endpoint) = &self.endpoint {
|
||||
write!(f, " endpoint: {endpoint}")?;
|
||||
}
|
||||
if let Some(fallback_endpoint) = &self.fallback_endpoint {
|
||||
write!(f, " fallback: {fallback_endpoint}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,7 +529,6 @@ where
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
@@ -555,14 +554,7 @@ where
|
||||
shutdown_tracker.clone_shutdown_token(),
|
||||
)
|
||||
} else {
|
||||
let cfg = GatewayConfig::new(
|
||||
details.gateway_id,
|
||||
details
|
||||
.gateway_owner_address
|
||||
.as_ref()
|
||||
.map(|o| o.to_string()),
|
||||
details.gateway_listener.to_string(),
|
||||
);
|
||||
let cfg = GatewayConfig::new(details.gateway_id, details.published_data.listeners);
|
||||
GatewayClient::new(
|
||||
GatewayClientConfig::new_default()
|
||||
.with_disabled_credentials_mode(config.client.disabled_credentials_mode)
|
||||
@@ -592,32 +584,13 @@ where
|
||||
// the gateway client startup procedure is slightly more complicated now
|
||||
// we need to:
|
||||
// - perform handshake (reg or auth)
|
||||
// - check for key upgrade
|
||||
// - maybe perform another upgrade handshake
|
||||
// - check for bandwidth
|
||||
// - start background tasks
|
||||
let auth_res = gateway_client
|
||||
let _ = gateway_client
|
||||
.perform_initial_authentication()
|
||||
.await
|
||||
.map_err(gateway_failure)?;
|
||||
|
||||
if auth_res.requires_key_upgrade {
|
||||
// drop the shared_key arc because we don't need it and we can't hold it for the purposes of upgrade
|
||||
drop(auth_res);
|
||||
|
||||
let updated_key = gateway_client
|
||||
.upgrade_key_authenticated()
|
||||
.await
|
||||
.map_err(gateway_failure)?;
|
||||
|
||||
details_store
|
||||
.upgrade_stored_remote_gateway_key(gateway_client.gateway_identity(), &updated_key)
|
||||
.await.map_err(|err| {
|
||||
tracing::error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
|
||||
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
|
||||
})?
|
||||
}
|
||||
|
||||
gateway_client
|
||||
.claim_initial_bandwidth()
|
||||
.await
|
||||
@@ -636,7 +609,6 @@ where
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
@@ -667,7 +639,6 @@ where
|
||||
config,
|
||||
initialisation_result,
|
||||
bandwidth_controller,
|
||||
details_store,
|
||||
packet_router,
|
||||
stats_reporter,
|
||||
#[cfg(unix)]
|
||||
@@ -975,8 +946,7 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (reply_storage_backend, credential_store, details_store) =
|
||||
self.client_store.into_runtime_stores();
|
||||
let (reply_storage_backend, credential_store, _) = self.client_store.into_runtime_stores();
|
||||
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -1069,7 +1039,6 @@ where
|
||||
&self.config,
|
||||
init_res,
|
||||
bandwidth_controller,
|
||||
&details_store,
|
||||
gateway_packet_router,
|
||||
stats_reporter.clone(),
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::error::ClientCoreError;
|
||||
use nym_client_core_gateways_storage::{ActiveGateway, GatewayRegistration, GatewaysDetailsStore};
|
||||
use nym_client_core_gateways_storage::{
|
||||
ActiveGateway, GatewayPublishedData, GatewayRegistration, GatewaysDetailsStore,
|
||||
};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
|
||||
// helpers for error wrapping
|
||||
@@ -85,6 +87,23 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn update_stored_published_data_gateway<D>(
|
||||
details_store: &D,
|
||||
gateway_id: &ed25519::PublicKey,
|
||||
published_data: &GatewayPublishedData,
|
||||
) -> Result<(), ClientCoreError>
|
||||
where
|
||||
D: GatewaysDetailsStore,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
details_store
|
||||
.update_gateway_published_data(gateway_id, published_data)
|
||||
.await
|
||||
.map_err(|source| ClientCoreError::GatewaysDetailsStoreError {
|
||||
source: Box::new(source),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load_active_gateway_details<D>(
|
||||
details_store: &D,
|
||||
) -> Result<ActiveGateway, ClientCoreError>
|
||||
|
||||
@@ -2,210 +2,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod v1_1_33 {
|
||||
use crate::client::base_client::{
|
||||
non_wasm_helpers::setup_fs_gateways_storage,
|
||||
storage::helpers::{set_active_gateway, store_gateway_details},
|
||||
};
|
||||
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
use crate::config::disk_persistence::CommonClientPaths;
|
||||
use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
|
||||
use crate::error::ClientCoreError;
|
||||
use nym_client_core_gateways_storage::{
|
||||
CustomGatewayDetails, GatewayDetails, GatewayRegistration, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_gateway_requests::shared_key::LegacySharedKeys;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{digest::Digest, Sha256};
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
mod base64 {
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&STANDARD.encode(bytes))
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<u8>, D::Error> {
|
||||
let s = <String>::deserialize(deserializer)?;
|
||||
STANDARD.decode(s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum PersistedGatewayDetails {
|
||||
/// Standard details of a remote gateway
|
||||
Default(PersistedGatewayConfig),
|
||||
|
||||
/// Custom gateway setup, such as for a client embedded inside gateway itself
|
||||
Custom(PersistedCustomGatewayDetails),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
struct PersistedGatewayConfig {
|
||||
/// The hash of the shared keys to ensure the correct ones are used with those gateway details.
|
||||
#[serde(with = "base64")]
|
||||
key_hash: Vec<u8>,
|
||||
|
||||
/// Actual gateway details being persisted.
|
||||
details: OldGatewayEndpointConfigV1_1_33,
|
||||
}
|
||||
|
||||
impl PersistedGatewayConfig {
|
||||
fn verify(&self, shared_key: &LegacySharedKeys) -> bool {
|
||||
let key_bytes = Zeroizing::new(shared_key.to_bytes());
|
||||
|
||||
let mut key_hasher = Sha256::new();
|
||||
key_hasher.update(&key_bytes);
|
||||
let key_hash = key_hasher.finalize();
|
||||
|
||||
self.key_hash == key_hash.deref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct PersistedCustomGatewayDetails {
|
||||
gateway_id: String,
|
||||
}
|
||||
|
||||
fn load_shared_key<P: AsRef<Path>>(path: P) -> Result<LegacySharedKeys, ClientCoreError> {
|
||||
// the shared key was a simple pem file
|
||||
Ok(nym_pemstore::load_key(path)?)
|
||||
}
|
||||
|
||||
fn gateway_details_from_raw(
|
||||
gateway_id: String,
|
||||
gateway_owner: String,
|
||||
gateway_listener: String,
|
||||
gateway_shared_key: LegacySharedKeys,
|
||||
) -> Result<GatewayDetails, ClientCoreError> {
|
||||
Ok(GatewayDetails::Remote(RemoteGatewayDetails {
|
||||
gateway_id: gateway_id
|
||||
.parse()
|
||||
.map_err(|err| ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway id was malformed: {err}"),
|
||||
})?,
|
||||
shared_key: Arc::new(gateway_shared_key.into()),
|
||||
gateway_owner_address: Some(gateway_owner.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway owner address was malformed: {err}"),
|
||||
}
|
||||
})?),
|
||||
gateway_listener: gateway_listener.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway listener address was malformed: {err}"),
|
||||
}
|
||||
})?,
|
||||
}))
|
||||
}
|
||||
|
||||
// helper to extract shared key and gateway details into the new GatewayRegistration
|
||||
fn extract_gateway_registration(
|
||||
storage_paths: &CommonClientPathsV1_1_33,
|
||||
) -> Result<GatewayRegistration, ClientCoreError> {
|
||||
let details_file = std::fs::File::open(&storage_paths.gateway_details).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!(
|
||||
"failed to open gateway details file at {}: {err}",
|
||||
storage_paths.gateway_details.display()
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
// in v1.1.33 of the clients, the gateway details struct was saved as json
|
||||
let details: PersistedGatewayDetails =
|
||||
serde_json::from_reader(details_file).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!(
|
||||
"failed to deserialize gateway details from {}: {err}",
|
||||
storage_paths.gateway_details.display()
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
let details = match details {
|
||||
PersistedGatewayDetails::Default(config) => {
|
||||
let gateway_shared_key =
|
||||
load_shared_key(&storage_paths.keys.gateway_shared_key_file)?;
|
||||
if !config.verify(&gateway_shared_key) {
|
||||
return Err(ClientCoreError::UpgradeFailure {
|
||||
message: "failed to verify consistency of the existing gateway details"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
gateway_details_from_raw(
|
||||
config.details.gateway_id,
|
||||
config.details.gateway_owner,
|
||||
config.details.gateway_listener,
|
||||
gateway_shared_key,
|
||||
)?
|
||||
}
|
||||
PersistedGatewayDetails::Custom(custom) => {
|
||||
GatewayDetails::Custom(CustomGatewayDetails {
|
||||
gateway_id: custom.gateway_id.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway id was malformed: {err}"),
|
||||
}
|
||||
})?,
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(details.into())
|
||||
}
|
||||
|
||||
// it's responsibility of the caller to ensure this is called **after** new registration has already been saved
|
||||
fn remove_old_gateway_details(storage_paths: &CommonClientPathsV1_1_33) -> std::io::Result<()> {
|
||||
std::fs::remove_file(&storage_paths.gateway_details)?;
|
||||
|
||||
if storage_paths.keys.gateway_shared_key_file.exists() {
|
||||
std::fs::remove_file(&storage_paths.keys.gateway_shared_key_file)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_gateway_details(
|
||||
old_storage_paths: &CommonClientPathsV1_1_33,
|
||||
new_storage_paths: &CommonClientPaths,
|
||||
preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
|
||||
_old_storage_paths: &CommonClientPathsV1_1_33,
|
||||
_new_storage_paths: &CommonClientPaths,
|
||||
_preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let gateway_registration = match preloaded_config {
|
||||
Some(config) => {
|
||||
let gateway_shared_key =
|
||||
load_shared_key(&old_storage_paths.keys.gateway_shared_key_file)?;
|
||||
gateway_details_from_raw(
|
||||
config.gateway_id,
|
||||
config.gateway_owner,
|
||||
config.gateway_listener,
|
||||
gateway_shared_key,
|
||||
)?
|
||||
.into()
|
||||
}
|
||||
None => extract_gateway_registration(old_storage_paths)?,
|
||||
};
|
||||
|
||||
// since we're migrating to a brand new store, the store should be empty
|
||||
// and thus set the 'new' gateway as the active one
|
||||
let details_store =
|
||||
setup_fs_gateways_storage(&new_storage_paths.gateway_registrations).await?;
|
||||
store_gateway_details(&details_store, &gateway_registration).await?;
|
||||
set_active_gateway(
|
||||
&details_store,
|
||||
&gateway_registration.details.gateway_id().to_base58_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
remove_old_gateway_details(old_storage_paths).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("failed to remove old data: {err}"),
|
||||
}
|
||||
})
|
||||
Err(ClientCoreError::UnsupportedMigration(
|
||||
"migration of legacy keys has been removed and is no longer supported".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,12 @@ where
|
||||
/// Key used to encrypt and decrypt content of an ACK packet.
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
/// Average delay an acknowledgement packet is going to get delayed at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Average delay a forward packet is going to get delayed at a single mixnode.
|
||||
average_packet_delay: Duration,
|
||||
|
||||
/// Defines configuration options related to cover traffic.
|
||||
cover_traffic: config::CoverTraffic,
|
||||
|
||||
@@ -122,6 +125,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
LoopCoverTrafficStream {
|
||||
ack_key,
|
||||
average_ack_delay,
|
||||
average_packet_delay: traffic_config.average_packet_delay,
|
||||
cover_traffic: cover_config,
|
||||
next_delay,
|
||||
mix_tx,
|
||||
@@ -187,7 +191,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
&self.ack_key,
|
||||
&self.our_full_destination,
|
||||
self.average_ack_delay,
|
||||
self.cover_traffic.loop_cover_traffic_average_delay,
|
||||
self.average_packet_delay,
|
||||
cover_traffic_packet_size,
|
||||
self.packet_type,
|
||||
) {
|
||||
|
||||
@@ -6,7 +6,7 @@ use nym_crypto::{
|
||||
asymmetric::{ed25519, x25519},
|
||||
hkdf::{DerivationMaterial, InvalidLength},
|
||||
};
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::sync::Arc;
|
||||
@@ -106,7 +106,5 @@ fn _assert_keys_zeroize_on_drop() {
|
||||
_assert_zeroize_on_drop::<ed25519::KeyPair>();
|
||||
_assert_zeroize_on_drop::<x25519::KeyPair>();
|
||||
_assert_zeroize_on_drop::<AckKey>();
|
||||
_assert_zeroize_on_drop::<LegacySharedKeys>();
|
||||
_assert_zeroize_on_drop::<SharedSymmetricKey>();
|
||||
_assert_zeroize_on_drop::<SharedGatewayKey>();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use std::fmt::Debug;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, error};
|
||||
use tracing::debug;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use futures::channel::oneshot;
|
||||
|
||||
@@ -8,7 +8,6 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||
use std::sync::Weak;
|
||||
use tracing::error;
|
||||
|
||||
pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
|
||||
@@ -16,6 +16,9 @@ use std::path::PathBuf;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientCoreError {
|
||||
#[error("could not perform the state migration: {0}")]
|
||||
UnsupportedMigration(String),
|
||||
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
@@ -43,9 +46,12 @@ pub enum ClientCoreError {
|
||||
#[error("Invalid URL: {0}")]
|
||||
InvalidUrl(String),
|
||||
|
||||
#[error("node doesn't advertise ip addresses : {0}")]
|
||||
MissingIpAddress(String),
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
|
||||
|
||||
#[error("no gateways on network")]
|
||||
NoGatewaysOnNetwork,
|
||||
@@ -163,6 +169,9 @@ pub enum ClientCoreError {
|
||||
#[error("custom selection of gateway was expected")]
|
||||
CustomGatewaySelectionExpected,
|
||||
|
||||
#[error("custom selection of gateway was unexpected")]
|
||||
UnexpectedCustomGatewaySelection,
|
||||
|
||||
#[error("the persisted gateway details were set for a custom setup")]
|
||||
UnexpectedPersistedCustomGatewayDetails,
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::error::ClientCoreError;
|
||||
use crate::init::types::RegistrationResult;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::client::GatewayListeners;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::{IdentityKeyRef, NymApiClientExt};
|
||||
@@ -379,12 +380,12 @@ pub(super) fn get_specified_gateway(
|
||||
|
||||
pub(super) async fn register_with_gateway(
|
||||
gateway_id: ed25519::PublicKey,
|
||||
gateway_listener: Url,
|
||||
gateway_listeners: GatewayListeners,
|
||||
our_identity: Arc<ed25519::KeyPair>,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Result<RegistrationResult, ClientCoreError> {
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway_listener,
|
||||
gateway_listeners,
|
||||
gateway_id,
|
||||
our_identity.clone(),
|
||||
#[cfg(unix)]
|
||||
@@ -409,14 +410,6 @@ pub(super) async fn register_with_gateway(
|
||||
}
|
||||
})?;
|
||||
|
||||
// this should NEVER happen, if it did, it means the function was misused,
|
||||
// because for any fresh **registration**, the derived key is always up to date
|
||||
if auth_response.requires_key_upgrade {
|
||||
return Err(ClientCoreError::UnexpectedKeyUpgrade {
|
||||
gateway_id: gateway_id.to_base58_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(RegistrationResult {
|
||||
shared_keys: auth_response.initial_shared_key,
|
||||
authenticated_ephemeral_client: gateway_client,
|
||||
@@ -441,7 +434,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_multiple_urls_prepared_for_retries() {
|
||||
let urls = vec![
|
||||
let urls = [
|
||||
Url::parse("https://api1.nym.com").unwrap(),
|
||||
Url::parse("https://api2.nym.com").unwrap(),
|
||||
Url::parse("https://api3.nym.com").unwrap(),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use crate::client::base_client::storage::helpers::{
|
||||
has_gateway_details, load_active_gateway_details, load_client_keys, load_gateway_details,
|
||||
store_gateway_details,
|
||||
store_gateway_details, update_stored_published_data_gateway,
|
||||
};
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
@@ -16,8 +16,8 @@ use crate::init::helpers::{
|
||||
use crate::init::types::{
|
||||
GatewaySelectionSpecification, GatewaySetup, InitialisationResult, SelectedGateway,
|
||||
};
|
||||
use nym_client_core_gateways_storage::GatewaysDetailsStore;
|
||||
use nym_client_core_gateways_storage::{GatewayDetails, GatewayRegistration};
|
||||
use nym_client_core_gateways_storage::{GatewayPublishedData, GatewaysDetailsStore};
|
||||
use nym_gateway_client::client::InitGatewayClient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use rand::rngs::OsRng;
|
||||
@@ -71,21 +71,28 @@ where
|
||||
let mut rng = OsRng;
|
||||
|
||||
let selected_gateway = match selection_specification {
|
||||
GatewaySelectionSpecification::UniformRemote { must_use_tls } => {
|
||||
GatewaySelectionSpecification::UniformRemote {
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
} => {
|
||||
let gateway = uniformly_random_gateway(&mut rng, &available_gateways, must_use_tls)?;
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls)?
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
|
||||
}
|
||||
GatewaySelectionSpecification::RemoteByLatency { must_use_tls } => {
|
||||
GatewaySelectionSpecification::RemoteByLatency {
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
} => {
|
||||
let gateway =
|
||||
choose_gateway_by_latency(&mut rng, &available_gateways, must_use_tls).await?;
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls)?
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
|
||||
}
|
||||
GatewaySelectionSpecification::Specified {
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
identity,
|
||||
} => {
|
||||
let gateway = get_specified_gateway(&identity, &available_gateways, must_use_tls)?;
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls)?
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
|
||||
}
|
||||
GatewaySelectionSpecification::Custom {
|
||||
gateway_identity,
|
||||
@@ -105,15 +112,15 @@ where
|
||||
let (gateway_details, authenticated_ephemeral_client) = match selected_gateway {
|
||||
SelectedGateway::Remote {
|
||||
gateway_id,
|
||||
gateway_owner_address,
|
||||
gateway_listener,
|
||||
|
||||
gateway_listeners,
|
||||
} => {
|
||||
// if we're using a 'normal' gateway setup, do register
|
||||
let our_identity = client_keys.identity_keypair();
|
||||
|
||||
let registration = helpers::register_with_gateway(
|
||||
gateway_id,
|
||||
gateway_listener.clone(),
|
||||
gateway_listeners.clone(),
|
||||
our_identity,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
@@ -123,8 +130,7 @@ where
|
||||
GatewayDetails::new_remote(
|
||||
gateway_id,
|
||||
registration.shared_keys,
|
||||
gateway_owner_address,
|
||||
gateway_listener,
|
||||
GatewayPublishedData::new(gateway_listeners),
|
||||
),
|
||||
Some(registration.authenticated_ephemeral_client),
|
||||
)
|
||||
@@ -150,6 +156,46 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn refresh_gateway_published_data<D>(
|
||||
details_store: &D,
|
||||
registration: GatewayRegistration,
|
||||
available_gateways: Vec<RoutingNode>,
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
) -> Result<(), ClientCoreError>
|
||||
where
|
||||
D: GatewaysDetailsStore,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let gateway_id = registration.gateway_id().to_base58_string();
|
||||
tracing::trace!("Updating gateway details : {gateway_id}");
|
||||
|
||||
let gateway = get_specified_gateway(&gateway_id, &available_gateways, must_use_tls)?;
|
||||
let selected_gateway = SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?;
|
||||
|
||||
let new_gateway_listeners = match selected_gateway {
|
||||
SelectedGateway::Remote {
|
||||
gateway_listeners, ..
|
||||
} => gateway_listeners,
|
||||
SelectedGateway::Custom { .. } => {
|
||||
// this should not happen, as `from_topology_node` returns a Remote
|
||||
Err(ClientCoreError::UnexpectedCustomGatewaySelection)?
|
||||
}
|
||||
};
|
||||
|
||||
let new_published_data = GatewayPublishedData::new(new_gateway_listeners);
|
||||
|
||||
// update gateway details
|
||||
update_stored_published_data_gateway(
|
||||
details_store,
|
||||
®istration.gateway_id(),
|
||||
&new_published_data,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn use_loaded_gateway_details<K, D>(
|
||||
key_store: &K,
|
||||
details_store: &D,
|
||||
|
||||
@@ -10,12 +10,11 @@ use nym_client_core_gateways_storage::{
|
||||
GatewayRegistration, GatewaysDetailsStore, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::client::InitGatewayClient;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_client::client::{GatewayListeners, InitGatewayClient};
|
||||
use nym_gateway_client::SharedSymmetricKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use serde::Serialize;
|
||||
use std::fmt::{Debug, Display};
|
||||
#[cfg(unix)]
|
||||
@@ -28,9 +27,7 @@ pub enum SelectedGateway {
|
||||
Remote {
|
||||
gateway_id: ed25519::PublicKey,
|
||||
|
||||
gateway_owner_address: Option<AccountId>,
|
||||
|
||||
gateway_listener: Url,
|
||||
gateway_listeners: GatewayListeners,
|
||||
},
|
||||
Custom {
|
||||
gateway_id: ed25519::PublicKey,
|
||||
@@ -42,24 +39,40 @@ impl SelectedGateway {
|
||||
pub fn from_topology_node(
|
||||
node: RoutingNode,
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
) -> Result<Self, ClientCoreError> {
|
||||
// for now, let's use 'old' behaviour, if you want to change it, you can pass it up the enum stack yourself : )
|
||||
let prefer_ipv6 = false;
|
||||
|
||||
let gateway_listener = if must_use_tls {
|
||||
node.ws_entry_address_tls()
|
||||
.ok_or(ClientCoreError::UnsupportedWssProtocol {
|
||||
gateway: node.identity_key.to_base58_string(),
|
||||
})?
|
||||
let (gateway_listener, fallback_listener) = if must_use_tls {
|
||||
// WSS main, no fallback
|
||||
let primary =
|
||||
node.ws_entry_address_tls()
|
||||
.ok_or(ClientCoreError::UnsupportedWssProtocol {
|
||||
gateway: node.identity_key.to_base58_string(),
|
||||
})?;
|
||||
(primary, None)
|
||||
} else {
|
||||
node.ws_entry_address(prefer_ipv6)
|
||||
.ok_or(ClientCoreError::UnsupportedEntry {
|
||||
let (maybe_primary, fallback) =
|
||||
node.ws_entry_address_with_fallback(prefer_ipv6, no_hostname);
|
||||
(
|
||||
maybe_primary.ok_or(ClientCoreError::UnsupportedEntry {
|
||||
id: node.node_id,
|
||||
identity: node.identity_key.to_base58_string(),
|
||||
})?
|
||||
})?,
|
||||
fallback,
|
||||
)
|
||||
};
|
||||
|
||||
let gateway_listener =
|
||||
let fallback_listener_url = fallback_listener.and_then(|address| {
|
||||
Url::parse(&address)
|
||||
.inspect_err(|err| {
|
||||
tracing::warn!("Malformed fallback listener, none will be used : {err}")
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
let gateway_listener_url =
|
||||
Url::parse(&gateway_listener).map_err(|source| ClientCoreError::MalformedListener {
|
||||
gateway_id: node.identity_key.to_base58_string(),
|
||||
raw_listener: gateway_listener,
|
||||
@@ -68,8 +81,10 @@ impl SelectedGateway {
|
||||
|
||||
Ok(SelectedGateway::Remote {
|
||||
gateway_id: node.identity_key,
|
||||
gateway_owner_address: None,
|
||||
gateway_listener,
|
||||
gateway_listeners: GatewayListeners {
|
||||
primary: gateway_listener_url,
|
||||
fallback: fallback_listener_url,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,7 +113,7 @@ impl SelectedGateway {
|
||||
/// - shared keys derived between ourselves and the node
|
||||
/// - an authenticated handle of an ephemeral handle created for the purposes of registration
|
||||
pub struct RegistrationResult {
|
||||
pub shared_keys: Arc<SharedGatewayKey>,
|
||||
pub shared_keys: Arc<SharedSymmetricKey>,
|
||||
pub authenticated_ephemeral_client: InitGatewayClient,
|
||||
}
|
||||
|
||||
@@ -145,20 +160,36 @@ impl InitialisationResult {
|
||||
pub fn gateway_id(&self) -> ed25519::PublicKey {
|
||||
self.gateway_registration.details.gateway_id()
|
||||
}
|
||||
|
||||
// indicates if the remote gateway details TTL has expired
|
||||
pub fn exipred_details(&self) -> bool {
|
||||
if let Some(expiration_timestamp) = self.gateway_registration.details.details_exipration() {
|
||||
OffsetDateTime::now_utc() > expiration_timestamp
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum GatewaySelectionSpecification {
|
||||
/// Uniformly choose a random remote gateway.
|
||||
UniformRemote { must_use_tls: bool },
|
||||
UniformRemote {
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
},
|
||||
|
||||
/// Should the new, remote, gateway be selected based on latency.
|
||||
RemoteByLatency { must_use_tls: bool },
|
||||
RemoteByLatency {
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
},
|
||||
|
||||
/// Gateway with this specific identity should be chosen.
|
||||
// JS: I don't really like the name of this enum variant but couldn't think of anything better at the time
|
||||
Specified {
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
identity: IdentityKey,
|
||||
},
|
||||
|
||||
@@ -174,6 +205,7 @@ impl Default for GatewaySelectionSpecification {
|
||||
fn default() -> Self {
|
||||
GatewaySelectionSpecification::UniformRemote {
|
||||
must_use_tls: false,
|
||||
no_hostname: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,16 +215,24 @@ impl GatewaySelectionSpecification {
|
||||
gateway_identity: Option<String>,
|
||||
latency_based_selection: Option<bool>,
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
) -> Self {
|
||||
if let Some(identity) = gateway_identity {
|
||||
GatewaySelectionSpecification::Specified {
|
||||
identity,
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
}
|
||||
} else if let Some(true) = latency_based_selection {
|
||||
GatewaySelectionSpecification::RemoteByLatency { must_use_tls }
|
||||
GatewaySelectionSpecification::RemoteByLatency {
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
}
|
||||
} else {
|
||||
GatewaySelectionSpecification::UniformRemote { must_use_tls }
|
||||
GatewaySelectionSpecification::UniformRemote {
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,6 +355,7 @@ pub struct InitResults {
|
||||
pub encryption_key: String,
|
||||
pub gateway_id: String,
|
||||
pub gateway_listener: String,
|
||||
pub fallback_listener: Option<String>,
|
||||
pub gateway_registration: OffsetDateTime,
|
||||
pub address: Recipient,
|
||||
}
|
||||
@@ -332,7 +373,13 @@ impl InitResults {
|
||||
identity_key: address.identity().to_base58_string(),
|
||||
encryption_key: address.encryption_key().to_base58_string(),
|
||||
gateway_id: gateway.gateway_id.to_base58_string(),
|
||||
gateway_listener: gateway.gateway_listener.to_string(),
|
||||
gateway_listener: gateway.published_data.listeners.primary.to_string(),
|
||||
fallback_listener: gateway
|
||||
.published_data
|
||||
.listeners
|
||||
.fallback
|
||||
.as_ref()
|
||||
.map(|uri| uri.to_string()),
|
||||
gateway_registration: registration,
|
||||
address,
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ pub(crate) async fn connect_async(
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||
use nym_gateway_requests::{
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
|
||||
GatewayRequestsError, SensitiveServerResponse, ServerResponse, SharedGatewayKey,
|
||||
SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersion,
|
||||
GatewayProtocolVersionExt, GatewayRequestsError, ServerResponse, SharedSymmetricKey,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
||||
@@ -47,43 +47,39 @@ use std::os::raw::c_int as RawFd;
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::sleep;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub mod config;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) mod websockets;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use websockets::connect_async;
|
||||
use crate::client::websockets::connect_async_with_fallback;
|
||||
|
||||
pub struct GatewayConfig {
|
||||
pub gateway_identity: ed25519::PublicKey,
|
||||
|
||||
// currently a dead field
|
||||
pub gateway_owner: Option<String>,
|
||||
|
||||
pub gateway_listener: String,
|
||||
pub gateway_listeners: GatewayListeners,
|
||||
}
|
||||
|
||||
impl GatewayConfig {
|
||||
pub fn new(
|
||||
gateway_identity: ed25519::PublicKey,
|
||||
gateway_owner: Option<String>,
|
||||
gateway_listener: String,
|
||||
) -> Self {
|
||||
pub fn new(gateway_identity: ed25519::PublicKey, gateway_listeners: GatewayListeners) -> Self {
|
||||
GatewayConfig {
|
||||
gateway_identity,
|
||||
gateway_owner,
|
||||
gateway_listener,
|
||||
gateway_listeners,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GatewayListeners {
|
||||
pub primary: Url,
|
||||
pub fallback: Option<Url>,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct AuthenticationResponse {
|
||||
pub initial_shared_key: Arc<SharedGatewayKey>,
|
||||
pub requires_key_upgrade: bool,
|
||||
pub initial_shared_key: Arc<SharedSymmetricKey>,
|
||||
}
|
||||
|
||||
// TODO: this should be refactored into a state machine that keeps track of its authentication state
|
||||
@@ -92,17 +88,16 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
|
||||
authenticated: bool,
|
||||
bandwidth: ClientBandwidth,
|
||||
gateway_address: String,
|
||||
gateway_addresses: GatewayListeners,
|
||||
gateway_identity: ed25519::PublicKey,
|
||||
local_identity: Arc<ed25519::KeyPair>,
|
||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
||||
shared_key: Option<Arc<SharedSymmetricKey>>,
|
||||
connection: SocketState,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
|
||||
// currently unused (but populated)
|
||||
negotiated_protocol: Option<u8>,
|
||||
negotiated_protocol: Option<GatewayProtocolVersion>,
|
||||
|
||||
// Callback on the fd as soon as the connection has been established
|
||||
#[cfg(unix)]
|
||||
@@ -119,7 +114,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
gateway_config: GatewayConfig,
|
||||
local_identity: Arc<ed25519::KeyPair>,
|
||||
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
|
||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
||||
shared_key: Option<Arc<SharedSymmetricKey>>,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
@@ -130,7 +125,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
cfg,
|
||||
authenticated: false,
|
||||
bandwidth: ClientBandwidth::new_empty(),
|
||||
gateway_address: gateway_config.gateway_listener,
|
||||
gateway_addresses: gateway_config.gateway_listeners,
|
||||
gateway_identity: gateway_config.gateway_identity,
|
||||
local_identity,
|
||||
shared_key,
|
||||
@@ -149,7 +144,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.gateway_identity
|
||||
}
|
||||
|
||||
pub fn shared_key(&self) -> Option<Arc<SharedGatewayKey>> {
|
||||
pub fn shared_key(&self) -> Option<Arc<SharedSymmetricKey>> {
|
||||
self.shared_key.clone()
|
||||
}
|
||||
|
||||
@@ -166,10 +161,12 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(clippy::unreachable)]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(mut socket) => Ok((*socket).close(None).await?),
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
// SAFETY: this is only called after the caller has already recovered the connection
|
||||
unreachable!("this branch should have never been reached!")
|
||||
}
|
||||
_ => Ok(()), // no need to do anything in those cases
|
||||
@@ -177,6 +174,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[allow(clippy::unreachable)]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(socket) => {
|
||||
@@ -184,6 +182,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
Ok(())
|
||||
}
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
// SAFETY: this is only called after the caller has already recovered the connection
|
||||
unreachable!("this branch should have never been reached!")
|
||||
}
|
||||
_ => Ok(()), // no need to do anything in those cases
|
||||
@@ -200,12 +199,19 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!(
|
||||
"Attempting to establish connection to gateway at: {}",
|
||||
self.gateway_address
|
||||
);
|
||||
let (ws_stream, _) = connect_async(
|
||||
&self.gateway_address,
|
||||
if let Some(fallback_url) = &self.gateway_addresses.fallback {
|
||||
debug!(
|
||||
"Attempting to establish connection to gateway at: {}, with fallback at: {fallback_url}",
|
||||
self.gateway_addresses.primary
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"Attempting to establish connection to gateway at: {}",
|
||||
self.gateway_addresses.primary
|
||||
);
|
||||
}
|
||||
let (ws_stream, _) = connect_async_with_fallback(
|
||||
&self.gateway_addresses,
|
||||
#[cfg(unix)]
|
||||
self.connection_fd_callback.clone(),
|
||||
)
|
||||
@@ -218,7 +224,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
let ws_stream = match JSWebsocket::new(&self.gateway_address) {
|
||||
let ws_stream = match JSWebsocket::new(self.gateway_addresses.primary.as_ref()) {
|
||||
Ok(ws_stream) => ws_stream,
|
||||
Err(e) => {
|
||||
return Err(GatewayClientError::NetworkErrorWasm(e));
|
||||
@@ -271,7 +277,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if let Some(shared_key) = self.shared_key() {
|
||||
let encrypted = message.encrypt(&*shared_key)?;
|
||||
let encrypted = message.encrypt(&shared_key)?;
|
||||
Box::pin(self.send_websocket_message_without_response(encrypted)).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -458,61 +464,28 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_gateway_protocol(
|
||||
&self,
|
||||
gateway_protocol: Option<u8>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||
|
||||
// right now there are no failure cases here, but this might change in the future
|
||||
match gateway_protocol {
|
||||
None => {
|
||||
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
|
||||
// note: in +1.2.0 we will have to return a hard error here
|
||||
Ok(())
|
||||
}
|
||||
Some(v) if v > CURRENT_PROTOCOL_VERSION => {
|
||||
let err = GatewayClientError::IncompatibleProtocol {
|
||||
gateway: Some(v),
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
error!("{err}");
|
||||
Err(err)
|
||||
}
|
||||
|
||||
Some(_) => {
|
||||
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn register(
|
||||
&mut self,
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
supported_gateway_protocol: GatewayProtocolVersion,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
debug_assert!(self.connection.is_available());
|
||||
log::debug!(
|
||||
"registering with gateway. using legacy key derivation: {}",
|
||||
!derive_aes256_gcm_siv_key
|
||||
);
|
||||
log::debug!("registering with gateway");
|
||||
|
||||
// it's fine to instantiate it here as it's only used once (during authentication or registration)
|
||||
// and putting it into the GatewayClient struct would be a hassle
|
||||
let mut rng = OsRng;
|
||||
|
||||
let shared_key = match &mut self.connection {
|
||||
let handshake_result = match &mut self.connection {
|
||||
SocketState::Available(ws_stream) => client_handshake(
|
||||
&mut rng,
|
||||
ws_stream,
|
||||
self.local_identity.as_ref(),
|
||||
self.gateway_identity,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
derive_aes256_gcm_siv_key,
|
||||
supported_gateway_protocol,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.shutdown_token.clone(),
|
||||
)
|
||||
@@ -521,99 +494,26 @@ impl<C, St> GatewayClient<C, St> {
|
||||
_ => return Err(GatewayClientError::ConnectionInInvalidState),
|
||||
}?;
|
||||
|
||||
let (authentication_status, gateway_protocol) = match self.read_control_response().await? {
|
||||
ServerResponse::Register {
|
||||
protocol_version,
|
||||
status,
|
||||
} => (status, protocol_version),
|
||||
let authentication_status = match self.read_control_response().await? {
|
||||
ServerResponse::Register { status, .. } => status,
|
||||
ServerResponse::Error { message } => {
|
||||
return Err(GatewayClientError::GatewayError(message))
|
||||
}
|
||||
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
};
|
||||
|
||||
self.check_gateway_protocol(gateway_protocol)?;
|
||||
self.authenticated = authentication_status;
|
||||
|
||||
if self.authenticated {
|
||||
self.shared_key = Some(Arc::new(shared_key));
|
||||
self.shared_key = Some(Arc::new(handshake_result.derived_key));
|
||||
}
|
||||
|
||||
// populate the negotiated protocol for future uses
|
||||
self.negotiated_protocol = gateway_protocol;
|
||||
self.negotiated_protocol = Some(handshake_result.negotiated_protocol);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn upgrade_key_authenticated(
|
||||
&mut self,
|
||||
) -> Result<Zeroizing<SharedSymmetricKey>, GatewayClientError> {
|
||||
info!("*** STARTING AES128CTR-HMAC KEY UPGRADE INTO AES256GCM-SIV***");
|
||||
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
if !shared_key.is_legacy() {
|
||||
return Err(GatewayClientError::KeyAlreadyUpgraded);
|
||||
}
|
||||
|
||||
// make sure we have the only reference, so we could safely swap it
|
||||
if Arc::strong_count(shared_key) != 1 {
|
||||
return Err(GatewayClientError::KeyAlreadyInUse);
|
||||
}
|
||||
|
||||
assert!(shared_key.is_legacy());
|
||||
let legacy_key = shared_key.unwrap_legacy();
|
||||
let (updated_key, hkdf_salt) = legacy_key.upgrade();
|
||||
let derived_key_digest = updated_key.digest();
|
||||
|
||||
let upgrade_request = ClientRequest::UpgradeKey {
|
||||
hkdf_salt,
|
||||
derived_key_digest,
|
||||
}
|
||||
.encrypt(legacy_key)?;
|
||||
|
||||
info!("sending upgrade request and awaiting the acknowledgement back");
|
||||
let (ciphertext, nonce) = match self
|
||||
.send_websocket_message_with_response(upgrade_request)
|
||||
.await?
|
||||
{
|
||||
ServerResponse::EncryptedResponse { ciphertext, nonce } => (ciphertext, nonce),
|
||||
ServerResponse::Error { message } => {
|
||||
return Err(GatewayClientError::GatewayError(message))
|
||||
}
|
||||
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
};
|
||||
|
||||
// attempt to decrypt it using NEW key
|
||||
let Ok(response) = SensitiveServerResponse::decrypt(&ciphertext, &nonce, &updated_key)
|
||||
else {
|
||||
return Err(GatewayClientError::FatalKeyUpgradeFailure);
|
||||
};
|
||||
|
||||
match response {
|
||||
SensitiveServerResponse::KeyUpgradeAck { .. } => {
|
||||
info!("received key upgrade acknowledgement")
|
||||
}
|
||||
_ => return Err(GatewayClientError::FatalKeyUpgradeFailure),
|
||||
}
|
||||
|
||||
// perform in memory swap and make a copy for updating storage
|
||||
let zeroizing_updated_key = updated_key.zeroizing_clone();
|
||||
self.shared_key = Some(Arc::new(updated_key.into()));
|
||||
|
||||
Ok(zeroizing_updated_key)
|
||||
}
|
||||
|
||||
async fn send_authenticate_request_and_handle_response(
|
||||
&mut self,
|
||||
msg: ClientControlRequest,
|
||||
@@ -624,11 +524,14 @@ impl<C, St> GatewayClient<C, St> {
|
||||
status,
|
||||
bandwidth_remaining,
|
||||
} => {
|
||||
self.check_gateway_protocol(protocol_version)?;
|
||||
if protocol_version.is_future_version() {
|
||||
error!("the gateway insists on using v{protocol_version} protocol which is not supported by this client");
|
||||
return Err(GatewayClientError::AuthenticationFailure);
|
||||
}
|
||||
self.authenticated = status;
|
||||
self.bandwidth.update_and_maybe_log(bandwidth_remaining);
|
||||
|
||||
self.negotiated_protocol = protocol_version;
|
||||
self.negotiated_protocol = Some(protocol_version);
|
||||
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
|
||||
|
||||
Ok(())
|
||||
@@ -638,56 +541,42 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn authenticate_v1(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!("using v1 authentication");
|
||||
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
let self_address = self
|
||||
.local_identity
|
||||
.public_key()
|
||||
.derive_destination_address();
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate(
|
||||
self_address,
|
||||
shared_key,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
)?;
|
||||
self.send_authenticate_request_and_handle_response(msg)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> {
|
||||
async fn authenticate_v2(
|
||||
&mut self,
|
||||
requested_protocol_version: GatewayProtocolVersion,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
debug!("using v2 authentication");
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate_v2(shared_key, &self.local_identity)?;
|
||||
let msg = ClientControlRequest::new_authenticate_v2(
|
||||
shared_key,
|
||||
&self.local_identity,
|
||||
requested_protocol_version,
|
||||
)?;
|
||||
self.send_authenticate_request_and_handle_response(msg)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate(&mut self, use_v2: bool) -> Result<(), GatewayClientError> {
|
||||
async fn authenticate(
|
||||
&mut self,
|
||||
requested_protocol_version: GatewayProtocolVersion,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
debug!("authenticating with gateway");
|
||||
|
||||
if use_v2 {
|
||||
self.authenticate_v2().await
|
||||
} else {
|
||||
self.authenticate_v1().await
|
||||
}
|
||||
// use the highest possible protocol version the gateway has announced support for
|
||||
self.authenticate_v2(requested_protocol_version).await
|
||||
}
|
||||
|
||||
/// Helper method to either call register or authenticate based on self.shared_key value
|
||||
#[instrument(skip_all,
|
||||
fields(
|
||||
gateway = %self.gateway_identity,
|
||||
gateway_address = %self.gateway_address
|
||||
gateway_address = %self.gateway_addresses.primary
|
||||
)
|
||||
)]
|
||||
pub async fn perform_initial_authentication(
|
||||
@@ -698,15 +587,11 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// 1. check gateway's protocol version
|
||||
let gw_protocol = match self.get_gateway_protocol().await {
|
||||
Ok(protocol) => Some(protocol),
|
||||
Err(_) => {
|
||||
// if we failed to send the request, it means the gateway is running the old binary,
|
||||
// so it has reset our connection - we have to reconnect
|
||||
self.establish_connection().await?;
|
||||
None
|
||||
}
|
||||
};
|
||||
// if we failed to get this request resolved, it means the gateway is on an old version
|
||||
// that definitely does not support auth v2 or aes256gcm, so we bail
|
||||
let gw_protocol = self.get_gateway_protocol().await?;
|
||||
|
||||
debug!("supported gateway protocol: {gw_protocol:?}");
|
||||
|
||||
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
|
||||
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
|
||||
@@ -718,16 +603,32 @@ impl<C, St> GatewayClient<C, St> {
|
||||
if !supports_auth_v2 {
|
||||
warn!("this gateway is on an old version that doesn't support authentication v2")
|
||||
}
|
||||
|
||||
// Dropping v1 support
|
||||
if !supports_auth_v2 || !supports_aes_gcm_siv {
|
||||
// we can't continue
|
||||
return Err(GatewayClientError::IncompatibleProtocol {
|
||||
gateway: gw_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
});
|
||||
}
|
||||
|
||||
if !supports_key_rotation_info {
|
||||
warn!("this gateway is on an old version that doesn't support key rotation packets")
|
||||
}
|
||||
|
||||
let gw_protocol = if gw_protocol.is_future_version() {
|
||||
warn!("we're running outdated software as gateway is announcing protocol {gw_protocol:?} whilst we're using {}. we're going to attempt to downgrade", GatewayProtocolVersion::CURRENT);
|
||||
GatewayProtocolVersion::CURRENT
|
||||
} else {
|
||||
gw_protocol
|
||||
};
|
||||
|
||||
if self.authenticated {
|
||||
debug!("Already authenticated");
|
||||
return if let Some(shared_key) = &self.shared_key {
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade: shared_key.is_legacy() && supports_aes_gcm_siv,
|
||||
})
|
||||
} else {
|
||||
Err(GatewayClientError::AuthenticationFailureWithPreexistingSharedKey)
|
||||
@@ -735,32 +636,30 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
if self.shared_key.is_some() {
|
||||
self.authenticate(supports_auth_v2).await?;
|
||||
self.authenticate(gw_protocol).await?;
|
||||
|
||||
if self.authenticated {
|
||||
// if we are authenticated it means we MUST have an associated shared_key
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
|
||||
let requires_key_upgrade = shared_key.is_legacy() && supports_aes_gcm_siv;
|
||||
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade,
|
||||
})
|
||||
} else {
|
||||
Err(GatewayClientError::AuthenticationFailure)
|
||||
}
|
||||
} else {
|
||||
self.register(supports_aes_gcm_siv).await?;
|
||||
self.register(gw_protocol).await?;
|
||||
|
||||
// if registration didn't return an error, we MUST have an associated shared key
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
|
||||
// we're always registering with the highest supported protocol,
|
||||
// so no upgrades are required
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -828,6 +727,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
fn unchecked_bandwidth_controller(&self) -> &BandwidthController<C, St> {
|
||||
// this is an unchecked method
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.bandwidth_controller.as_ref().unwrap()
|
||||
}
|
||||
|
||||
@@ -919,6 +820,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
BinaryRequest::ForwardSphinx { packet }
|
||||
};
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
req.into_ws_message(
|
||||
self.shared_key
|
||||
.as_ref()
|
||||
@@ -1025,6 +927,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.send_with_reconnection_on_failure(msg).await
|
||||
}
|
||||
|
||||
// SAFETY: this method is only called when the connection is in `PartiallyDelegated` state
|
||||
#[allow(clippy::unreachable)]
|
||||
async fn recover_socket_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
if self.connection.is_available() {
|
||||
return Ok(());
|
||||
@@ -1054,6 +958,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
return Err(GatewayClientError::ConnectionInInvalidState);
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let partially_delegated =
|
||||
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
|
||||
SocketState::Available(conn) => {
|
||||
@@ -1069,7 +974,13 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.shutdown_token.clone(),
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
other => {
|
||||
error!(
|
||||
"attempted to start mixnet listener whilst the connection is in {} state!",
|
||||
other.name()
|
||||
);
|
||||
return Err(GatewayClientError::ConnectionInInvalidState);
|
||||
}
|
||||
};
|
||||
|
||||
self.connection = SocketState::PartiallyDelegated(partially_delegated);
|
||||
@@ -1082,8 +993,12 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
|
||||
self.authenticate(self.negotiated_protocol.supports_authenticate_v2())
|
||||
.await?;
|
||||
if let Some(negotiated_protocol) = self.negotiated_protocol {
|
||||
self.authenticate(negotiated_protocol).await?;
|
||||
} else {
|
||||
// This should never happen, because it would mean we're not registered
|
||||
return Err(GatewayClientError::NotRegistered);
|
||||
}
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
@@ -1128,7 +1043,7 @@ pub struct InitOnly;
|
||||
impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
// for initialisation we do not need credential storage. Though it's still a bit weird we have to set the generic...
|
||||
pub fn new_init(
|
||||
gateway_listener: Url,
|
||||
gateway_listeners: GatewayListeners,
|
||||
gateway_identity: ed25519::PublicKey,
|
||||
local_identity: Arc<ed25519::KeyPair>,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
@@ -1147,7 +1062,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
cfg: GatewayClientConfig::default().with_disabled_credentials_mode(true),
|
||||
authenticated: false,
|
||||
bandwidth: ClientBandwidth::new_empty(),
|
||||
gateway_address: gateway_listener.to_string(),
|
||||
gateway_addresses: gateway_listeners,
|
||||
gateway_identity,
|
||||
local_identity,
|
||||
shared_key: None,
|
||||
@@ -1179,7 +1094,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
cfg: self.cfg,
|
||||
authenticated: self.authenticated,
|
||||
bandwidth: self.bandwidth,
|
||||
gateway_address: self.gateway_address,
|
||||
gateway_addresses: self.gateway_addresses,
|
||||
gateway_identity: self.gateway_identity,
|
||||
local_identity: self.local_identity,
|
||||
shared_key: self.shared_key,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::client::GatewayListeners;
|
||||
use crate::error::GatewayClientError;
|
||||
|
||||
use nym_http_api_client::HickoryDnsResolver;
|
||||
@@ -39,7 +41,6 @@ pub(crate) async fn connect_async(
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
}
|
||||
@@ -86,3 +87,35 @@ pub(crate) async fn connect_async(
|
||||
source: Box::new(error),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) async fn connect_async_with_fallback(
|
||||
endpoints: &GatewayListeners,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
|
||||
match connect_async(
|
||||
endpoints.primary.as_ref(),
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(inner) => Ok(inner),
|
||||
Err(e) => {
|
||||
if let Some(fallback) = &endpoints.fallback {
|
||||
tracing::warn!(
|
||||
"Main endpoint failed {} : {e}, trying fallback : {fallback}",
|
||||
endpoints.primary
|
||||
);
|
||||
connect_async(
|
||||
fallback.as_ref(),
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ pub enum GatewayClientError {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
|
||||
|
||||
#[error("No shared key was provided or obtained")]
|
||||
NoSharedKeyAvailable,
|
||||
@@ -83,6 +83,9 @@ pub enum GatewayClientError {
|
||||
#[error("Client is not authenticated")]
|
||||
NotAuthenticated,
|
||||
|
||||
#[error("Client is not registered")]
|
||||
NotRegistered,
|
||||
|
||||
#[error("Client does not have enough bandwidth: estimated {0}, remaining: {1}")]
|
||||
NotEnoughBandwidth(i64, i64),
|
||||
|
||||
@@ -116,8 +119,8 @@ pub enum GatewayClientError {
|
||||
#[error("Failed to send mixnet message")]
|
||||
MixnetMsgSenderFailedToSend,
|
||||
|
||||
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway:?}")]
|
||||
IncompatibleProtocol { gateway: Option<u8>, current: u8 },
|
||||
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway}")]
|
||||
IncompatibleProtocol { gateway: u8, current: u8 },
|
||||
|
||||
#[error(
|
||||
"The packet router hasn't been set - are you sure you started up the client correctly?"
|
||||
|
||||
@@ -7,9 +7,7 @@ use tracing::{error, warn};
|
||||
use tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
pub use client::{config::GatewayClientConfig, GatewayClient, GatewayConfig};
|
||||
pub use nym_gateway_requests::shared_key::{
|
||||
LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey,
|
||||
};
|
||||
pub use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
pub use packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
PacketRouter,
|
||||
@@ -47,7 +45,7 @@ pub(crate) fn cleanup_socket_messages(
|
||||
|
||||
pub(crate) fn try_decrypt_binary_message(
|
||||
bin_msg: Vec<u8>,
|
||||
shared_keys: &SharedGatewayKey,
|
||||
shared_keys: &SharedSymmetricKey,
|
||||
) -> Option<Vec<u8>> {
|
||||
match BinaryResponse::try_from_encrypted_tagged_bytes(bin_msg, shared_keys) {
|
||||
Ok(bin_response) => match bin_response {
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{cleanup_socket_messages, try_decrypt_binary_message};
|
||||
use futures::channel::oneshot;
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_gateway_requests::{SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError};
|
||||
use nym_task::ShutdownToken;
|
||||
use si_scale::helpers::bibytes2;
|
||||
@@ -63,7 +63,7 @@ pub(crate) struct PartiallyDelegatedHandle {
|
||||
|
||||
struct PartiallyDelegatedRouter {
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
|
||||
stream_return: SplitStreamSender,
|
||||
@@ -73,7 +73,7 @@ struct PartiallyDelegatedRouter {
|
||||
impl PartiallyDelegatedRouter {
|
||||
fn new(
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
stream_return: SplitStreamSender,
|
||||
stream_return_requester: oneshot::Receiver<()>,
|
||||
@@ -197,11 +197,6 @@ impl PartiallyDelegatedRouter {
|
||||
SensitiveServerResponse::RememberMeAck {} => {
|
||||
info!("received remember me acknowledgement");
|
||||
}
|
||||
SensitiveServerResponse::KeyUpgradeAck {} => {
|
||||
warn!(
|
||||
"received illegal key upgrade acknowledgement in an authenticated client"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
warn!("received unknown SensitiveServerResponse");
|
||||
}
|
||||
@@ -277,7 +272,7 @@ impl PartiallyDelegatedHandle {
|
||||
pub(crate) fn split_and_listen_for_mixnet_messages(
|
||||
conn: WsConn,
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
shutdown: ShutdownToken,
|
||||
) -> Self {
|
||||
@@ -327,6 +322,7 @@ impl PartiallyDelegatedHandle {
|
||||
Ok(self.sink_half.send_all(&mut send_stream).await?)
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
pub(crate) async fn merge(self) -> Result<WsConn, GatewayClientError> {
|
||||
let (mut stream_receiver, notify) = self.delegated_stream;
|
||||
|
||||
@@ -355,8 +351,10 @@ impl PartiallyDelegatedHandle {
|
||||
// in receive_res
|
||||
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
|
||||
let stream = stream_results?;
|
||||
|
||||
// the error is thrown when trying to reunite sink and stream that did not originate
|
||||
// from the same split which is impossible to happen here
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Ok(self.sink_half.reunite(stream).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -387,4 +385,13 @@ impl SocketState {
|
||||
SocketState::Available(_) | SocketState::PartiallyDelegated(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self) -> &'static str {
|
||||
match self {
|
||||
SocketState::Available(_) => "available",
|
||||
SocketState::PartiallyDelegated(_) => "partially delegated",
|
||||
SocketState::NotConnected => "not connected",
|
||||
SocketState::Invalid => "invalid",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ workspace = true
|
||||
features = ["json", "rustls-tls"]
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
bip39 = { workspace = true }
|
||||
cosmrs = { workspace = true, features = ["bip32"] }
|
||||
ts-rs = { workspace = true }
|
||||
|
||||
@@ -7,6 +7,7 @@ use cosmrs::{tx, AccountId, Coin, Denom};
|
||||
use nym_validator_client::http_client;
|
||||
use nym_validator_client::nyxd::CosmWasmClient;
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use nym_validator_client::signing::signer::OfflineSigner;
|
||||
use nym_validator_client::signing::tx_signer::TxSigner;
|
||||
use nym_validator_client::signing::SignerData;
|
||||
|
||||
@@ -19,8 +20,8 @@ async fn main() {
|
||||
let validator = "https://rpc.sandbox.nymtech.net";
|
||||
let to_address: AccountId = "n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa".parse().unwrap();
|
||||
|
||||
let signer = DirectSecp256k1HdWallet::from_mnemonic(prefix, signer_mnemonic);
|
||||
let signer_address = signer.try_derive_accounts().unwrap()[0].address().clone();
|
||||
let signer = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, signer_mnemonic).unwrap();
|
||||
let signer_address = signer.signer_addresses()[0].clone();
|
||||
|
||||
// local 'client' ONLY signing messages
|
||||
let tx_signer = signer;
|
||||
@@ -57,9 +58,15 @@ async fn main() {
|
||||
100000u32,
|
||||
);
|
||||
|
||||
let tx_raw = tx_signer
|
||||
.sign_direct(&signer_address, vec![send_msg], fee, memo, signer_data)
|
||||
.unwrap();
|
||||
let tx_raw = TxSigner::sign_direct(
|
||||
&tx_signer,
|
||||
&signer_address,
|
||||
vec![send_msg],
|
||||
fee,
|
||||
memo,
|
||||
signer_data,
|
||||
)
|
||||
.unwrap();
|
||||
let tx_bytes = tx_raw.to_bytes().unwrap();
|
||||
|
||||
// compare balances from before and after the tx
|
||||
|
||||
@@ -5,8 +5,7 @@ use crate::nyxd::{self, NyxdClient};
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use crate::signing::signer::{NoSigner, OfflineSigner};
|
||||
use crate::{
|
||||
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient,
|
||||
ValidatorClientError,
|
||||
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ValidatorClientError,
|
||||
};
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -164,7 +163,7 @@ impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
|
||||
) -> Result<DirectSigningHttpRpcValidatorClient, ValidatorClientError> {
|
||||
let rpc_client = http_client(config.nyxd_url.as_str())?;
|
||||
let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
|
||||
|
||||
Ok(Self::new_signing_with_rpc_client(
|
||||
config, rpc_client, wallet,
|
||||
@@ -177,12 +176,13 @@ impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Client<ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
#[allow(deprecated)]
|
||||
impl Client<crate::ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
pub fn new_reqwest_signing(
|
||||
config: Config,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> DirectSigningReqwestRpcValidatorClient {
|
||||
let rpc_client = ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
|
||||
@@ -203,9 +203,10 @@ impl Client<HttpRpcClient> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Client<ReqwestRpcClient> {
|
||||
#[allow(deprecated)]
|
||||
impl Client<crate::ReqwestRpcClient> {
|
||||
pub fn new_reqwest_query(config: Config) -> QueryReqwestRpcValidatorClient {
|
||||
let rpc_client = ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
Self::new_with_rpc_client(config, rpc_client)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nym_api;
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWalletError;
|
||||
pub use tendermint_rpc::error::Error as TendermintRpcError;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -26,6 +27,12 @@ pub enum ValidatorClientError {
|
||||
|
||||
#[error("No validator API url has been provided")]
|
||||
NoAPIUrlAvailable,
|
||||
|
||||
#[error("failed to derive signing accounts: {source}")]
|
||||
AccountDerivationFailure {
|
||||
#[from]
|
||||
source: DirectSecp256k1HdWalletError,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<nym_api::error::NymAPIError> for ValidatorClientError {
|
||||
|
||||
@@ -12,6 +12,7 @@ pub mod rpc;
|
||||
pub mod signing;
|
||||
|
||||
pub use crate::error::ValidatorClientError;
|
||||
#[allow(deprecated)]
|
||||
pub use crate::rpc::reqwest::ReqwestRpcClient;
|
||||
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
pub use client::{Client, Config, EcashApiClient};
|
||||
@@ -38,9 +39,13 @@ pub type DirectSigningHttpRpcValidatorClient = Client<HttpRpcClient, DirectSecp2
|
||||
#[cfg(feature = "http-client")]
|
||||
pub type DirectSigningHttpRpcNyxdClient = nyxd::NyxdClient<HttpRpcClient, DirectSecp256k1HdWallet>;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub type QueryReqwestRpcValidatorClient = Client<ReqwestRpcClient>;
|
||||
#[allow(deprecated)]
|
||||
pub type QueryReqwestRpcNyxdClient = nyxd::NyxdClient<ReqwestRpcClient>;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub type DirectSigningReqwestRpcValidatorClient = Client<ReqwestRpcClient, DirectSecp256k1HdWallet>;
|
||||
#[allow(deprecated)]
|
||||
pub type DirectSigningReqwestRpcNyxdClient =
|
||||
nyxd::NyxdClient<ReqwestRpcClient, DirectSecp256k1HdWallet>;
|
||||
|
||||
@@ -178,7 +178,7 @@ where
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract"))?;
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
|
||||
self.execute(signer_address, dkg_contract_address, &msg, fee, memo, funds)
|
||||
.await
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ where
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("coconut bandwidth contract"))?;
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
|
||||
self.execute(
|
||||
signer_address,
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ where
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
group_contract_address,
|
||||
|
||||
+1
-1
@@ -667,7 +667,7 @@ where
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let memo = msg.default_memo();
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
mixnet_contract_address,
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ where
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
multisig_contract_address,
|
||||
|
||||
+1
-1
@@ -165,7 +165,7 @@ where
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
performance_contract_address,
|
||||
|
||||
+1
-1
@@ -375,7 +375,7 @@ where
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let memo = msg.name().to_string();
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
vesting_contract_address,
|
||||
|
||||
@@ -324,7 +324,7 @@ where
|
||||
{
|
||||
type Error = S::Error;
|
||||
|
||||
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
|
||||
fn get_accounts(&self) -> &[AccountData] {
|
||||
self.signer.get_accounts()
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::signing::signer::NoSigner;
|
||||
use crate::signing::signer::OfflineSigner;
|
||||
use crate::signing::tx_signer::TxSigner;
|
||||
use crate::signing::AccountData;
|
||||
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient};
|
||||
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient};
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
|
||||
use cosmrs::tx::{Raw, SignDoc};
|
||||
@@ -158,12 +158,13 @@ impl NyxdClient<HttpClient> {
|
||||
}
|
||||
}
|
||||
|
||||
impl NyxdClient<ReqwestRpcClient> {
|
||||
#[allow(deprecated)]
|
||||
impl NyxdClient<crate::ReqwestRpcClient> {
|
||||
pub fn connect_reqwest(
|
||||
config: Config,
|
||||
endpoint: Url,
|
||||
) -> Result<QueryReqwestRpcNyxdClient, NyxdError> {
|
||||
let client = ReqwestRpcClient::new(endpoint);
|
||||
let client = crate::ReqwestRpcClient::new(endpoint);
|
||||
|
||||
Ok(NyxdClient {
|
||||
client: MaybeSigningClient::new(client, (&config).into()),
|
||||
@@ -195,18 +196,19 @@ impl NyxdClient<HttpClient, DirectSecp256k1HdWallet> {
|
||||
let client = http_client(endpoint)?;
|
||||
|
||||
let prefix = &config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
|
||||
Ok(Self::connect_with_signer(config, client, wallet))
|
||||
}
|
||||
}
|
||||
|
||||
impl NyxdClient<ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
#[allow(deprecated)]
|
||||
impl NyxdClient<crate::ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
pub fn connect_reqwest_with_mnemonic(
|
||||
config: Config,
|
||||
endpoint: Url,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> DirectSigningReqwestRpcNyxdClient {
|
||||
let client = ReqwestRpcClient::new(endpoint);
|
||||
let client = crate::ReqwestRpcClient::new(endpoint);
|
||||
|
||||
let prefix = &config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
@@ -391,17 +393,12 @@ where
|
||||
S: OfflineSigner + Send + Sync,
|
||||
NyxdError: From<<S as OfflineSigner>::Error>,
|
||||
{
|
||||
pub fn signing_account(&self) -> Result<AccountData, NyxdError> {
|
||||
pub fn signing_account(&self) -> Result<&AccountData, NyxdError> {
|
||||
Ok(self.find_account(&self.address())?)
|
||||
}
|
||||
|
||||
pub fn address(&self) -> AccountId {
|
||||
match self.client.signer_addresses() {
|
||||
Ok(addresses) => addresses[0].clone(),
|
||||
Err(_) => {
|
||||
panic!("key derivation failure")
|
||||
}
|
||||
}
|
||||
self.client.signer_addresses()[0].clone()
|
||||
}
|
||||
|
||||
pub fn mix_coin(&self, amount: u128) -> Coin {
|
||||
@@ -867,7 +864,7 @@ where
|
||||
{
|
||||
type Error = S::Error;
|
||||
|
||||
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
|
||||
fn get_accounts(&self) -> &[AccountData] {
|
||||
self.client.get_accounts()
|
||||
}
|
||||
|
||||
|
||||
@@ -42,12 +42,15 @@ macro_rules! perform_with_compat {
|
||||
}};
|
||||
}
|
||||
|
||||
// the separate implementation is now completely redundant
|
||||
#[deprecated(note = "use HttpClient directly instead")]
|
||||
pub struct ReqwestRpcClient {
|
||||
compat: CompatMode,
|
||||
inner: reqwest::Client,
|
||||
url: Url,
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl ReqwestRpcClient {
|
||||
pub fn new(url: Url) -> Self {
|
||||
ReqwestRpcClient {
|
||||
@@ -131,6 +134,7 @@ impl TendermintRpcErrorMap for reqwest::Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl TendermintRpcClient for ReqwestRpcClient {
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::signing::signer::{OfflineSigner, SigningError};
|
||||
use crate::signing::{AccountData, Secp256k1Derivation};
|
||||
use cosmrs::bip32::{DerivationPath, XPrv};
|
||||
use cosmrs::crypto::secp256k1::SigningKey;
|
||||
use cosmrs::crypto::PublicKey;
|
||||
use crate::signing::{
|
||||
derive_extended_private_key, derive_keypair, AccountData, Secp256k1Derivation, Secp256k1Keypair,
|
||||
};
|
||||
use bip32::XPrv;
|
||||
use cosmrs::bip32::DerivationPath;
|
||||
use cosmrs::tx;
|
||||
use cosmrs::tx::SignDoc;
|
||||
use nym_config::defaults;
|
||||
use std::borrow::Cow;
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
type Secp256k1Keypair = (SigningKey, PublicKey);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DirectSecp256k1HdWalletError {
|
||||
#[error(transparent)]
|
||||
@@ -36,28 +36,22 @@ pub enum DirectSecp256k1HdWalletError {
|
||||
}
|
||||
|
||||
// TODO: maybe lock this one behind feature flag?
|
||||
#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct DirectSecp256k1HdWallet {
|
||||
/// Base secret
|
||||
secret: bip39::Mnemonic,
|
||||
|
||||
/// BIP39 seed
|
||||
seed: [u8; 64],
|
||||
|
||||
// An unfortunate result of immature rust async story is that async traits (only available in the separate package)
|
||||
// can't yet figure out everything and if we stored our derived account data on the struct,
|
||||
// that would include the secret key which is a dyn EcdsaSigner and hence not Sync making the wallet
|
||||
// not Sync and if used on the signing client in an async trait, it wouldn't be Send
|
||||
/// Derivation instructions
|
||||
/// Derived accounts
|
||||
#[zeroize(skip)]
|
||||
accounts: Vec<Secp256k1Derivation>,
|
||||
// unfortunately `dyn EcdsaSigner` does not guarantee Zeroize
|
||||
accounts: Vec<AccountData>,
|
||||
}
|
||||
|
||||
impl OfflineSigner for DirectSecp256k1HdWallet {
|
||||
type Error = DirectSecp256k1HdWalletError;
|
||||
|
||||
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
|
||||
self.try_derive_accounts()
|
||||
fn get_accounts(&self) -> &[AccountData] {
|
||||
&self.accounts
|
||||
}
|
||||
|
||||
fn sign_direct_with_account(
|
||||
@@ -77,55 +71,27 @@ impl DirectSecp256k1HdWallet {
|
||||
}
|
||||
|
||||
/// Restores a wallet from the given BIP39 mnemonic using default options.
|
||||
#[deprecated(
|
||||
note = "this function can potentially panic if accounts can't be derived correctly. please use .checked_from_mnemonic() instead"
|
||||
)]
|
||||
pub fn from_mnemonic(prefix: &str, mnemonic: bip39::Mnemonic) -> Self {
|
||||
// unfortunately due to backwards compatibility requirements,
|
||||
// we can't change signature of this method
|
||||
#[allow(deprecated)]
|
||||
DirectSecp256k1HdWalletBuilder::new(prefix).build(mnemonic)
|
||||
}
|
||||
|
||||
/// Restores a wallet from the given BIP39 mnemonic using default options.
|
||||
pub fn checked_from_mnemonic(
|
||||
prefix: &str,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<Self, DirectSecp256k1HdWalletError> {
|
||||
DirectSecp256k1HdWalletBuilder::new(prefix).try_build(mnemonic)
|
||||
}
|
||||
|
||||
pub fn generate(prefix: &str, word_count: usize) -> Result<Self, DirectSecp256k1HdWalletError> {
|
||||
let mneomonic = bip39::Mnemonic::generate(word_count)?;
|
||||
Ok(Self::from_mnemonic(prefix, mneomonic))
|
||||
}
|
||||
|
||||
fn derive_keypair(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<Secp256k1Keypair, DirectSecp256k1HdWalletError> {
|
||||
let extended_private_key = XPrv::derive_from_path(self.seed, hd_path)?;
|
||||
|
||||
let private_key: SigningKey = extended_private_key.into();
|
||||
let public_key = private_key.public_key();
|
||||
|
||||
Ok((private_key, public_key))
|
||||
}
|
||||
|
||||
pub fn derive_extended_private_key(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<XPrv, DirectSecp256k1HdWalletError> {
|
||||
Ok(XPrv::derive_from_path(self.seed, hd_path)?)
|
||||
}
|
||||
|
||||
pub fn try_derive_accounts(&self) -> Result<Vec<AccountData>, DirectSecp256k1HdWalletError> {
|
||||
let mut accounts = Vec::with_capacity(self.accounts.len());
|
||||
for derivation_info in &self.accounts {
|
||||
let keypair = self.derive_keypair(&derivation_info.hd_path)?;
|
||||
|
||||
// it seems this can only fail if the provided account prefix is invalid
|
||||
let address = keypair
|
||||
.1
|
||||
.account_id(&derivation_info.prefix)
|
||||
.map_err(
|
||||
|source| DirectSecp256k1HdWalletError::AccountDerivationError { source },
|
||||
)?;
|
||||
|
||||
accounts.push(AccountData {
|
||||
address,
|
||||
public_key: keypair.1,
|
||||
private_key: keypair.0,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(accounts)
|
||||
Self::checked_from_mnemonic(prefix, mneomonic)
|
||||
}
|
||||
|
||||
pub fn secret(&self) -> &bip39::Mnemonic {
|
||||
@@ -142,6 +108,43 @@ impl DirectSecp256k1HdWallet {
|
||||
pub fn mnemonic_string(&self) -> Zeroizing<String> {
|
||||
Zeroizing::new(self.secret.to_string())
|
||||
}
|
||||
|
||||
pub fn account_seed<'a, P: Into<Cow<'a, str>>>(
|
||||
&self,
|
||||
bip39_password: P,
|
||||
) -> Zeroizing<[u8; 64]> {
|
||||
Zeroizing::new(self.secret.to_seed(bip39_password))
|
||||
}
|
||||
|
||||
/// Derive an extended private key from the stored account secret assuming no bip39 password
|
||||
#[deprecated(
|
||||
note = "use derive_extended_private_key_with_password to ensure correct derivation if used bip39 password"
|
||||
)]
|
||||
pub fn derive_extended_private_key(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<XPrv, DirectSecp256k1HdWalletError> {
|
||||
let seed = self.account_seed("");
|
||||
derive_extended_private_key(seed, hd_path)
|
||||
}
|
||||
|
||||
pub fn derive_keypair<'a, P: Into<Cow<'a, str>>>(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
bip39_password: P,
|
||||
) -> Result<Secp256k1Keypair, DirectSecp256k1HdWalletError> {
|
||||
let seed = self.account_seed(bip39_password);
|
||||
derive_keypair(seed, hd_path)
|
||||
}
|
||||
|
||||
pub fn derive_extended_private_key_with_password<'a, P: Into<Cow<'a, str>>>(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
bip39_password: P,
|
||||
) -> Result<XPrv, DirectSecp256k1HdWalletError> {
|
||||
let seed = self.account_seed(bip39_password);
|
||||
derive_extended_private_key(seed, hd_path)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -188,23 +191,39 @@ impl DirectSecp256k1HdWalletBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
note = "this function can potentially panic if accounts can't be derived correctly. please use .try_build() instead"
|
||||
)]
|
||||
pub fn build(self, mnemonic: bip39::Mnemonic) -> DirectSecp256k1HdWallet {
|
||||
let seed = mnemonic.to_seed(&self.bip39_password);
|
||||
// unfortunately due to backwards compatibility requirements,
|
||||
// we can't change signature of this method
|
||||
#[allow(clippy::expect_used)]
|
||||
self.try_build(mnemonic)
|
||||
.expect("account derivation failure")
|
||||
}
|
||||
|
||||
pub fn try_build(
|
||||
self,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<DirectSecp256k1HdWallet, DirectSecp256k1HdWalletError> {
|
||||
let seed = Zeroizing::new(mnemonic.to_seed(&self.bip39_password));
|
||||
let prefix = self.prefix.clone();
|
||||
let accounts = self
|
||||
.hd_paths
|
||||
.iter()
|
||||
.map(|hd_path| Secp256k1Derivation {
|
||||
hd_path: hd_path.clone(),
|
||||
prefix: prefix.clone(),
|
||||
.map(|hd_path| {
|
||||
Secp256k1Derivation {
|
||||
hd_path: hd_path.clone(),
|
||||
prefix: prefix.clone(),
|
||||
}
|
||||
.try_derive_account(&seed)
|
||||
})
|
||||
.collect();
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
DirectSecp256k1HdWallet {
|
||||
Ok(DirectSecp256k1HdWallet {
|
||||
accounts,
|
||||
seed,
|
||||
secret: mnemonic,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +234,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn generating_account_addresses() {
|
||||
fn generating_account_addresses() -> anyhow::Result<()> {
|
||||
// test vectors produced from our js wallet
|
||||
let mnemonics = ["crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove",
|
||||
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
|
||||
@@ -230,11 +249,10 @@ mod tests {
|
||||
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
|
||||
];
|
||||
for (idx, mnemonic) in mnemonics.iter().enumerate() {
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap());
|
||||
assert_eq!(
|
||||
wallet.try_derive_accounts().unwrap()[0].address,
|
||||
addrs[idx].parse().unwrap()
|
||||
)
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::checked_from_mnemonic(&prefix, mnemonic.parse()?)?;
|
||||
assert_eq!(wallet.signer_addresses()[0], addrs[idx].parse().unwrap());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWalletError;
|
||||
use bip32::XPrv;
|
||||
use cosmrs::bip32::DerivationPath;
|
||||
use cosmrs::crypto::secp256k1::SigningKey;
|
||||
use cosmrs::crypto::PublicKey;
|
||||
@@ -12,14 +14,64 @@ pub mod direct_wallet;
|
||||
pub mod signer;
|
||||
pub mod tx_signer;
|
||||
|
||||
pub(crate) type Secp256k1Keypair = (SigningKey, PublicKey);
|
||||
|
||||
/// Derivation information required to derive a keypair and an address from a mnemonic.
|
||||
#[derive(Debug, Clone)]
|
||||
struct Secp256k1Derivation {
|
||||
pub(crate) struct Secp256k1Derivation {
|
||||
hd_path: DerivationPath,
|
||||
prefix: String,
|
||||
}
|
||||
|
||||
// TODO: is this struct going to be derivable with other signer types?
|
||||
impl Secp256k1Derivation {
|
||||
pub(crate) fn try_derive_account<S>(
|
||||
&self,
|
||||
seed: S,
|
||||
) -> Result<AccountData, DirectSecp256k1HdWalletError>
|
||||
where
|
||||
S: AsRef<[u8]>,
|
||||
{
|
||||
let keypair = derive_keypair(seed, &self.hd_path)?;
|
||||
|
||||
// it seems this can only fail if the provided account prefix is invalid
|
||||
let address = keypair
|
||||
.1
|
||||
.account_id(&self.prefix)
|
||||
.map_err(|source| DirectSecp256k1HdWalletError::AccountDerivationError { source })?;
|
||||
|
||||
Ok(AccountData {
|
||||
address,
|
||||
public_key: keypair.1,
|
||||
private_key: keypair.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive_keypair<S>(
|
||||
seed: S,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<Secp256k1Keypair, DirectSecp256k1HdWalletError>
|
||||
where
|
||||
S: AsRef<[u8]>,
|
||||
{
|
||||
let extended_private_key = derive_extended_private_key(seed, hd_path)?;
|
||||
|
||||
let private_key: SigningKey = extended_private_key.into();
|
||||
let public_key = private_key.public_key();
|
||||
|
||||
Ok((private_key, public_key))
|
||||
}
|
||||
|
||||
pub fn derive_extended_private_key<S>(
|
||||
seed: S,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<XPrv, DirectSecp256k1HdWalletError>
|
||||
where
|
||||
S: AsRef<[u8]>,
|
||||
{
|
||||
Ok(XPrv::derive_from_path(seed, hd_path)?)
|
||||
}
|
||||
|
||||
pub struct AccountData {
|
||||
pub address: AccountId,
|
||||
|
||||
|
||||
@@ -33,23 +33,18 @@ pub enum SignerType {
|
||||
pub trait OfflineSigner {
|
||||
type Error: From<SigningError>;
|
||||
|
||||
// I really dislike existence of this function because it makes you re-derive your key **twice** for each contract transaction
|
||||
fn signer_addresses(&self) -> Result<Vec<AccountId>, Self::Error> {
|
||||
let derived_addresses = self
|
||||
.get_accounts()?
|
||||
.into_iter()
|
||||
.map(|account| account.address)
|
||||
.collect();
|
||||
Ok(derived_addresses)
|
||||
fn signer_addresses(&self) -> Vec<AccountId> {
|
||||
self.get_accounts()
|
||||
.iter()
|
||||
.map(|account| account.address.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error>;
|
||||
fn get_accounts(&self) -> &[AccountData];
|
||||
|
||||
fn find_account(&self, signer_address: &AccountId) -> Result<AccountData, Self::Error> {
|
||||
// TODO: we could really use some zeroize action here
|
||||
let accounts = self.get_accounts()?;
|
||||
accounts
|
||||
.into_iter()
|
||||
fn find_account(&self, signer_address: &AccountId) -> Result<&AccountData, Self::Error> {
|
||||
self.get_accounts()
|
||||
.iter()
|
||||
.find(|account| &account.address == signer_address)
|
||||
.ok_or_else(|| {
|
||||
SigningError::AccountNotFound {
|
||||
@@ -76,7 +71,7 @@ pub trait OfflineSigner {
|
||||
message: M,
|
||||
) -> Result<Signature, Self::Error> {
|
||||
let signer = self.find_account(signer_address)?;
|
||||
self.sign_raw_with_account(&signer, message)
|
||||
self.sign_raw_with_account(signer, message)
|
||||
}
|
||||
|
||||
fn sign_direct(
|
||||
@@ -85,7 +80,7 @@ pub trait OfflineSigner {
|
||||
sign_doc: SignDoc,
|
||||
) -> Result<tx::Raw, Self::Error> {
|
||||
let signer = self.find_account(signer_address)?;
|
||||
self.sign_direct_with_account(&signer, sign_doc)
|
||||
self.sign_direct_with_account(signer, sign_doc)
|
||||
}
|
||||
|
||||
// unless explicitly defined, each signing method is unsupported
|
||||
@@ -122,7 +117,7 @@ pub struct NoSigner;
|
||||
// impl OfflineSigner for NoSigner {
|
||||
// type Error = SignerUnavailable;
|
||||
//
|
||||
// fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
|
||||
// fn get_accounts(&self) -> &[AccountData] {
|
||||
// return Err(SignerUnavailable);
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -50,7 +50,7 @@ pub trait TxSigner: OfflineSigner {
|
||||
)
|
||||
.map_err(|source| SigningError::SignDocFailure { source })?;
|
||||
|
||||
self.sign_direct_with_account(&account_from_signer, sign_doc)
|
||||
self.sign_direct_with_account(account_from_signer, sign_doc)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use clap::Parser;
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use nym_validator_client::signing::signer::OfflineSigner;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -15,9 +16,10 @@ pub fn create_account(args: Args, prefix: &str) {
|
||||
let word_count = args.word_count.unwrap_or(24);
|
||||
let mnemonic = bip39::Mnemonic::generate(word_count).expect("failed to generate mnemonic!");
|
||||
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)
|
||||
.expect("failed to derive accounts!");
|
||||
|
||||
// Output address and mnemonics into separate lines for easier parsing
|
||||
println!("{}", wallet.mnemonic_string().as_str());
|
||||
println!("{}", wallet.try_derive_accounts().unwrap()[0].address());
|
||||
println!("{}", wallet.signer_addresses()[0]);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::show_error;
|
||||
use clap::Parser;
|
||||
use log::{error, info};
|
||||
use nym_validator_client::nyxd::{AccountId, CosmWasmClient};
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::show_error;
|
||||
use nym_validator_client::signing::signer::OfflineSigner;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -50,20 +50,19 @@ pub async fn get_pubkey(
|
||||
}
|
||||
|
||||
pub fn get_pubkey_from_mnemonic(address: AccountId, prefix: &str, mnemonic: bip39::Mnemonic) {
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
match wallet.try_derive_accounts() {
|
||||
Ok(accounts) => match accounts.iter().find(|a| *a.address() == address) {
|
||||
Some(account) => {
|
||||
println!("{}", account.public_key().to_string());
|
||||
}
|
||||
None => {
|
||||
error!("Could not derive key that matches {address}")
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to derive accounts. {e}");
|
||||
let wallet = match DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic) {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
error!("Failed to derive accounts. {err}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(account) = wallet.find_account(&address) else {
|
||||
error!("Could not derive key that matches {address}");
|
||||
return;
|
||||
};
|
||||
println!("{}", account.public_key().to_string());
|
||||
}
|
||||
|
||||
pub async fn get_pubkey_from_chain(address: AccountId, client: &QueryClient) {
|
||||
|
||||
@@ -36,32 +36,35 @@ pub fn sign(args: Args, prefix: &str, mnemonic: Option<bip39::Mnemonic>) {
|
||||
return;
|
||||
}
|
||||
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.expect("mnemonic not set"));
|
||||
match wallet.try_derive_accounts() {
|
||||
Ok(accounts) => match accounts.first() {
|
||||
Some(account) => {
|
||||
let msg = args.message.into_bytes();
|
||||
match wallet.sign_raw_with_account(account, msg) {
|
||||
Ok(signature) => {
|
||||
let output = SignatureOutputJson {
|
||||
account_id: account.address().to_string(),
|
||||
public_key: account.public_key(),
|
||||
signature_as_hex: signature.to_string(),
|
||||
};
|
||||
println!("{}", json!(output));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to sign message. {e}");
|
||||
}
|
||||
let wallet = match DirectSecp256k1HdWallet::checked_from_mnemonic(
|
||||
prefix,
|
||||
mnemonic.expect("mnemonic not set"),
|
||||
) {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
error!("Could not derive an account key from the mnemonic: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match wallet.get_accounts().first() {
|
||||
Some(account) => {
|
||||
let msg = args.message.into_bytes();
|
||||
match wallet.sign_raw_with_account(account, msg) {
|
||||
Ok(signature) => {
|
||||
let output = SignatureOutputJson {
|
||||
account_id: account.address().to_string(),
|
||||
public_key: account.public_key(),
|
||||
signature_as_hex: signature.to_string(),
|
||||
};
|
||||
println!("{}", json!(output));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to sign message. {e}");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("Could not derive an account key from the mnemonic",)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to derive accounts. {e}");
|
||||
}
|
||||
None => {
|
||||
error!("Could not derive an account key from the mnemonic",)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,23 +241,28 @@ impl Epoch {
|
||||
//
|
||||
// Note: It's important that the variant ordering is not changed otherwise it would mess up the derived `PartialOrd`
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
#[derive(Copy, Default)]
|
||||
pub enum EpochState {
|
||||
#[default]
|
||||
WaitingInitialisation,
|
||||
PublicKeySubmission { resharing: bool },
|
||||
DealingExchange { resharing: bool },
|
||||
VerificationKeySubmission { resharing: bool },
|
||||
VerificationKeyValidation { resharing: bool },
|
||||
VerificationKeyFinalization { resharing: bool },
|
||||
PublicKeySubmission {
|
||||
resharing: bool,
|
||||
},
|
||||
DealingExchange {
|
||||
resharing: bool,
|
||||
},
|
||||
VerificationKeySubmission {
|
||||
resharing: bool,
|
||||
},
|
||||
VerificationKeyValidation {
|
||||
resharing: bool,
|
||||
},
|
||||
VerificationKeyFinalization {
|
||||
resharing: bool,
|
||||
},
|
||||
InProgress,
|
||||
}
|
||||
|
||||
impl Default for EpochState {
|
||||
fn default() -> Self {
|
||||
Self::WaitingInitialisation
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EpochState {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
||||
@@ -8,7 +8,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let out_dir = env::var("OUT_DIR").context("missing OUT_DIR env variable")?;
|
||||
let database_path = format!("{out_dir}/nym-credential-proxy-example.sqlite");
|
||||
|
||||
// remove the db file if it already existed from previous build
|
||||
|
||||
@@ -127,6 +127,13 @@ pub enum CredentialProxyError {
|
||||
#[error("failed to create deposit")]
|
||||
DepositFailure,
|
||||
|
||||
#[error("failed to load jwt signing key from {path}: {err}")]
|
||||
JWTSigningKeyLoadFailure {
|
||||
path: String,
|
||||
#[source]
|
||||
err: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("can't obtain sufficient number of credential shares due to unavailable quorum")]
|
||||
UnavailableSigningQuorum,
|
||||
|
||||
@@ -161,6 +168,14 @@ pub enum CredentialProxyError {
|
||||
device_id: String,
|
||||
credential_id: String,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"the attestation check url has not been provided through either the CLI nor the default .env config"
|
||||
)]
|
||||
AttestationCheckUrlNotSet,
|
||||
|
||||
#[error("the provided attestation check url is malformed: {source}")]
|
||||
MalformedAttestationCheckUrl { source: url::ParseError },
|
||||
}
|
||||
|
||||
impl From<NymAPIError> for CredentialProxyError {
|
||||
|
||||
@@ -7,9 +7,9 @@ use crate::ticketbook_manager::TicketbookManager;
|
||||
use nym_compact_ecash::Base58;
|
||||
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
CurrentEpochResponse, DepositResponse, GlobalDataParams, MasterVerificationKeyResponse,
|
||||
PartialVerificationKey, PartialVerificationKeysResponse, TicketbookAsyncRequest,
|
||||
TicketbookObtainParams, TicketbookRequest, TicketbookWalletSharesAsyncResponse,
|
||||
TicketbookWalletSharesResponse,
|
||||
ObtainTicketBookSharesAsyncResponse, PartialVerificationKey, PartialVerificationKeysResponse,
|
||||
TicketbookAsyncRequest, TicketbookObtainParams, TicketbookRequest,
|
||||
TicketbookWalletSharesAsyncResponse, TicketbookWalletSharesResponse,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{Instrument, Level, error, info, span, warn};
|
||||
@@ -65,7 +65,7 @@ impl TicketbookManager {
|
||||
uuid: Uuid,
|
||||
request: TicketbookAsyncRequest,
|
||||
params: TicketbookObtainParams,
|
||||
) -> Result<TicketbookWalletSharesAsyncResponse, CredentialProxyError> {
|
||||
) -> Result<ObtainTicketBookSharesAsyncResponse, CredentialProxyError> {
|
||||
let requested_on = OffsetDateTime::now_utc();
|
||||
let span = span!(Level::INFO, "[async] obtain ticketboook", uuid = %uuid);
|
||||
async move {
|
||||
@@ -110,7 +110,7 @@ impl TicketbookManager {
|
||||
}
|
||||
|
||||
// 4. in the meantime, return the id to the user
|
||||
Ok(TicketbookWalletSharesAsyncResponse { id, uuid })
|
||||
Ok(TicketbookWalletSharesAsyncResponse { id, uuid }.into())
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
|
||||
@@ -39,8 +39,11 @@ impl SqliteEcashTicketbookManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn begin_storage_tx(&self) -> Result<Transaction<'_, Sqlite>, sqlx::Error> {
|
||||
self.connection_pool.begin().await
|
||||
/// Starts a write (IMMEDIATE) transaction, to prevent issue when upgrading from a read one to a write one
|
||||
pub(crate) async fn begin_storage_write_tx(
|
||||
&self,
|
||||
) -> Result<Transaction<'_, Sqlite>, sqlx::Error> {
|
||||
self.connection_pool.begin_with("BEGIN IMMEDIATE").await
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_pending_ticketbook(
|
||||
|
||||
@@ -246,7 +246,7 @@ mod tests {
|
||||
let _exp_date_sigs = generate_expiration_date_signatures(
|
||||
sig_req.expiration_date.ecash_unix_timestamp(),
|
||||
&[signing_keys.secret_key()],
|
||||
&vec![signing_keys.verification_key()],
|
||||
&[signing_keys.verification_key()],
|
||||
&signing_keys.verification_key(),
|
||||
&[1],
|
||||
)?;
|
||||
@@ -263,7 +263,7 @@ mod tests {
|
||||
|
||||
let wallet = issuance.aggregate_signature_shares(
|
||||
&signing_keys.verification_key(),
|
||||
&vec![partial_wallet],
|
||||
&[partial_wallet],
|
||||
sig_req,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@ impl Storage for PersistentStorage {
|
||||
tickets: u32,
|
||||
) -> Result<Option<RetrievedTicketbook>, Self::StorageError> {
|
||||
let deadline = ecash_today().ecash_date();
|
||||
let mut tx = self.storage_manager.begin_storage_tx().await?;
|
||||
let mut tx = self.storage_manager.begin_storage_write_tx().await?;
|
||||
|
||||
// we don't want ticketbooks with expiration in the past
|
||||
let Some(raw) =
|
||||
|
||||
@@ -36,7 +36,11 @@ nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0", default-fea
|
||||
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -17,6 +17,51 @@ pub mod bs58_ed25519_pubkey {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod vec_bs58_ed25519_pubkey {
|
||||
use super::*;
|
||||
use serde::{Deserialize, Deserializer, Serializer, ser::SerializeSeq};
|
||||
|
||||
pub fn serialize<S: Serializer>(
|
||||
keys: &Vec<PublicKey>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let mut seq = serializer.serialize_seq(Some(keys.len()))?;
|
||||
for key in keys {
|
||||
seq.serialize_element(&Bs58KeyWrapper(*key))?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<PublicKey>, D::Error> {
|
||||
let wrapped = Vec::<Bs58KeyWrapper>::deserialize(deserializer)?;
|
||||
Ok(wrapped.into_iter().map(|k| k.0).collect())
|
||||
}
|
||||
|
||||
struct Bs58KeyWrapper(PublicKey);
|
||||
|
||||
impl serde::Serialize for Bs58KeyWrapper {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
bs58_ed25519_pubkey::serialize(&self.0, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Bs58KeyWrapper {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(Bs58KeyWrapper(bs58_ed25519_pubkey::deserialize(
|
||||
deserializer,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod bs58_ed25519_signature {
|
||||
use crate::asymmetric::ed25519::Signature;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
@@ -33,3 +78,53 @@ pub mod bs58_ed25519_signature {
|
||||
Signature::from_base58_string(s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use jwt_simple::reexports::{anyhow, serde_json};
|
||||
use nym_test_utils::helpers::deterministic_rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[test]
|
||||
fn vec_bs58_ed25519_pubkey_json() -> anyhow::Result<()> {
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct KeysWrapper(#[serde(with = "vec_bs58_ed25519_pubkey")] Vec<PublicKey>);
|
||||
|
||||
use crate::asymmetric::ed25519;
|
||||
let mut rng = deterministic_rng();
|
||||
let empty = KeysWrapper(vec![]);
|
||||
let single_key = KeysWrapper(vec![PublicKey::from_base58_string(
|
||||
"Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt",
|
||||
)?]);
|
||||
let three_keys = KeysWrapper(vec![
|
||||
ed25519::KeyPair::new(&mut rng).public_key,
|
||||
ed25519::KeyPair::new(&mut rng).public_key,
|
||||
ed25519::KeyPair::new(&mut rng).public_key,
|
||||
]);
|
||||
|
||||
let se_empty = serde_json::to_string(&empty)?;
|
||||
let se_single_key = serde_json::to_string(&single_key)?;
|
||||
let se_three_keys = serde_json::to_string(&three_keys)?;
|
||||
|
||||
assert_eq!(se_empty, r#"[]"#);
|
||||
assert_eq!(
|
||||
se_single_key,
|
||||
r#"["Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt"]"#
|
||||
);
|
||||
assert_eq!(
|
||||
se_three_keys,
|
||||
r#"["HmgHDV79LpnEaSUp8QZQwSroxVvS4RewF7yM9e7qu8y3","311xRh859qCd5MVqoPRCoNx26eYhLknGwtjzkkTJFGhf","A5BMp8WJ6Uk91U4JpWRv2Bc6X35AaRaSEy8QEWeAkaBv"]"#
|
||||
);
|
||||
|
||||
let empty_de = serde_json::from_str::<KeysWrapper>(&se_empty)?;
|
||||
let single_key_de = serde_json::from_str::<KeysWrapper>(&se_single_key)?;
|
||||
let three_keys_de = serde_json::from_str::<KeysWrapper>(&se_three_keys)?;
|
||||
|
||||
assert_eq!(empty, empty_de);
|
||||
assert_eq!(single_key, single_key_de);
|
||||
assert_eq!(three_keys, three_keys_de);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2020-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::shared_key::{SharedGatewayKey, SharedKeyUsageError};
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Replacement for what used to be an `AuthToken`.
|
||||
///
|
||||
/// Replacement for what used to be an `AuthToken`. We used to be generating an `AuthToken` based on
|
||||
/// local secret and remote address in order to allow for authentication. Due to changes in registration
|
||||
/// and the fact we are deriving a shared key, we are encrypting remote's address with the previously
|
||||
/// derived shared key. If the value is as expected, then authentication is successful.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
// this is no longer constant size due to the differences in ciphertext between aes128ctr and aes256gcm-siv (inclusion of tag)
|
||||
pub struct EncryptedAddressBytes(Vec<u8>);
|
||||
|
||||
impl From<Vec<u8>> for EncryptedAddressBytes {
|
||||
fn from(encrypted_address: Vec<u8>) -> Self {
|
||||
EncryptedAddressBytes(encrypted_address)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EncryptedAddressConversionError {
|
||||
#[error("Failed to decode the encrypted address - {0}")]
|
||||
DecodeError(#[from] bs58::decode::Error),
|
||||
}
|
||||
|
||||
impl EncryptedAddressBytes {
|
||||
pub fn new(
|
||||
address: &DestinationAddressBytes,
|
||||
key: &SharedGatewayKey,
|
||||
nonce: &[u8],
|
||||
) -> Result<Self, SharedKeyUsageError> {
|
||||
let ciphertext = key.encrypt_naive(address.as_bytes_ref(), Some(nonce))?;
|
||||
|
||||
Ok(EncryptedAddressBytes(ciphertext))
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
address: &DestinationAddressBytes,
|
||||
key: &SharedGatewayKey,
|
||||
nonce: &[u8],
|
||||
) -> bool {
|
||||
let Ok(reconstructed) = Self::new(address, key, nonce) else {
|
||||
return false;
|
||||
};
|
||||
self == &reconstructed
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn try_from_base58_string<S: Into<String>>(
|
||||
val: S,
|
||||
) -> Result<Self, EncryptedAddressConversionError> {
|
||||
let decoded = bs58::decode(val.into()).into_vec()?;
|
||||
Ok(EncryptedAddressBytes(decoded))
|
||||
}
|
||||
|
||||
pub fn to_base58_string(self) -> String {
|
||||
bs58::encode(self.0).into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncryptedAddressBytes> for String {
|
||||
fn from(val: EncryptedAddressBytes) -> Self {
|
||||
val.to_base58_string()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user