Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f8a8ddf7e | |||
| 3c92ce60ca | |||
| 846dbba363 | |||
| 94ab9d5466 | |||
| c78d942383 | |||
| 0b6166d20e | |||
| 6384467526 | |||
| fdd3823585 | |||
| 892a3bd826 | |||
| 59ff7d6588 | |||
| 20c4553bca | |||
| 4c38481c36 | |||
| 07680db2c7 | |||
| 59cbce50f7 | |||
| ac13ddbda8 | |||
| 67803930b6 | |||
| 7052e2e902 | |||
| cccfa76336 | |||
| a946336e67 | |||
| e5836bc1cb | |||
| f12108a7db | |||
| 70bdbce23f | |||
| e6f9b551ed | |||
| fcfa0b604e | |||
| 8b086e0239 | |||
| 6c76834b6c | |||
| 071589237b | |||
| 771ee10ba2 | |||
| 33ce05a3df | |||
| 73016ed687 | |||
| 8a5205ac4c | |||
| aaa7e317bf | |||
| f28c49e9d6 | |||
| e2ceaf48ed | |||
| 3e2137a33e | |||
| 984fa065e3 | |||
| da46ea7485 | |||
| b1bc359806 | |||
| b338644620 | |||
| 1ec0bf868b | |||
| 07842661b9 | |||
| 0cd4dd5747 | |||
| abdd960b20 | |||
| db2f3bff05 | |||
| be56c79106 | |||
| 3ccfbee834 | |||
| 942ab3c8e8 | |||
| 9ec937dd30 | |||
| 6ccc4a988a | |||
| 27890eb1a3 | |||
| fa327a1b2a | |||
| cea66c1237 | |||
| 757a89c5d7 | |||
| 1e3f531e15 | |||
| 7cc33d8df7 | |||
| 1bd0bfeee1 | |||
| f297af2a8c | |||
| d9190e5899 | |||
| a562812ad9 | |||
| 7368692629 | |||
| c185f485a7 | |||
| 6930968e88 | |||
| 8294191913 | |||
| 9b2fb45270 | |||
| cb8747abb8 | |||
| 47d37d8aed | |||
| d452932b18 | |||
| 702dfdc927 | |||
| 18e8dfe394 | |||
| 0208a84b77 | |||
| 7105bbf4b4 | |||
| 39692502df | |||
| fcefa079b0 | |||
| 371422f27b | |||
| 5541f242ff | |||
| 348e93dd70 | |||
| 7f8b7eea8c | |||
| 8760c40d46 | |||
| 8ae4b8fee2 | |||
| 4f4885fe50 | |||
| bc52db53b7 | |||
| 08d49a6f2e | |||
| 6f53192dbf | |||
| b5afb77f19 | |||
| 29714dea76 | |||
| 8fd9cee189 | |||
| 2b4a11e273 | |||
| a58b32703c | |||
| de80b4ce48 | |||
| 85a3b25be9 | |||
| 708bd71a56 | |||
| 40b886e0bd | |||
| 23c1c4bdac | |||
| 2dd8707725 | |||
| 0bb3c4b2bf | |||
| 72e8180abe | |||
| 2d5b1d577c | |||
| b5e45040ca | |||
| e420081512 | |||
| 0da4ee985b | |||
| 6d8cacc900 | |||
| 49543fcd98 | |||
| 7b80716c9a | |||
| a4a48c60ae | |||
| e027b5a1fe | |||
| 723df5584e | |||
| 2ca5155748 | |||
| 4f0cc58a11 | |||
| 2ccdfedd65 | |||
| d7ddb7592c | |||
| 7371ce3e36 | |||
| cd7bb9931e | |||
| b77dbdd87e | |||
| 83dcf3fd13 | |||
| a5c6e9d0e2 | |||
| a417411184 | |||
| 24d5e4aba9 | |||
| 6cb2fc8445 | |||
| 4ea2c3beb3 | |||
| be8c1191f3 | |||
| d969979c8c | |||
| c6fd3c8527 | |||
| 6ac4d93909 | |||
| 197a7eaec8 | |||
| f598ee2916 | |||
| b2fa6cdf8f | |||
| 97dbef155d | |||
| 9dbd91d93e | |||
| 7914cbdbb7 | |||
| 99febfb3aa | |||
| 2b00188983 | |||
| 82f270329f | |||
| 3cb17e76bd | |||
| 7b2f8a4ed1 | |||
| 438e745cb3 | |||
| 674fd511f4 | |||
| 66d85a7c0d | |||
| d12a5d754a | |||
| 3a78d62240 | |||
| 5e651b55fc | |||
| 8a6bf4a03d | |||
| 6a2f1a67ed | |||
| d56ab91a2e | |||
| 8f670f467b | |||
| d013168823 | |||
| 8dc3ba4ec3 | |||
| 712e3f5183 | |||
| 5229df47ab | |||
| 32cffed36b | |||
| 49c710e651 | |||
| 0a5227a894 | |||
| b231eb4f04 | |||
| fdd2c8fac2 | |||
| e2dd8ac743 | |||
| 8001fa7f40 | |||
| 80370b98ec | |||
| 3524089ad8 | |||
| ec7ee49282 | |||
| 653d1c2dea | |||
| b579f987b1 | |||
| 59254c92c3 | |||
| 69887921cc | |||
| e075b07632 | |||
| d32b680351 | |||
| fcd59a19be | |||
| 08b20ac2ab | |||
| 4c007669f9 | |||
| c3a8fa8d0d | |||
| d8769157fd | |||
| 7cccf3cfff | |||
| 02eec164f8 | |||
| 4f13ab1e0a | |||
| a34c7ef19f | |||
| f00b18298c | |||
| 0426adc94e | |||
| 4b4a2fe387 | |||
| 1ebb7e06c7 | |||
| 1fd17c5cb3 | |||
| ef65cf4c9e | |||
| 48dad0f16b | |||
| 93ac638765 | |||
| c6589ca92c | |||
| 03d5a87826 | |||
| 512cfd1b74 | |||
| ba0625cd97 | |||
| a2c489dc5b | |||
| 5cee248122 | |||
| 86aec84697 | |||
| 8f376d1b9b | |||
| f0ae4f4090 | |||
| 4e850f6fe0 | |||
| bd3678dd4f | |||
| 28c1637198 | |||
| 8de574ec97 | |||
| 4464d12103 | |||
| 0d9d97e31e | |||
| a7705a5f2c | |||
| 7a300bdd74 | |||
| 6569479083 | |||
| 611844b248 | |||
| 2cc9b05520 | |||
| a450b6f984 |
@@ -7,7 +7,7 @@ jobs:
|
||||
build:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
env:
|
||||
NEXT_PUBLIC_SITE_URL: https://nymtech.net/docs
|
||||
NEXT_PUBLIC_SITE_URL: https://nym.com/docs
|
||||
defaults:
|
||||
run:
|
||||
working-directory: documentation/docs
|
||||
|
||||
@@ -48,6 +48,8 @@ jobs:
|
||||
run: pnpm i
|
||||
- name: Build project
|
||||
run: pnpm run build
|
||||
- name: Generate sitemap
|
||||
run: npx next-sitemap
|
||||
- name: Move files to /dist/
|
||||
run: ../scripts/move-to-dist.sh
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
sonarqube:
|
||||
name: SonarQube
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: arc-linux-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
|
||||
@@ -2,8 +2,6 @@ name: nightly-build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '14 1 * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
jobs:
|
||||
integration-tests:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: arc-linux-latest
|
||||
env:
|
||||
API_BASE_URL: http://localhost:8000
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ env:
|
||||
jobs:
|
||||
check-milestone:
|
||||
name: Check Milestone
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: arc-linux-latest
|
||||
steps:
|
||||
- if: github.event.pull_request.milestone == null && contains( env.LABELS, 'no-milestone' ) == false
|
||||
run: exit 1
|
||||
|
||||
@@ -36,6 +36,9 @@ jobs:
|
||||
with:
|
||||
go-version: "1.24.6"
|
||||
|
||||
- name: Update root CA certificate bundle
|
||||
run: ./wasm/mix-fetch/go-mix-conn/scripts/update-root-certs.sh
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ storybook-static
|
||||
**/.DS_Store
|
||||
cpu-cycles/libcpucycles/build
|
||||
foxyfox.env
|
||||
scratch.txt
|
||||
|
||||
.next
|
||||
ppa-private-key.b64
|
||||
|
||||
@@ -4,6 +4,52 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2026.5-raclette] (2026-03-10)
|
||||
|
||||
- bugfix: correctly populate gateway probe LP data ([#6533])
|
||||
- chore: introduce additional prometheus metrics for registration times ([#6532])
|
||||
- bugfix: lp information to have proper snake_case on API endpoints ([#6531])
|
||||
- removed redundant LP states ([#6509])
|
||||
- chore: removed all matrix notifications from github actions ([#6495])
|
||||
- feat: Lewes Protocol with PSQv2 ([#6491])
|
||||
- build(deps): bump minimatch from 3.1.2 to 3.1.4 in /documentation/docs ([#6486])
|
||||
- build(deps): bump bn.js from 4.12.2 to 4.12.3 in /documentation/docs ([#6484])
|
||||
- build(deps): bump bn.js from 4.12.2 to 4.12.3 ([#6483])
|
||||
- build(deps): bump ajv from 8.17.1 to 8.18.0 in /clients/native/examples/js-examples/websocket ([#6478])
|
||||
- build(deps): bump ajv from 6.12.6 to 6.14.0 in /documentation/docs ([#6477])
|
||||
- build(deps): bump minimatch and glob in /documentation/scripts/post-process ([#6476])
|
||||
- build(deps): bump hono from 4.11.9 to 4.12.0 ([#6475])
|
||||
- build(deps): bump keccak from 0.1.5 to 0.1.6 ([#6472])
|
||||
- build(deps-dev): bump qs from 6.14.1 to 6.14.2 in /clients/native/examples/js-examples/websocket ([#6466])
|
||||
- build(deps): bump mikefarah/yq from 4.52.2 to 4.52.4 ([#6465])
|
||||
- Otel minimal v2 ([#6464])
|
||||
- build(deps): bump qs and express in /wasm/client/internal-dev ([#6461])
|
||||
- bugfix: restore 'latest_measurement' field for nym-node /verloc endpoint ([#6452])
|
||||
- build(deps-dev): bump webpack from 5.77.0 to 5.104.1 in /wasm/node-tester/internal-dev ([#6451])
|
||||
- Max/mixfetch concurrent test ([#6417])
|
||||
|
||||
[#6533]: https://github.com/nymtech/nym/pull/6533
|
||||
[#6532]: https://github.com/nymtech/nym/pull/6532
|
||||
[#6531]: https://github.com/nymtech/nym/pull/6531
|
||||
[#6509]: https://github.com/nymtech/nym/pull/6509
|
||||
[#6495]: https://github.com/nymtech/nym/pull/6495
|
||||
[#6491]: https://github.com/nymtech/nym/pull/6491
|
||||
[#6486]: https://github.com/nymtech/nym/pull/6486
|
||||
[#6484]: https://github.com/nymtech/nym/pull/6484
|
||||
[#6483]: https://github.com/nymtech/nym/pull/6483
|
||||
[#6478]: https://github.com/nymtech/nym/pull/6478
|
||||
[#6477]: https://github.com/nymtech/nym/pull/6477
|
||||
[#6476]: https://github.com/nymtech/nym/pull/6476
|
||||
[#6475]: https://github.com/nymtech/nym/pull/6475
|
||||
[#6472]: https://github.com/nymtech/nym/pull/6472
|
||||
[#6466]: https://github.com/nymtech/nym/pull/6466
|
||||
[#6465]: https://github.com/nymtech/nym/pull/6465
|
||||
[#6464]: https://github.com/nymtech/nym/pull/6464
|
||||
[#6461]: https://github.com/nymtech/nym/pull/6461
|
||||
[#6452]: https://github.com/nymtech/nym/pull/6452
|
||||
[#6451]: https://github.com/nymtech/nym/pull/6451
|
||||
[#6417]: https://github.com/nymtech/nym/pull/6417
|
||||
|
||||
## [2026.4-quark] (2026-02-24)
|
||||
|
||||
- Enhance CI workflow with feature inputs ([#6462])
|
||||
|
||||
Generated
+1946
-1411
File diff suppressed because it is too large
Load Diff
+15
-9
@@ -157,8 +157,8 @@ members = [
|
||||
"tools/internal/mixnet-connectivity-check",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/localnet-orchestrator",
|
||||
"tools/internal/localnet-orchestrator/dkg-bypass-contract",
|
||||
"tools/internal/validator-status-check",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-id-cli",
|
||||
@@ -171,9 +171,10 @@ members = [
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
"wasm/zknym-lib",
|
||||
# "nym-gateway-probe",
|
||||
"nym-gateway-probe",
|
||||
"integration-tests",
|
||||
"common/nym-kkt-ciphersuite", "common/nym-kkt-context",
|
||||
"common/nym-kkt-ciphersuite",
|
||||
"common/nym-kkt-context",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -190,7 +191,8 @@ default-members = [
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
"tools/nymvisor",
|
||||
"nym-registration-client"
|
||||
"nym-registration-client",
|
||||
"tools/internal/localnet-orchestrator"
|
||||
]
|
||||
|
||||
exclude = ["contracts", "nym-wallet", "cpu-cycles"]
|
||||
@@ -202,7 +204,7 @@ homepage = "https://nymtech.net"
|
||||
documentation = "https://nymtech.net"
|
||||
edition = "2024"
|
||||
license = "Apache-2.0"
|
||||
rust-version = "1.85"
|
||||
rust-version = "1.87.0"
|
||||
readme = "README.md"
|
||||
version = "1.20.4"
|
||||
|
||||
@@ -232,6 +234,7 @@ bloomfilter = "3.0.1"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
bytes = "1.11.1"
|
||||
cargo-edit = "0.13.8"
|
||||
cargo_metadata = "0.19.2"
|
||||
celes = "2.6.0"
|
||||
cfg-if = "1.0.0"
|
||||
@@ -347,8 +350,8 @@ si-scale = "0.2.3"
|
||||
snow = "0.9.6"
|
||||
sphinx-packet = "=0.6.0"
|
||||
sqlx = "0.8.6"
|
||||
strum = "0.27.2"
|
||||
strum_macros = "0.27.2"
|
||||
strum = "0.28.0"
|
||||
strum_macros = "0.28.0"
|
||||
subtle-encoding = "0.5"
|
||||
syn = "2"
|
||||
sysinfo = "0.37.0"
|
||||
@@ -375,7 +378,7 @@ tracing-opentelemetry = "0.32.1"
|
||||
tracing-subscriber = "0.3.20"
|
||||
tracing-indicatif = "0.3.9"
|
||||
tracing-test = "0.2.5"
|
||||
ts-rs = "10.1.0"
|
||||
ts-rs = "12.0.1"
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
typed-builder = "0.23.0"
|
||||
uniffi = "0.29.2"
|
||||
@@ -438,6 +441,7 @@ nym-ecash-time = { version = "1.20.4", path = "common/ecash-time" }
|
||||
nym-exit-policy = { version = "1.20.4", path = "common/exit-policy" }
|
||||
nym-ffi-shared = { version = "1.20.4", path = "sdk/ffi/shared" }
|
||||
nym-gateway-client = { version = "1.20.4", path = "common/client-libs/gateway-client", default-features = false }
|
||||
nym-gateway-probe = { version = "1.18.0", path = "nym-gateway-probe" }
|
||||
nym-gateway-requests = { version = "1.20.4", path = "common/gateway-requests" }
|
||||
nym-gateway-storage = { version = "1.20.4", path = "common/gateway-storage" }
|
||||
nym-gateway-stats-storage = { version = "1.20.4", path = "common/gateway-stats-storage" }
|
||||
@@ -448,8 +452,10 @@ nym-http-api-common = { version = "1.20.4", path = "common/http-api-common", def
|
||||
nym-id = { version = "1.20.4", path = "common/nym-id" }
|
||||
nym-ip-packet-client = { version = "1.20.4", path = "nym-ip-packet-client" }
|
||||
nym-ip-packet-requests = { version = "1.20.4", path = "common/ip-packet-requests" }
|
||||
nym-lp = { version = "1.20.4", path = "common/nym-lp" }
|
||||
nym-kkt = { version = "0.1.0", path = "common/nym-kkt" }
|
||||
nym-kkt-ciphersuite = { version = "1.20.4", path = "common/nym-kkt-ciphersuite" }
|
||||
nym-kkt-context = { version = "1.20.4", path = "common/nym-kkt-context" }
|
||||
nym-metrics = { version = "1.20.4", path = "common/nym-metrics" }
|
||||
nym-mixnet-client = { version = "1.20.4", path = "common/client-libs/mixnet-client" }
|
||||
nym-mixnet-contract-common = { version = "1.20.4", path = "common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
|
||||
@@ -30,8 +30,11 @@ client ───► Gateway ──┘ mix │ mix ┌─►mix ───►
|
||||
|
||||
```
|
||||
|
||||
<!-- This is broken
|
||||
[](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
|
||||
-->
|
||||
|
||||
> This project integrates with the Midnight Network
|
||||
|
||||
### Building
|
||||
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
---
|
||||
ansible_ssh_private_key_file: ~/.ssh/<SSH_KEY>
|
||||
|
||||
# nym_version: "v2025.21-mozzarella"
|
||||
#
|
||||
# NOTE:
|
||||
# if you want to pin Nym to a specific version instead of using the
|
||||
# latest release from GitHub in /tasks/main.yml then
|
||||
# uncomment the line above and set the tag
|
||||
|
||||
cli_url: "https://github.com/nymtech/nym/releases/download/nym-binaries-{{ nym_version }}/nym-cli"
|
||||
tunnel_manager_url: "https://github.com/nymtech/nym/raw/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh"
|
||||
quic_bridge_deployment_url: "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh"
|
||||
|
||||
# NOTE: These values will be used globally unless overwritten per node in inventory/all
|
||||
###############################################################################
|
||||
## GLOBAL VARS
|
||||
## These values will be used globally unless overwritten per node in inventory/all
|
||||
###############################################################################
|
||||
|
||||
ansible_user: root # used for ssh, like `ssh root@nym-exit.ch-1.mynodes.net`
|
||||
email: "<EMAIL>" # used in certbot, description.toml and landing page
|
||||
website: "<WEBSITE>" # it is used in the description.toml
|
||||
description: "<NODE_PUBLIC_DESCRIPTION>" # or define per node in inventory/all
|
||||
# operator_name: "<OPERATOR_NAME>" # used in landing page if provided
|
||||
|
||||
###############################################################################
|
||||
## GLOBAL VARS
|
||||
## These values will be used globally unless overwritten per node in inventory/all
|
||||
## Set these vars only if you want them globally for all nodes
|
||||
## Per node changes in inventory/all will overwrite these global vars
|
||||
###############################################################################
|
||||
|
||||
# NOTE: Set these vars if you want them globally for all nodes
|
||||
# Per node changes in inventory/all will overwrite these global ones:
|
||||
hostname: "" # this is a fallback, keep it and setup hostname per node in inventory/all
|
||||
# moniker: "<MONIKER>" # if not setup here not in inventory/all it get's derived from the hostname
|
||||
# mode: <MODE> # entry-gateway/exit-gateway/mixnode
|
||||
# wireguard_enabled: <WIREGUARD_ENABLED> # true/false
|
||||
hostname: "" # this is a fallback, keep it and setup hostname per node in inventory/all
|
||||
|
||||
###############################################################################
|
||||
## GLOBAL PACKAGES
|
||||
## These will be installed during deployment
|
||||
###############################################################################
|
||||
|
||||
# NOTE: Possible vars to incule on landing page, etc.
|
||||
# operator_name: "<OPERATOR_NAME>"
|
||||
|
||||
packages:
|
||||
- tmux
|
||||
@@ -42,3 +48,73 @@ packages:
|
||||
- jq
|
||||
- wget
|
||||
- ufw
|
||||
|
||||
|
||||
###############################################################################
|
||||
## OPTIONAL OVERRIDES
|
||||
## All values below already have defaults in the playbook/roles
|
||||
## Uncomment only if you want to override them
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
## SYSTEM MAINTENANCE PLAYBOOK KNOBS
|
||||
###############################################################################
|
||||
|
||||
# nym_version: "v2025.21-mozzarella"
|
||||
|
||||
## NOTE:
|
||||
## if you want to pin Nym to a specific version instead of using the
|
||||
## latest release from GitHub in /tasks/main.yml then
|
||||
## uncomment the line above and set the tag
|
||||
|
||||
###############################################################################
|
||||
## SYSTEM MAINTENANCE PLAYBOOK KNOBS
|
||||
###############################################################################
|
||||
|
||||
## JOURNALD LIMITS
|
||||
|
||||
# journald_system_max_use: "100M" # max persistent journal size
|
||||
# journald_runtime_max_use: "50M" # max runtime journal size
|
||||
# journald_system_max_file_size: "25M" # max single journal file
|
||||
# journald_runtime_max_file_size: "10M" # max runtime journal file
|
||||
# journald_max_retention_sec: "3day" # retention time
|
||||
|
||||
# journald_rate_limit_interval: "30s" # rate limit window
|
||||
# journald_rate_limit_burst: "1000" # rate limit burst
|
||||
|
||||
|
||||
## NYM-NODE LOG CONTROL
|
||||
|
||||
# nymnode_log_level_max: "warning" # drop INFO logs
|
||||
# nymnode_rate_limit_interval: "30s" # per nym-node rate limit window
|
||||
# nymnode_rate_limit_burst: "200" # per nym-node rate limit burst
|
||||
|
||||
|
||||
## JOURNAL VACUUM TARGETS
|
||||
|
||||
# journal_vacuum_size: "100M"
|
||||
# journal_vacuum_time: "3days"
|
||||
|
||||
|
||||
## RSYSLOG
|
||||
|
||||
# disable_rsyslog: true
|
||||
|
||||
|
||||
## FSTRIM SCHEDULE
|
||||
|
||||
# fstrim_every_calendar: "*:0/15" # Aggressive
|
||||
# fstrim_every_calendar: "hourly" # Less aggressive
|
||||
|
||||
|
||||
## OPTIONAL CLEANUPS
|
||||
|
||||
# enable_apt_cleanup: true
|
||||
# enable_snap_cleanup: true
|
||||
|
||||
|
||||
## WRITEBACK TUNING
|
||||
|
||||
# enable_writeback_tuning: true
|
||||
# writeback_dirty_writeback_centisecs: 1500
|
||||
# writeback_dirty_expire_centisecs: 6000
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
- name: Restrict logging, vacuum journals, and enable periodic trim
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
# global knobs - override in inventory/group_vars/host_vars as needed
|
||||
vars:
|
||||
journald_system_max_use: "100M"
|
||||
journald_runtime_max_use: "50M"
|
||||
journald_system_max_file_size: "25M"
|
||||
journald_runtime_max_file_size: "10M"
|
||||
journald_max_retention_sec: "3day"
|
||||
journald_rate_limit_interval: "30s"
|
||||
journald_rate_limit_burst: "1000"
|
||||
|
||||
# per nym-node rate limit + level cap
|
||||
nymnode_log_level_max: "warning"
|
||||
nymnode_rate_limit_interval: "30s"
|
||||
nymnode_rate_limit_burst: "200"
|
||||
|
||||
# journal vacuum targets
|
||||
journal_vacuum_size: "100M"
|
||||
journal_vacuum_time: "3days"
|
||||
|
||||
# fstrim cadence (note: the systemd override uses cron-like calendar)
|
||||
fstrim_every_calendar: "*:0/15"
|
||||
|
||||
roles:
|
||||
- role: journald_limits
|
||||
- role: nymnode_logging
|
||||
- role: rsyslog_disable
|
||||
- role: journal_vacuum
|
||||
- role: classic_log_cleanup
|
||||
- role: apt_cleanup
|
||||
- role: snap_cleanup
|
||||
- role: fstrim_15min
|
||||
- role: report
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: Clean apt cache
|
||||
command: apt-get clean
|
||||
ignore_errors: true
|
||||
|
||||
- name: Autoremove unused packages
|
||||
command: apt-get -y autoremove
|
||||
ignore_errors: true
|
||||
|
||||
- name: Remove apt lists to reclaim space (they will be re-fetched on update)
|
||||
file:
|
||||
path: /var/lib/apt/lists
|
||||
state: absent
|
||||
ignore_errors: true
|
||||
|
||||
- name: Recreate apt lists directory
|
||||
file:
|
||||
path: /var/lib/apt/lists
|
||||
state: directory
|
||||
mode: "0755"
|
||||
ignore_errors: true
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: Remove classic /var/log files if present (optional)
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /var/log/syslog
|
||||
- /var/log/syslog.1
|
||||
- /var/log/kern.log
|
||||
- /var/log/kern.log.1
|
||||
- /var/log/auth.log
|
||||
- /var/log/auth.log.1
|
||||
- /var/log/ufw.log
|
||||
- /var/log/ufw.log.1
|
||||
ignore_errors: true
|
||||
|
||||
# This is best-effort and may still fail if other packages' postrotate scripts assume services exist.
|
||||
- name: Force logrotate (best-effort)
|
||||
command: "logrotate --force /etc/logrotate.conf"
|
||||
ignore_errors: true
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
fstrim_timer_dropin_dir: "/etc/systemd/system/fstrim.timer.d"
|
||||
fstrim_every_calendar: "*:0/15"
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
- name: Ensure systemd drop-in dir for fstrim.timer exists
|
||||
file:
|
||||
path: "{{ fstrim_timer_dropin_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Override fstrim.timer schedule
|
||||
copy:
|
||||
dest: "{{ fstrim_timer_dropin_dir }}/override.conf"
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Timer]
|
||||
OnCalendar=
|
||||
OnCalendar={{ fstrim_every_calendar }}
|
||||
Persistent=true
|
||||
RandomizedDelaySec=0
|
||||
|
||||
- name: Reload systemd after fstrim override
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Enable and start fstrim timer
|
||||
systemd:
|
||||
name: fstrim.timer
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Run fstrim now (best-effort)
|
||||
command: fstrim -av
|
||||
ignore_errors: true
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
journal_vacuum_size: "100M"
|
||||
journal_vacuum_time: "3days"
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Vacuum journal to size cap (hard)
|
||||
command: "journalctl --vacuum-size={{ journal_vacuum_size }}"
|
||||
|
||||
- name: Vacuum journal older than retention window (time)
|
||||
command: "journalctl --vacuum-time={{ journal_vacuum_time }}"
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
journald_system_max_use: "100M"
|
||||
journald_runtime_max_use: "50M"
|
||||
journald_system_max_file_size: "25M"
|
||||
journald_runtime_max_file_size: "10M"
|
||||
journald_max_retention_sec: "3day"
|
||||
journald_rate_limit_interval: "30s"
|
||||
journald_rate_limit_burst: "1000"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Restart journald
|
||||
systemd:
|
||||
name: systemd-journald
|
||||
state: restarted
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: Configure journald limits (persistent, capped, rate-limited)
|
||||
copy:
|
||||
dest: /etc/systemd/journald.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Journal]
|
||||
Storage=persistent
|
||||
Compress=yes
|
||||
Seal=yes
|
||||
|
||||
SystemMaxUse={{ journald_system_max_use }}
|
||||
RuntimeMaxUse={{ journald_runtime_max_use }}
|
||||
SystemMaxFileSize={{ journald_system_max_file_size }}
|
||||
RuntimeMaxFileSize={{ journald_runtime_max_file_size }}
|
||||
MaxRetentionSec={{ journald_max_retention_sec }}
|
||||
|
||||
RateLimitIntervalSec={{ journald_rate_limit_interval }}
|
||||
RateLimitBurst={{ journald_rate_limit_burst }}
|
||||
notify: Restart journald
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
nymnode_log_level_max: "warning"
|
||||
nymnode_rate_limit_interval: "30s"
|
||||
nymnode_rate_limit_burst: "200"
|
||||
nymnode_unit_name: "nym-node" # set to "nym-node.service" if your distro expects it
|
||||
nymnode_dropin_dir: "/etc/systemd/system/nym-node.service.d"
|
||||
nymnode_dropin_file: "10-logging.conf"
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
- name: Ensure systemd drop-in dir for nym-node exists
|
||||
file:
|
||||
path: "{{ nymnode_dropin_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Cap nym-node logs + apply per-unit rate limiting
|
||||
copy:
|
||||
dest: "{{ nymnode_dropin_dir }}/{{ nymnode_dropin_file }}"
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Service]
|
||||
LogLevelMax={{ nymnode_log_level_max }}
|
||||
LogRateLimitIntervalSec={{ nymnode_rate_limit_interval }}
|
||||
LogRateLimitBurst={{ nymnode_rate_limit_burst }}
|
||||
|
||||
- name: Reload systemd after nym-node drop-in
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart nym-node to apply new logging limits (best-effort)
|
||||
systemd:
|
||||
name: "{{ nymnode_unit_name }}"
|
||||
state: restarted
|
||||
ignore_errors: true
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Show journal disk usage
|
||||
command: journalctl --disk-usage
|
||||
register: journal_usage
|
||||
changed_when: false
|
||||
|
||||
- debug:
|
||||
var: journal_usage.stdout
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: Stop/disable rsyslog if installed (best-effort)
|
||||
systemd:
|
||||
name: rsyslog
|
||||
state: stopped
|
||||
enabled: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Remove rsyslog logrotate stanza if present (prevents logrotate failures)
|
||||
file:
|
||||
path: /etc/logrotate.d/rsyslog
|
||||
state: absent
|
||||
ignore_errors: true
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Remove disabled snap revisions (best-effort)
|
||||
shell: |
|
||||
set -euo pipefail
|
||||
snap list --all | awk '/disabled/{print $1, $3}' | while read -r name rev; do
|
||||
snap remove "$name" --revision="$rev" || true
|
||||
done
|
||||
args:
|
||||
executable: /bin/bash
|
||||
ignore_errors: true
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "nym-client-core"
|
||||
version.workspace = true
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
license.workspace = true
|
||||
description = "Crate containing core client functionality and configs, used by all other Nym client implentations"
|
||||
|
||||
@@ -32,6 +32,7 @@ const DEFAULT_MIN_MIXNODE_PERFORMANCE: u8 = 50;
|
||||
const DEFAULT_MIN_GATEWAY_PERFORMANCE: u8 = 50;
|
||||
|
||||
const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
|
||||
const DEFAULT_MAX_STARTUP_TOPOLOGY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
|
||||
|
||||
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
|
||||
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
|
||||
@@ -555,6 +556,11 @@ pub struct Topology {
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub max_startup_gateway_waiting_period: Duration,
|
||||
|
||||
/// Defines how long the client is going to wait on startup for minimal topology to become online,
|
||||
/// before abandoning the procedure.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub max_startup_network_waiting_period: Duration,
|
||||
|
||||
/// Specifies a minimum performance of a mixnode that is used on route construction.
|
||||
/// This setting is only applicable when `NymApi` topology is used.
|
||||
pub minimum_mixnode_performance: u8,
|
||||
@@ -583,6 +589,7 @@ impl Default for Topology {
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
disable_refreshing: false,
|
||||
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
|
||||
max_startup_network_waiting_period: DEFAULT_MAX_STARTUP_TOPOLOGY_WAITING_PERIOD,
|
||||
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
|
||||
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
|
||||
use_extended_topology: false,
|
||||
|
||||
@@ -159,6 +159,7 @@ impl From<ConfigV6> for Config {
|
||||
use_extended_topology: value.debug.topology.use_extended_topology,
|
||||
ignore_egress_epoch_role: value.debug.topology.ignore_egress_epoch_role,
|
||||
ignore_ingress_epoch_role: value.debug.topology.ignore_ingress_epoch_role,
|
||||
..Default::default()
|
||||
},
|
||||
reply_surbs: ReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: value
|
||||
|
||||
@@ -160,7 +160,10 @@ where
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
info!("registered with new gateway {} (under address {address}), but this will not be our default address", gateway_details.gateway_id);
|
||||
info!(
|
||||
"registered with new gateway {} (under address {address}), but this will not be our default address",
|
||||
gateway_details.gateway_id
|
||||
);
|
||||
}
|
||||
|
||||
Ok(GatewayInfo {
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
use super::mix_traffic::ClientRequestSender;
|
||||
use super::received_buffer::ReceivedBufferMessage;
|
||||
use super::statistics_control::StatisticsControl;
|
||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||
use crate::client::base_client::storage::MixnetClientStorage;
|
||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use crate::client::event_control::EventControl;
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::mix_traffic::transceiver::{GatewayReceiver, GatewayTransceiver, RemoteGateway};
|
||||
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController, MixTrafficEvent};
|
||||
use crate::client::real_messages_control;
|
||||
@@ -52,12 +52,12 @@ use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_statistics_common::generate_client_stats_id;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_task::ShutdownTracker;
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_topology::HardcodedTopologyProvider;
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, UserAgent};
|
||||
use nym_validator_client::{UserAgent, nyxd::contract_traits::DkgQueryClient};
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::thread_rng;
|
||||
@@ -220,6 +220,7 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
nym_api_urls: Option<Vec<nym_network_defaults::ApiUrl>>,
|
||||
|
||||
wait_for_gateway: bool,
|
||||
wait_for_initial_topology: bool,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
shutdown: Option<ShutdownTracker>,
|
||||
@@ -250,6 +251,7 @@ where
|
||||
dkg_query_client,
|
||||
nym_api_urls: None,
|
||||
wait_for_gateway: false,
|
||||
wait_for_initial_topology: false,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
shutdown: None,
|
||||
@@ -305,6 +307,12 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_wait_for_initial_topology(mut self, wait_for_initial_topology: bool) -> Self {
|
||||
self.wait_for_initial_topology = wait_for_initial_topology;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_topology_provider(
|
||||
mut self,
|
||||
@@ -674,6 +682,7 @@ where
|
||||
topology_accessor: TopologyAccessor,
|
||||
local_gateway: NodeIdentity,
|
||||
wait_for_gateway: bool,
|
||||
wait_for_initial_topology: bool,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let topology_refresher_config =
|
||||
@@ -694,6 +703,46 @@ where
|
||||
tracing::info!("Obtaining initial network topology");
|
||||
topology_refresher.try_refresh().await;
|
||||
|
||||
// 1. wait for the minimum topology (if applicable)
|
||||
if topology_refresher
|
||||
.ensure_topology_is_routable()
|
||||
.await
|
||||
.is_err()
|
||||
&& wait_for_initial_topology
|
||||
{
|
||||
if let Err(err) = topology_refresher
|
||||
.wait_for_initial_network(topology_config.max_startup_network_waiting_period)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"the network did not come become online within the specified timeout: {err}"
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. wait for our gateway (if applicable)
|
||||
if topology_refresher
|
||||
.ensure_contains_routable_egress(local_gateway)
|
||||
.await
|
||||
.is_err()
|
||||
&& wait_for_gateway
|
||||
{
|
||||
if let Err(err) = topology_refresher
|
||||
.wait_for_gateway(
|
||||
local_gateway,
|
||||
topology_config.max_startup_gateway_waiting_period,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"the gateway did not come back online within the specified timeout: {err}"
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. check if the topology is routable (in case we were NOT waiting for it)
|
||||
if let Err(err) = topology_refresher.ensure_topology_is_routable().await {
|
||||
tracing::error!(
|
||||
"The current network topology seem to be insufficient to route any packets through \
|
||||
@@ -702,30 +751,15 @@ where
|
||||
return Err(ClientCoreError::InsufficientNetworkTopology(err));
|
||||
}
|
||||
|
||||
let gateway_wait_timeout = if wait_for_gateway {
|
||||
Some(topology_config.max_startup_gateway_waiting_period)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 4. check if the gateway exists (in case we were NOT waiting for it)
|
||||
if let Err(err) = topology_refresher
|
||||
.ensure_contains_routable_egress(local_gateway)
|
||||
.await
|
||||
{
|
||||
if let Some(waiting_timeout) = gateway_wait_timeout {
|
||||
if let Err(err) = topology_refresher
|
||||
.wait_for_gateway(local_gateway, waiting_timeout)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"the gateway did not come back online within the specified timeout: {err}"
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
} else {
|
||||
tracing::error!("the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
tracing::error!(
|
||||
"the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}"
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
if !topology_config.disable_refreshing {
|
||||
@@ -1024,6 +1058,7 @@ where
|
||||
shared_topology_accessor.clone(),
|
||||
self_address.gateway(),
|
||||
self.wait_for_gateway,
|
||||
self.wait_for_initial_topology,
|
||||
&shutdown_tracker.clone(),
|
||||
)
|
||||
.await?;
|
||||
@@ -1195,9 +1230,11 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(network_details.nym_api_urls.as_ref().unwrap().len(), 2);
|
||||
assert!(network_details.nym_api_urls.as_ref().unwrap()[1]
|
||||
.front_hosts
|
||||
.is_some());
|
||||
assert!(
|
||||
network_details.nym_api_urls.as_ref().unwrap()[1]
|
||||
.front_hosts
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1210,11 +1247,13 @@ mod tests {
|
||||
|
||||
assert_eq!(api_url.url, "https://nym-frontdoor.vercel.app/api/");
|
||||
assert_eq!(api_url.front_hosts.as_ref().unwrap().len(), 2);
|
||||
assert!(api_url
|
||||
.front_hosts
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&"vercel.app".to_string()));
|
||||
assert!(
|
||||
api_url
|
||||
.front_hosts
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&"vercel.app".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
client::replies::reply_storage::{fs_backend, CombinedReplyStorage, ReplyStorageBackend},
|
||||
client::replies::reply_storage::{CombinedReplyStorage, ReplyStorageBackend, fs_backend},
|
||||
config,
|
||||
config::Config,
|
||||
error::ClientCoreError,
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core_gateways_storage::OnDiskGatewaysDetails;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient};
|
||||
use nym_validator_client::{QueryHttpRpcNyxdClient, nyxd};
|
||||
use std::{io, path::Path};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{error, info, trace};
|
||||
@@ -24,7 +24,9 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
|
||||
Ok(backend) => backend,
|
||||
Err(err) => {
|
||||
error!("setup_fresh_backend: Failed to setup persistent storage backend for our reply needs: {err}");
|
||||
error!(
|
||||
"setup_fresh_backend: Failed to setup persistent storage backend for our reply needs: {err}"
|
||||
);
|
||||
return Err(ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
});
|
||||
@@ -93,7 +95,9 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
match fs_backend::Backend::try_load(db_path).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("setup_fs_reply_surb_backend: Failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
error!(
|
||||
"setup_fs_reply_surb_backend: Failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future"
|
||||
);
|
||||
archive_corrupted_database(db_path).await?;
|
||||
setup_fresh_backend(db_path, surb_config).await
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::error::ClientCoreError;
|
||||
use nym_client_core_gateways_storage::{
|
||||
ActiveGateway, GatewayPublishedData, GatewayRegistration, GatewaysDetailsStore,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod v1_1_33 {
|
||||
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
use crate::config::disk_persistence::CommonClientPaths;
|
||||
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
|
||||
use crate::error::ClientCoreError;
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::cover::generate_loop_cover_packet;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::utils::sample_poisson_duration;
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use nym_statistics_common::clients::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
|
||||
use rand::{CryptoRng, Rng, rngs::OsRng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -20,10 +20,10 @@ use tokio::sync::mpsc::error::TrySendError;
|
||||
use tracing::*;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::{sleep, Sleep};
|
||||
use tokio::time::{Sleep, sleep};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::{sleep, Sleep};
|
||||
use wasmtimer::tokio::{Sleep, sleep};
|
||||
|
||||
pub struct LoopCoverTrafficStream<R>
|
||||
where
|
||||
@@ -179,7 +179,9 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
) {
|
||||
Ok(topology) => topology,
|
||||
Err(err) => {
|
||||
warn!("We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}");
|
||||
warn!(
|
||||
"We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,10 +13,10 @@ use crate::config::disk_persistence::ClientKeysPaths;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_pemstore::KeyPairPath;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
|
||||
// we have to define it as an async trait since wasm storage is async
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
use async_trait::async_trait;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
|
||||
+3
-3
@@ -2,13 +2,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::action_controller::{AckActionSender, Action};
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_statistics_common::clients::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
|
||||
|
||||
use futures::StreamExt;
|
||||
use nym_gateway_client::AcknowledgementReceiver;
|
||||
use nym_sphinx::{
|
||||
acknowledgements::{identifier::recover_identifier, AckKey},
|
||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||
acknowledgements::{AckKey, identifier::recover_identifier},
|
||||
chunking::fragment::{COVER_FRAG_ID, FragmentIdentifier},
|
||||
};
|
||||
use nym_task::ShutdownToken;
|
||||
use std::sync::Arc;
|
||||
|
||||
+2
-2
@@ -3,11 +3,11 @@
|
||||
|
||||
use super::PendingAcknowledgement;
|
||||
use crate::client::real_messages_control::acknowledgement_control::RetransmissionRequestSender;
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use futures::channel::mpsc;
|
||||
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey};
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_sphinx::Delay as SphinxDelay;
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_task::ShutdownToken;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
+1
-1
@@ -9,8 +9,8 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use tracing::*;
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ use nym_gateway_client::AcknowledgementReceiver;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::{
|
||||
Delay as SphinxDelay,
|
||||
acknowledgements::AckKey,
|
||||
addressing::clients::Recipient,
|
||||
chunking::fragment::{Fragment, FragmentIdentifier},
|
||||
Delay as SphinxDelay,
|
||||
};
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
action_controller::{AckActionSender, Action},
|
||||
PendingAcknowledgement, RetransmissionRequestReceiver,
|
||||
action_controller::{AckActionSender, Action},
|
||||
};
|
||||
use crate::client::real_messages_control::acknowledgement_control::PacketDestination;
|
||||
use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError};
|
||||
@@ -13,7 +13,7 @@ use futures::StreamExt;
|
||||
use nym_sphinx::chunking::fragment::Fragment;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
|
||||
use nym_task::{connections::TransmissionLane, ShutdownToken};
|
||||
use nym_task::{ShutdownToken, connections::TransmissionLane};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::{Arc, Weak};
|
||||
use tracing::*;
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::action_controller::{AckActionSender, Action};
|
||||
use super::SentPacketNotificationReceiver;
|
||||
use super::action_controller::{AckActionSender, Action};
|
||||
use futures::StreamExt;
|
||||
use nym_sphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||
use nym_sphinx::chunking::fragment::{COVER_FRAG_ID, FragmentIdentifier};
|
||||
use tracing::*;
|
||||
|
||||
/// Module responsible for starting up retransmission timers.
|
||||
|
||||
@@ -10,17 +10,17 @@ use crate::client::replies::reply_controller::MaxRetransmissions;
|
||||
use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags};
|
||||
use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit};
|
||||
use nym_client_core_surb_storage::RetrievedReplySurb;
|
||||
use nym_sphinx::Delay;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessage, ReplyMessage};
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessage, ReplyMessage};
|
||||
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use nym_sphinx::message::NymMessage;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
|
||||
use nym_sphinx::Delay;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashMap;
|
||||
@@ -272,7 +272,9 @@ where
|
||||
let primary_count = msg.required_packets(self.config.primary_packet_size);
|
||||
let secondary_count = msg.required_packets(secondary_packet);
|
||||
|
||||
trace!("This message would require: {primary_count} primary packets or {secondary_count} secondary packets...");
|
||||
trace!(
|
||||
"This message would require: {primary_count} primary packets or {secondary_count} secondary packets..."
|
||||
);
|
||||
// if there would be no benefit in using the secondary packet - use the primary (duh)
|
||||
if primary_count <= secondary_count {
|
||||
trace!("so choosing primary for this message");
|
||||
|
||||
@@ -25,9 +25,9 @@ use nym_gateway_client::AcknowledgementReceiver;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||
use nym_task::ShutdownToken;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||
use rand::{CryptoRng, Rng, rngs::OsRng};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::client::replies::reply_controller::key_rotation_helpers::KeyRotationConfig;
|
||||
|
||||
@@ -17,11 +17,11 @@ use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_sphinx::utils::sample_poisson_duration;
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_statistics_common::clients::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_task::connections::{
|
||||
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
use nym_task::ShutdownToken;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -29,11 +29,11 @@ use std::time::Duration;
|
||||
use tracing::*;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::{sleep, Sleep};
|
||||
use tokio::time::{Sleep, sleep};
|
||||
|
||||
// use nym_wasm_utils::console_log;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::{sleep, Sleep};
|
||||
use wasmtimer::tokio::{Sleep, sleep};
|
||||
mod sending_delay_controller;
|
||||
|
||||
/// Configurable parameters of the `OutQueueControl`
|
||||
@@ -230,7 +230,9 @@ where
|
||||
let (next_message, fragment_id, packet_size) = match next_message {
|
||||
StreamMessage::Cover => {
|
||||
let cover_traffic_packet_size = self.loop_cover_message_size();
|
||||
trace!("the next loop cover message will be put in a {cover_traffic_packet_size} packet");
|
||||
trace!(
|
||||
"the next loop cover message will be put in a {cover_traffic_packet_size} packet"
|
||||
);
|
||||
|
||||
// TODO for way down the line: in very rare cases (during topology update) we might have
|
||||
// to wait a really tiny bit before actually obtaining the permit hence messing with our
|
||||
@@ -244,7 +246,9 @@ where
|
||||
) {
|
||||
Ok(topology) => topology,
|
||||
Err(err) => {
|
||||
warn!("We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}");
|
||||
warn!(
|
||||
"We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -436,7 +440,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut next_delay) = &mut self.next_delay {
|
||||
if let Some(next_delay) = &mut self.next_delay {
|
||||
// it is not yet time to return a message
|
||||
if next_delay.as_mut().poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::helpers::{get_time_now, Instant};
|
||||
use crate::client::helpers::{Instant, get_time_now};
|
||||
use std::time::Duration;
|
||||
|
||||
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
|
||||
|
||||
@@ -5,20 +5,20 @@ use crate::client::helpers::get_time_now;
|
||||
use crate::client::replies::{
|
||||
reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_crypto::Digest;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_gateway_client::MixnetMessageReceiver;
|
||||
use nym_sphinx::anonymous_replies::requests::{
|
||||
RepliableMessage, RepliableMessageContent, ReplyMessage, ReplyMessageContent,
|
||||
};
|
||||
use nym_sphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey};
|
||||
use nym_sphinx::anonymous_replies::{SurbEncryptionKey, encryption_key::EncryptionKeyDigest};
|
||||
use nym_sphinx::message::{NymMessage, PlainMessage};
|
||||
use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_statistics_common::clients::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
|
||||
use nym_task::ShutdownToken;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
@@ -78,14 +78,19 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
|
||||
let fragment = match self.message_receiver.recover_fragment(fragment_data) {
|
||||
Err(err) => {
|
||||
warn!("failed to recover fragment from raw data: {err}. The whole underlying message might be corrupted and unrecoverable!");
|
||||
warn!(
|
||||
"failed to recover fragment from raw data: {err}. The whole underlying message might be corrupted and unrecoverable!"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Ok(frag) => frag,
|
||||
};
|
||||
|
||||
if self.recently_reconstructed.contains(&fragment.id()) {
|
||||
debug!("Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost", fragment.id());
|
||||
debug!(
|
||||
"Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost",
|
||||
fragment.id()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -93,7 +98,9 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
match self.message_receiver.insert_new_fragment(fragment) {
|
||||
Err(err) => match err {
|
||||
MessageRecoveryError::MalformedReconstructedMessage { source, used_sets } => {
|
||||
error!("message reconstruction failed - {source}. Attempting to re-use the message sets...");
|
||||
error!(
|
||||
"message reconstruction failed - {source}. Attempting to re-use the message sets..."
|
||||
);
|
||||
// TODO: should we really insert reconstructed sets? could this be abused for some attack?
|
||||
for set_id in used_sets {
|
||||
if !self.recently_reconstructed.insert(set_id) {
|
||||
@@ -144,7 +151,9 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
&mut raw_fragment,
|
||||
) {
|
||||
Err(err) => {
|
||||
warn!("failed to recover fragment data: {err}. The whole underlying message might be corrupted and unrecoverable!");
|
||||
warn!(
|
||||
"failed to recover fragment data: {err}. The whole underlying message might be corrupted and unrecoverable!"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Ok(frag_data) => frag_data,
|
||||
@@ -275,7 +284,9 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
}
|
||||
RepliableMessageContent::Heartbeat(content) => {
|
||||
let additional_reply_surbs = content.additional_reply_surbs;
|
||||
error!("received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)");
|
||||
error!(
|
||||
"received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)"
|
||||
);
|
||||
(additional_reply_surbs, false)
|
||||
}
|
||||
RepliableMessageContent::DataV2(content) => {
|
||||
@@ -304,7 +315,9 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
}
|
||||
RepliableMessageContent::HeartbeatV2(content) => {
|
||||
let additional_reply_surbs = content.additional_reply_surbs;
|
||||
error!("received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)");
|
||||
error!(
|
||||
"received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)"
|
||||
);
|
||||
(additional_reply_surbs, false)
|
||||
}
|
||||
};
|
||||
@@ -380,7 +393,9 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
if let Some(sender) = &inner_guard.message_sender {
|
||||
trace!("Sending reconstructed messages to announced sender");
|
||||
if let Err(err) = sender.unbounded_send(reconstructed_messages) {
|
||||
warn!("The reconstructed message receiver went offline without explicit notification (relevant error: - {err})");
|
||||
warn!(
|
||||
"The reconstructed message receiver went offline without explicit notification (relevant error: - {err})"
|
||||
);
|
||||
inner_guard.message_sender = None;
|
||||
inner_guard.messages.extend(err.into_inner());
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ use crate::client::real_messages_control::acknowledgement_control::PendingAcknow
|
||||
use crate::client::real_messages_control::message_handler::{
|
||||
FragmentWithMaxRetransmissions, MessageHandler, PreparationError,
|
||||
};
|
||||
use crate::client::replies::reply_controller::key_rotation_helpers::SurbRefreshState;
|
||||
use crate::client::replies::reply_controller::Config;
|
||||
use crate::client::replies::reply_controller::key_rotation_helpers::SurbRefreshState;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use crate::client::transmission_buffer::TransmissionBuffer;
|
||||
use futures::channel::oneshot;
|
||||
use nym_client_core_surb_storage::{ReceivedReplySurb, ReceivedReplySurbsMap};
|
||||
use nym_crypto::aes::cipher::crypto_common::rand_core::CryptoRng;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||
use nym_topology::NymTopologyMetadata;
|
||||
@@ -50,7 +50,9 @@ impl SenderData {
|
||||
let pending_retransmissions = self.pending_retransmissions.len();
|
||||
let total_pending = pending_retransmissions + pending_replies;
|
||||
|
||||
debug!("total queue size: {total_pending} = pending data {pending_replies} + pending retransmission {pending_retransmissions}");
|
||||
debug!(
|
||||
"total queue size: {total_pending} = pending data {pending_replies} + pending retransmission {pending_retransmissions}"
|
||||
);
|
||||
|
||||
total_pending
|
||||
}
|
||||
@@ -200,7 +202,9 @@ where
|
||||
let total_required_surbs = total_queue + target_surbs_after_clearing_queue;
|
||||
let total_available_surbs = pending_surbs + available_surbs;
|
||||
|
||||
debug!("available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..+{min_surbs_threshold_buffer}..{max_surbs_threshold}");
|
||||
debug!(
|
||||
"available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..+{min_surbs_threshold_buffer}..{max_surbs_threshold}"
|
||||
);
|
||||
|
||||
// We should request more surbs if:
|
||||
// 1. We haven't hit the maximum surb threshold, and
|
||||
@@ -225,9 +229,13 @@ where
|
||||
.is_none()
|
||||
{
|
||||
// don't report it every single time
|
||||
warn!("received reply request for {recipient_tag} but we don't have any surbs stored for that recipient!");
|
||||
warn!(
|
||||
"received reply request for {recipient_tag} but we don't have any surbs stored for that recipient!"
|
||||
);
|
||||
} else {
|
||||
trace!("received reply request for {recipient_tag} but we don't have any surbs stored for that recipient!");
|
||||
trace!(
|
||||
"received reply request for {recipient_tag} but we don't have any surbs stored for that recipient!"
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -383,7 +391,9 @@ where
|
||||
let (surbs_for_reply, _) = self.surbs_storage.get_reply_surbs(&target, to_take.len());
|
||||
|
||||
let Some(surbs_for_reply) = surbs_for_reply else {
|
||||
error!("somehow different task has stolen our reply surbs! - this should have been impossible");
|
||||
error!(
|
||||
"somehow different task has stolen our reply surbs! - this should have been impossible"
|
||||
);
|
||||
self.re_insert_pending_retransmission(&target, to_take);
|
||||
return;
|
||||
};
|
||||
@@ -459,7 +469,9 @@ where
|
||||
.get_reply_surbs(&target, to_send_clone.len());
|
||||
|
||||
let Some(surbs_for_reply) = surbs_for_reply else {
|
||||
error!("somehow different task has stolen our reply surbs! - this should have been impossible");
|
||||
error!(
|
||||
"somehow different task has stolen our reply surbs! - this should have been impossible"
|
||||
);
|
||||
self.re_insert_pending_replies(&target, to_send);
|
||||
return;
|
||||
};
|
||||
@@ -543,7 +555,9 @@ where
|
||||
let ack_ref = match timed_out_ack.upgrade() {
|
||||
Some(ack) => ack,
|
||||
None => {
|
||||
debug!("we received the ack for one of the reply packets as we were putting it in the retransmission queue");
|
||||
debug!(
|
||||
"we received the ack for one of the reply packets as we were putting it in the retransmission queue"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -657,9 +671,13 @@ where
|
||||
|
||||
// only log at higher level if it's the first time this error has occurred in a while
|
||||
if now - last_failure > time::Duration::seconds(30) {
|
||||
warn!("failed to request more surbs to clear pending queue of size {total_queue} (attempted to request: {request_size}): {err}")
|
||||
warn!(
|
||||
"failed to request more surbs to clear pending queue of size {total_queue} (attempted to request: {request_size}): {err}"
|
||||
)
|
||||
} else {
|
||||
debug!("failed to request more surbs to clear pending queue of size {total_queue} (attempted to request: {request_size}): {err}")
|
||||
debug!(
|
||||
"failed to request more surbs to clear pending queue of size {total_queue} (attempted to request: {request_size}): {err}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -681,7 +699,10 @@ where
|
||||
.surbs_storage
|
||||
.surbs_last_received_at(pending_reply_target)
|
||||
else {
|
||||
error!("we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!", retransmission_buf.total_size());
|
||||
error!(
|
||||
"we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!",
|
||||
retransmission_buf.total_size()
|
||||
);
|
||||
to_remove.push(*pending_reply_target);
|
||||
continue;
|
||||
};
|
||||
@@ -702,7 +723,9 @@ where
|
||||
// if client is offline)
|
||||
if vals.current_clear_rerequest_counter > max_rerequests {
|
||||
to_remove.push(*pending_reply_target);
|
||||
debug!("we have reached the maximum threshold of attempting to request surbs from {pending_reply_target}. dropping the sender");
|
||||
debug!(
|
||||
"we have reached the maximum threshold of attempting to request surbs from {pending_reply_target}. dropping the sender"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -710,7 +733,10 @@ where
|
||||
if diff > max_drop_wait {
|
||||
to_remove.push(*pending_reply_target)
|
||||
} else {
|
||||
debug!("We haven't received any surbs in {} from {pending_reply_target}. Going to explicitly ask for more", humantime::format_duration(diff.unsigned_abs()));
|
||||
debug!(
|
||||
"We haven't received any surbs in {} from {pending_reply_target}. Going to explicitly ask for more",
|
||||
humantime::format_duration(diff.unsigned_abs())
|
||||
);
|
||||
vals.increment_current_clear_rerequest_counter();
|
||||
to_request.push(*pending_reply_target);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||
use std::sync::Weak;
|
||||
|
||||
|
||||
@@ -43,7 +43,9 @@ where
|
||||
// 1. check whether we sent any surbs in the past to this recipient, otherwise
|
||||
// they have no business in asking for more
|
||||
if !self.tags_storage.exists(&recipient) {
|
||||
warn!("{recipient} asked us for reply SURBs even though we never sent them any anonymous messages before!");
|
||||
warn!(
|
||||
"{recipient} asked us for reply SURBs even though we never sent them any anonymous messages before!"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -54,7 +56,12 @@ where
|
||||
.reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size
|
||||
{
|
||||
warn!("The requested reply surb amount is larger than our maximum allowed ({amount} > {}). Lowering it to a more sane value...", self.config.reply_surbs.maximum_allowed_reply_surb_request_size);
|
||||
warn!(
|
||||
"The requested reply surb amount is larger than our maximum allowed ({amount} > {}). Lowering it to a more sane value...",
|
||||
self.config
|
||||
.reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size
|
||||
);
|
||||
amount = self
|
||||
.config
|
||||
.reply_surbs
|
||||
|
||||
@@ -23,7 +23,7 @@ use nym_sphinx::addressing::Recipient;
|
||||
use nym_statistics_common::clients::{
|
||||
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
||||
};
|
||||
use nym_task::{connections::TransmissionLane, ShutdownToken, ShutdownTracker};
|
||||
use nym_task::{ShutdownToken, ShutdownTracker, connections::TransmissionLane};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Time interval between reporting statistics locally (logging/shutdown_token)
|
||||
|
||||
@@ -5,8 +5,8 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::{NymRouteProvider, NymTopology, NymTopologyError, NymTopologyMetadata};
|
||||
use nym_validator_client::models::KeyRotationId;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tokio::sync::{Notify, RwLock, RwLockReadGuard};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -63,7 +63,9 @@ impl TopologyRefresher {
|
||||
trace!("Refreshing the topology");
|
||||
|
||||
if self.topology_accessor.controlled_manually() {
|
||||
info!("topology is being controlled manually - we're going to wait until the control is released...");
|
||||
info!(
|
||||
"topology is being controlled manually - we're going to wait until the control is released..."
|
||||
);
|
||||
self.topology_accessor
|
||||
.wait_for_released_manual_control()
|
||||
.await;
|
||||
@@ -138,6 +140,35 @@ impl TopologyRefresher {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait_for_initial_network(
|
||||
&mut self,
|
||||
timeout_duration: Duration,
|
||||
) -> Result<(), NymTopologyError> {
|
||||
info!(
|
||||
"going to wait for at most {timeout_duration:?} for initial network to become online"
|
||||
);
|
||||
|
||||
let deadline = sleep(timeout_duration);
|
||||
tokio::pin!(deadline);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = &mut deadline => {
|
||||
return Err(NymTopologyError::TimedOutWaitingForTopology)
|
||||
}
|
||||
_ = self.try_refresh() => {
|
||||
if let Err(err) = self.ensure_topology_is_routable().await {
|
||||
info!("network is still not routable...: {err}");
|
||||
} else {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
sleep(self.refresh_rate).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// it's perfectly fine if task is interrupted mid-refresh
|
||||
// there's no data to persist or send over
|
||||
pub async fn run(&mut self) {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
|
||||
use nym_topology::NymTopology;
|
||||
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
@@ -82,7 +82,9 @@ impl NymApiTopologyProvider {
|
||||
|
||||
fn use_next_nym_api(&mut self) {
|
||||
if self.nym_api_urls.len() == 1 {
|
||||
warn!("There's only a single nym API available - it won't be possible to use a different one");
|
||||
warn!(
|
||||
"There's only a single nym API available - it won't be possible to use a different one"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,7 +157,10 @@ impl NymApiTopologyProvider {
|
||||
let mixnodes = mixnodes_res.nodes;
|
||||
|
||||
if !gateways_res.metadata.consistency_check(&metadata) {
|
||||
warn!("inconsistent nodes metadata between mixnodes and gateways calls! {metadata:?} and {:?}", gateways_res.metadata);
|
||||
warn!(
|
||||
"inconsistent nodes metadata between mixnodes and gateways calls! {metadata:?} and {:?}",
|
||||
gateways_res.metadata
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::helpers::{get_time_now, Instant};
|
||||
use crate::client::helpers::{Instant, get_time_now};
|
||||
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
|
||||
use nym_sphinx::chunking::fragment::Fragment;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use rand::{Rng, seq::SliceRandom};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
time::Duration,
|
||||
|
||||
@@ -7,9 +7,9 @@ use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_task::RegistryAccessError;
|
||||
use nym_topology::node::RoutingNodeError;
|
||||
use nym_topology::{NodeId, NymTopologyError};
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use rand::distributions::WeightedError;
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
@@ -56,7 +56,9 @@ pub enum ClientCoreError {
|
||||
#[error("no gateways on network")]
|
||||
NoGatewaysOnNetwork,
|
||||
|
||||
#[error("there are no more new gateways on the network - it seems this client has already registered with all nodes it could have")]
|
||||
#[error(
|
||||
"there are no more new gateways on the network - it seems this client has already registered with all nodes it could have"
|
||||
)]
|
||||
NoNewGatewaysAvailable,
|
||||
|
||||
#[error("list of nym apis is empty")]
|
||||
@@ -127,7 +129,9 @@ pub enum ClientCoreError {
|
||||
#[error("unexpected exit")]
|
||||
UnexpectedExit,
|
||||
|
||||
#[error("this operation would have resulted in the gateway {gateway_id:?} key being overwritten without permission")]
|
||||
#[error(
|
||||
"this operation would have resulted in the gateway {gateway_id:?} key being overwritten without permission"
|
||||
)]
|
||||
ForbiddenGatewayKeyOverwrite { gateway_id: String },
|
||||
|
||||
#[error(
|
||||
@@ -151,7 +155,9 @@ pub enum ClientCoreError {
|
||||
#[error("attempted to obtain fresh gateway details whilst already knowing about one")]
|
||||
UnexpectedGatewayDetails,
|
||||
|
||||
#[error("the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys")]
|
||||
#[error(
|
||||
"the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys"
|
||||
)]
|
||||
MismatchedGatewayDetails { gateway_id: String },
|
||||
|
||||
#[error("unable to upgrade config file from `{current_version}`")]
|
||||
@@ -227,7 +233,9 @@ pub enum ClientCoreError {
|
||||
source: url::ParseError,
|
||||
},
|
||||
|
||||
#[error("this client (id: '{client_id}') has already been initialised before. If you want to add additional gateway, use `add-gateway` command")]
|
||||
#[error(
|
||||
"this client (id: '{client_id}') has already been initialised before. If you want to add additional gateway, use `add-gateway` command"
|
||||
)]
|
||||
AlreadyInitialised { client_id: String },
|
||||
|
||||
#[error("this client has already registered with gateway {gateway_id}")]
|
||||
|
||||
@@ -5,13 +5,13 @@ 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_gateway_client::client::GatewayListeners;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::UserAgent;
|
||||
use nym_validator_client::client::{IdentityKeyRef, NymApiClientExt};
|
||||
use nym_validator_client::nym_nodes::SkimmedNodesWithMetadata;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use rand::{Rng, seq::SliceRandom};
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::RawFd;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
@@ -28,10 +28,10 @@ use nym_wasm_utils::websocket::JSWebsocket;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::net::TcpStream;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::Instant;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::std::Instant;
|
||||
|
||||
@@ -7,8 +7,8 @@ use crate::client::base_client::storage::helpers::{
|
||||
has_gateway_details, load_active_gateway_details, load_client_keys, load_gateway_details,
|
||||
store_gateway_details, update_stored_published_data_gateway,
|
||||
};
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::init::helpers::{
|
||||
choose_gateway_by_latency, get_specified_gateway, uniformly_random_gateway,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::config::Config;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::init::{setup_gateway, use_loaded_gateway_details};
|
||||
@@ -10,8 +10,8 @@ use nym_client_core_gateways_storage::{
|
||||
GatewayRegistration, GatewaysDetailsStore, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::client::{GatewayListeners, InitGatewayClient};
|
||||
use nym_gateway_client::SharedSymmetricKey;
|
||||
use nym_gateway_client::client::{GatewayListeners, InitGatewayClient};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(deprecated)] // silences clippy warning: use of deprecated associated function `nym_crypto::generic_array::GenericArray::<T, N>::clone_from_slice`: please upgrade to generic-array 1.x - TODO
|
||||
use std::future::Future;
|
||||
|
||||
#[cfg(all(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)] // silences clippy warning: use of deprecated associated function `nym_crypto::generic_array::GenericArray::<T, N>::from_exact_iter`: please upgrade to generic-array 1.x - TODO
|
||||
pub use backend::*;
|
||||
pub use combined::CombinedReplyStorage;
|
||||
pub use key_storage::SentReplyKeys;
|
||||
|
||||
@@ -23,7 +23,7 @@ use nym_api_requests::models::{
|
||||
MixnodeCoreStatusResponse, NymNodeDescriptionV1, NymNodeDescriptionV2,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNode, SkimmedNodesWithMetadata,
|
||||
NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNodeV1, SkimmedNodesWithMetadata,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_http_api_client::UserAgent;
|
||||
@@ -354,12 +354,12 @@ impl NymApiClient {
|
||||
}
|
||||
|
||||
#[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes instead")]
|
||||
pub async fn get_basic_mixnodes(&self) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
pub async fn get_basic_mixnodes(&self) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_basic_mixnodes().await?.nodes)
|
||||
}
|
||||
|
||||
#[deprecated(note = "use get_all_basic_entry_assigned_nodes instead")]
|
||||
pub async fn get_basic_gateways(&self) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
pub async fn get_basic_gateways(&self) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_basic_gateways().await?.nodes)
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ impl NymApiClient {
|
||||
#[deprecated(note = "use get_all_basic_entry_assigned_nodes_with_metadata instead")]
|
||||
pub async fn get_all_basic_entry_assigned_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
|
||||
self.get_all_basic_entry_assigned_nodes_with_metadata()
|
||||
.await
|
||||
.map(|res| res.nodes)
|
||||
@@ -389,7 +389,7 @@ impl NymApiClient {
|
||||
#[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes_with_metadata instead")]
|
||||
pub async fn get_all_basic_active_mixing_assigned_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
|
||||
self.get_all_basic_active_mixing_assigned_nodes_with_metadata()
|
||||
.await
|
||||
.map(|res| res.nodes)
|
||||
@@ -406,7 +406,7 @@ impl NymApiClient {
|
||||
#[deprecated(note = "use get_all_basic_mixing_capable_nodes_with_metadata instead")]
|
||||
pub async fn get_all_basic_mixing_capable_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
|
||||
self.get_all_basic_mixing_capable_nodes_with_metadata()
|
||||
.await
|
||||
.map(|res| res.nodes)
|
||||
@@ -420,7 +420,7 @@ impl NymApiClient {
|
||||
|
||||
/// retrieve basic information for all bonded nodes on the network
|
||||
#[deprecated(note = "use get_all_basic_nodes_with_metadata instead")]
|
||||
pub async fn get_all_basic_nodes(&self) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
pub async fn get_all_basic_nodes(&self) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
|
||||
self.get_all_basic_nodes_with_metadata()
|
||||
.await
|
||||
.map(|res| res.nodes)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::nym_api::error::NymAPIError;
|
||||
use crate::nym_api::routes::{ecash, CORE_STATUS_COUNT, SINCE_ARG};
|
||||
use crate::nym_nodes::SkimmedNodesWithMetadata;
|
||||
use crate::ValidatorClientError;
|
||||
use async_trait::async_trait;
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -20,11 +21,14 @@ use nym_api_requests::models::{
|
||||
NymNodeDescriptionV1, NymNodeDescriptionV2, PerformanceHistoryResponse, RewardedSetResponse,
|
||||
SignerInformationResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponseV1,
|
||||
PaginatedCachedNodesResponseV2,
|
||||
};
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_mixnet_contract_common::{IdentityKeyRef, NodeId, NymNodeDetails};
|
||||
use std::net::IpAddr;
|
||||
use time::format_description::BorrowedFormatItem;
|
||||
use time::Date;
|
||||
use tracing::instrument;
|
||||
|
||||
pub use nym_api_requests::{
|
||||
ecash::{
|
||||
models::SpentCredentialsResponse, BlindSignRequestBody, BlindedSignatureResponse,
|
||||
@@ -36,17 +40,14 @@ pub use nym_api_requests::{
|
||||
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
|
||||
MixnodeUptimeHistoryResponse, StakeSaturationResponse, UptimeResponse,
|
||||
},
|
||||
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata, SkimmedNode},
|
||||
nym_nodes::{
|
||||
CachedNodesResponse, NodesByAddressesRequestBody, NodesByAddressesResponse,
|
||||
PaginatedCachedNodesResponseV1, PaginatedCachedNodesResponseV2, SemiSkimmedNodeV1,
|
||||
SemiSkimmedNodeV3, SemiSkimmedNodesWithMetadata, SkimmedNodeV1,
|
||||
},
|
||||
NymNetworkDetailsResponse,
|
||||
};
|
||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_mixnet_contract_common::{IdentityKeyRef, NodeId, NymNodeDetails};
|
||||
use std::net::IpAddr;
|
||||
use time::format_description::BorrowedFormatItem;
|
||||
use time::Date;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::ValidatorClientError;
|
||||
pub use nym_coconut_dkg_common::types::EpochId;
|
||||
|
||||
pub mod error;
|
||||
@@ -390,7 +391,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
#[deprecated]
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNodeV1>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
@@ -406,7 +407,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_gateways(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
async fn get_basic_gateways(&self) -> Result<CachedNodesResponse<SkimmedNodeV1>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
@@ -443,7 +444,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -485,7 +486,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -527,7 +528,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -569,7 +570,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -612,7 +613,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -654,7 +655,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -695,7 +696,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -733,7 +734,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -770,7 +771,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SemiSkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SemiSkimmedNodeV1>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -797,6 +798,21 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_expanded_nodes_v3(
|
||||
&self,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SemiSkimmedNodeV3>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if use_bincode {
|
||||
params.push(("output", "bincode".to_string()))
|
||||
}
|
||||
|
||||
self.get_response("/v3/unstable/nym-nodes/semi-skimmed", ¶ms)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_report(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
pub const V1_API_VERSION: &str = "v1";
|
||||
pub const V2_API_VERSION: &str = "v2";
|
||||
pub const V3_API_VERSION: &str = "v3";
|
||||
pub const MIXNODES: &str = "mixnodes";
|
||||
pub const GATEWAYS: &str = "gateways";
|
||||
pub const DESCRIBED: &str = "described";
|
||||
|
||||
+1
-1
@@ -130,7 +130,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
|
||||
let req = QueryBalanceRequest {
|
||||
address: address.to_string(),
|
||||
denom: search_denom.to_string(),
|
||||
denom: search_denom,
|
||||
};
|
||||
|
||||
let res = self
|
||||
|
||||
@@ -199,6 +199,18 @@ impl NyxdClient<HttpClient, DirectSecp256k1HdWallet> {
|
||||
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
|
||||
Ok(Self::connect_with_signer(config, client, wallet))
|
||||
}
|
||||
|
||||
pub fn connect_with_mnemonic_and_network_details<U>(
|
||||
endpoint: U,
|
||||
network_details: NymNetworkDetails,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<DirectSigningHttpRpcNyxdClient, NyxdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let config = Config::try_from_nym_network_details(&network_details)?;
|
||||
Self::connect_with_mnemonic(config, endpoint, mnemonic)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(clippy::derivable_impls)]
|
||||
// MAX: surpressing warning for the moment, will be dealt with in a different PR (TODO)
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use strum::IntoEnumIterator as _;
|
||||
use thiserror::Error;
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
@@ -315,6 +316,10 @@ impl TicketType {
|
||||
_ => Err(UnknownTicketType),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exposed_iter() -> impl Iterator<Item = TicketType> {
|
||||
TicketType::iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TicketType> for TicketTypeRepr {
|
||||
|
||||
@@ -511,14 +511,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_key_conversion() {
|
||||
let dalek_kp = super::KeyPair::new(&mut rand::thread_rng());
|
||||
let dalek_kp = KeyPair::new(&mut rand::thread_rng());
|
||||
|
||||
let mut dalek_private_key_bytes = dalek_kp.private_key().as_bytes().to_owned();
|
||||
|
||||
libcrux_curve25519::clamp(&mut dalek_private_key_bytes);
|
||||
let libcrux_private_key =
|
||||
libcrux_psq::handshake::types::DHPrivateKey::from_bytes(&dalek_private_key_bytes)
|
||||
.unwrap();
|
||||
let libcrux_private_key = DHPrivateKey::from_bytes(&dalek_private_key_bytes).unwrap();
|
||||
let libcrux_public_key = libcrux_private_key.to_public();
|
||||
|
||||
assert_eq!(libcrux_public_key.as_ref(), dalek_kp.public_key.as_bytes());
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)] // silences clippy warning: deprecated associated function `generic_array::GenericArray::<T, N>::from_exact_iter`: please upgrade to generic-array 1.x - TODO
|
||||
|
||||
#[cfg(feature = "asymmetric")]
|
||||
pub mod asymmetric;
|
||||
pub mod bech32_address_validation;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#![allow(deprecated)] // silences clippy warning: deprecated associated function `nym_crypto::generic_array::GenericArray::<T, N>::clone_from_slice`: please upgrade to generic-array 1.x - TODO
|
||||
|
||||
pub use nym_crypto::generic_array;
|
||||
use nym_crypto::OutputSizeUser;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)]
|
||||
// silences clippy warning: use of deprecated tuple variant `HttpClientError::GenericRequestFailure`: use another more strongly typed variant - this variant is only left for compatibility reasons - TODO
|
||||
|
||||
//! Nym HTTP API Client
|
||||
//!
|
||||
//! Centralizes and implements the core API client functionality. This crate provides custom,
|
||||
@@ -1401,6 +1404,7 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// 'get' data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
|
||||
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
|
||||
/// into the provided type `T` based on the content type header
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
async fn get_response<P, T, K, V>(
|
||||
&self,
|
||||
path: P,
|
||||
|
||||
@@ -22,6 +22,16 @@ pub struct ChainDetails {
|
||||
pub stake_denom: DenomDetailsOwned,
|
||||
}
|
||||
|
||||
impl ChainDetails {
|
||||
pub fn mainnet() -> Self {
|
||||
ChainDetails {
|
||||
bech32_account_prefix: mainnet::BECH32_PREFIX.into(),
|
||||
mix_denom: mainnet::MIX_DENOM.into(),
|
||||
stake_denom: mainnet::STAKE_DENOM.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
pub struct NymContracts {
|
||||
@@ -181,11 +191,7 @@ impl NymNetworkDetails {
|
||||
// Consider caching this process (lazy static)
|
||||
NymNetworkDetails {
|
||||
network_name: mainnet::NETWORK_NAME.into(),
|
||||
chain_details: ChainDetails {
|
||||
bech32_account_prefix: mainnet::BECH32_PREFIX.into(),
|
||||
mix_denom: mainnet::MIX_DENOM.into(),
|
||||
stake_denom: mainnet::STAKE_DENOM.into(),
|
||||
},
|
||||
chain_details: ChainDetails::mainnet(),
|
||||
endpoints: mainnet::validators(),
|
||||
contracts: NymContracts {
|
||||
mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS),
|
||||
|
||||
@@ -12,9 +12,9 @@ num_enum = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-crypto = { path = "../crypto", features = ["hashing"] }
|
||||
nym-crypto = { workspace = true, features = ["hashing"] }
|
||||
nym-kkt-ciphersuite = { workspace = true, features = ["digests"] }
|
||||
nym-kkt-context = { path = "../nym-kkt-context" }
|
||||
nym-kkt-context = { workspace = true }
|
||||
nym-pemstore = { workspace = true }
|
||||
|
||||
libcrux-kem = { workspace = true }
|
||||
@@ -30,6 +30,7 @@ libcrux-ml-kem = { workspace = true }
|
||||
[dev-dependencies]
|
||||
rand_chacha = "0.9.0"
|
||||
anyhow = { workspace = true }
|
||||
nym-test-utils = { workspace = true }
|
||||
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::error::KKTError;
|
||||
pub const MAX_PAYLOAD_LEN: usize = 1_000_000;
|
||||
const CARRIER_KDF_INFO_TX: &str = "CARRIER_V1_KDF_TX";
|
||||
const CARRIER_KDF_INFO_RX: &str = "CARRIER_V1_KDF_RX";
|
||||
const CARRIER_KKT_AAD: &[u8] = b"kkt-carrier-v1";
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct Carrier {
|
||||
@@ -107,7 +108,7 @@ impl Carrier {
|
||||
&self.tx_key,
|
||||
plaintext,
|
||||
&mut output_buffer,
|
||||
b"kkt-carrier-v1",
|
||||
CARRIER_KKT_AAD,
|
||||
&as_nonce_bytes(self.tx_counter),
|
||||
)?;
|
||||
|
||||
@@ -126,7 +127,7 @@ impl Carrier {
|
||||
&self.rx_key,
|
||||
&mut output_buffer,
|
||||
ciphertext,
|
||||
b"kkt-carrier-v1",
|
||||
CARRIER_KKT_AAD,
|
||||
&as_nonce_bytes(self.rx_counter),
|
||||
)?;
|
||||
|
||||
|
||||
+20
-11
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::context::KKTStatus;
|
||||
use nym_kkt_ciphersuite::error::KKTCiphersuiteError;
|
||||
use nym_kkt_ciphersuite::{HashFunction, KEM};
|
||||
use nym_kkt_context::KKTContextEncodingError;
|
||||
use std::fmt::Debug;
|
||||
use thiserror::Error;
|
||||
@@ -15,40 +16,40 @@ pub enum KKTError {
|
||||
#[error(transparent)]
|
||||
MaskedByteError(#[from] MaskedByteError),
|
||||
|
||||
#[error("KEM mapping failure: {}", info)]
|
||||
#[error("KEM mapping failure: {info}")]
|
||||
KEMMapping { info: &'static str },
|
||||
|
||||
#[error("Insecure Encapsulation Key Hash Length")]
|
||||
InsecureHashLen,
|
||||
|
||||
#[error("KKT Frame Decoding Error: {}", info)]
|
||||
#[error("KKT Frame Decoding Error: {info}")]
|
||||
FrameDecodingError { info: String },
|
||||
|
||||
#[error("KKT Frame Encoding Error: {}", info)]
|
||||
#[error("KKT Frame Encoding Error: {info}")]
|
||||
FrameEncodingError { info: String },
|
||||
|
||||
#[error("KKT Incompatibility Error: {}", info)]
|
||||
#[error("KKT Incompatibility Error: {info}")]
|
||||
IncompatibilityError { info: &'static str },
|
||||
|
||||
#[error("KKT Responder Flagged Error: {}", status)]
|
||||
#[error("KKT Responder Flagged Error: {status}")]
|
||||
ResponderFlaggedError { status: KKTStatus },
|
||||
|
||||
#[error("PSQ KEM Error: {}", info)]
|
||||
#[error("PSQ KEM Error: {info}")]
|
||||
KEMError { info: &'static str },
|
||||
|
||||
#[error("Local Function Input Error: {}", info)]
|
||||
#[error("Local Function Input Error: {info}")]
|
||||
FunctionInputError { info: &'static str },
|
||||
|
||||
#[error("{}", info)]
|
||||
#[error("{info}")]
|
||||
X25519Error { info: &'static str },
|
||||
|
||||
#[error("{}", info)]
|
||||
#[error("{info}")]
|
||||
AEADError { info: &'static str },
|
||||
|
||||
#[error("{}", info)]
|
||||
#[error("{info}")]
|
||||
DecodingError { info: &'static str },
|
||||
|
||||
#[error("{}", info)]
|
||||
#[error("{info}")]
|
||||
UnsupportedAlgorithm { info: &'static str },
|
||||
|
||||
#[error("Generic libcrux error")]
|
||||
@@ -62,6 +63,14 @@ pub enum KKTError {
|
||||
#[error("the received encapsulation key hash does not match the expected value")]
|
||||
MismatchedKEMHash,
|
||||
|
||||
#[error(
|
||||
"there are no known digests for initiator's KEM key with {kem} KEM and {hash_function} hash function"
|
||||
)]
|
||||
NoKnownKEMKeyDigests {
|
||||
kem: KEM,
|
||||
hash_function: HashFunction,
|
||||
},
|
||||
|
||||
#[error(transparent)]
|
||||
MalformedContext(#[from] KKTContextEncodingError),
|
||||
}
|
||||
|
||||
@@ -140,14 +140,7 @@ pub fn initiator_process(
|
||||
},
|
||||
};
|
||||
|
||||
Ok(KKTFrame::new(
|
||||
context,
|
||||
body,
|
||||
match payload {
|
||||
Some(payload_vec) => payload_vec,
|
||||
None => Vec::with_capacity(0),
|
||||
},
|
||||
))
|
||||
Ok(KKTFrame::new(context, body, payload.unwrap_or_default()))
|
||||
}
|
||||
|
||||
pub fn initiator_ingest_response(
|
||||
|
||||
+124
-57
@@ -16,9 +16,6 @@ pub use nym_kkt_context as context;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, HashLength, KEM, SignatureScheme};
|
||||
use rand09::RngCore;
|
||||
|
||||
use crate::keys::KEMKeys;
|
||||
use crate::{
|
||||
initiator::KKTInitiator,
|
||||
@@ -28,9 +25,13 @@ mod test {
|
||||
},
|
||||
responder::KKTResponder,
|
||||
};
|
||||
use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, HashLength, KEM, SignatureScheme};
|
||||
use nym_test_utils::helpers::deterministic_rng_09;
|
||||
use rand09::RngCore;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn test_kkt_psq_e2e_encrypted_carrier() {
|
||||
fn test_kkt_psq_e2e_one_way_encrypted_carrier() {
|
||||
let mut rng = rand09::rng();
|
||||
|
||||
let mut payload: Vec<u8> = vec![0u8; 900_000];
|
||||
@@ -46,7 +47,6 @@ mod test {
|
||||
HashFunction::Shake256,
|
||||
] {
|
||||
// generate kem public keys
|
||||
|
||||
let responder_mlkem_keypair = generate_keypair_mlkem(&mut rng);
|
||||
let responder_mceliece_keypair = generate_keypair_mceliece(&mut rng);
|
||||
|
||||
@@ -63,24 +63,13 @@ mod test {
|
||||
HashLength::Default.value(),
|
||||
responder_kem.mc_eliece_encapsulation_key().as_ref(),
|
||||
);
|
||||
let initiator_mlkem_keypair = generate_keypair_mlkem(&mut rng);
|
||||
let initiator_mceliece_keypair = generate_keypair_mceliece(&mut rng);
|
||||
|
||||
let _i_dir_hash_mlkem = hash_encapsulation_key(
|
||||
hash_function,
|
||||
HashLength::Default.value(),
|
||||
initiator_mlkem_keypair.public_key().as_slice(),
|
||||
);
|
||||
|
||||
let _i_dir_hash_mceliece = hash_encapsulation_key(
|
||||
hash_function,
|
||||
HashLength::Default.value(),
|
||||
initiator_mceliece_keypair.pk.as_ref(),
|
||||
);
|
||||
let init_hashes = BTreeMap::new();
|
||||
|
||||
let responder = KKTResponder::new(
|
||||
&responder_x25519_keypair,
|
||||
&responder_kem,
|
||||
&init_hashes,
|
||||
&[
|
||||
HashFunction::Blake3,
|
||||
HashFunction::SHA256,
|
||||
@@ -124,41 +113,6 @@ mod test {
|
||||
responder_kem.ml_kem768_encapsulation_key().as_slice(),
|
||||
)
|
||||
}
|
||||
// Mutual - MlKem
|
||||
{
|
||||
let ciphersuite = Ciphersuite::resolve_ciphersuite(
|
||||
KEM::MlKem768,
|
||||
hash_function,
|
||||
SignatureScheme::Ed25519,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let (mut initiator, request) = KKTInitiator::generate_one_way_request(
|
||||
&mut rng,
|
||||
ciphersuite,
|
||||
&responder_x25519_keypair.pk,
|
||||
&r_dir_hash_mlkem,
|
||||
1u8,
|
||||
Some(payload.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let processed_request = responder.process_request(request, payload.len()).unwrap();
|
||||
|
||||
assert_eq!(processed_request.request_payload, payload);
|
||||
|
||||
// if we keep unverified keys, this should change
|
||||
assert!(processed_request.remote_encapsulation_key.is_none());
|
||||
|
||||
let processed_response = initiator
|
||||
.process_response(processed_request.response, 0)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
processed_response.encapsulation_key.as_bytes(),
|
||||
responder_kem.ml_kem768_encapsulation_key().as_slice(),
|
||||
)
|
||||
}
|
||||
|
||||
// OneWay - McEliece
|
||||
{
|
||||
@@ -191,7 +145,110 @@ mod test {
|
||||
responder_kem.mc_eliece_encapsulation_key().as_ref()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kkt_psq_e2e_mutual_encrypted_carrier() {
|
||||
let mut rng = deterministic_rng_09();
|
||||
|
||||
let mut payload: Vec<u8> = vec![0u8; 50000];
|
||||
rng.fill_bytes(&mut payload);
|
||||
|
||||
// generate kem public keys
|
||||
let initiator_mlkem_keypair = generate_keypair_mlkem(&mut rng);
|
||||
let initiator_mceliece_keypair = generate_keypair_mceliece(&mut rng);
|
||||
|
||||
let responder_mlkem_keypair = generate_keypair_mlkem(&mut rng);
|
||||
let responder_mceliece_keypair = generate_keypair_mceliece(&mut rng);
|
||||
|
||||
let responder_x25519_keypair = generate_lp_keypair_x25519(&mut rng);
|
||||
|
||||
let initiator_kem = KEMKeys::new(initiator_mceliece_keypair, initiator_mlkem_keypair);
|
||||
let responder_kem = KEMKeys::new(responder_mceliece_keypair, responder_mlkem_keypair);
|
||||
|
||||
let init_hashes = initiator_kem.encapsulation_keys_digests();
|
||||
|
||||
let responder = KKTResponder::new(
|
||||
&responder_x25519_keypair,
|
||||
&responder_kem,
|
||||
&init_hashes,
|
||||
&[
|
||||
HashFunction::Blake3,
|
||||
HashFunction::SHA256,
|
||||
HashFunction::Shake128,
|
||||
HashFunction::Shake256,
|
||||
],
|
||||
&[SignatureScheme::Ed25519],
|
||||
&[1],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for hash_function in [
|
||||
HashFunction::Blake3,
|
||||
HashFunction::SHA256,
|
||||
HashFunction::Shake128,
|
||||
HashFunction::Shake256,
|
||||
] {
|
||||
let r_dir_hash_mlkem = hash_encapsulation_key(
|
||||
hash_function,
|
||||
HashLength::Default.value(),
|
||||
responder_kem.ml_kem768_encapsulation_key().as_slice(),
|
||||
);
|
||||
|
||||
let r_dir_hash_mceliece = hash_encapsulation_key(
|
||||
hash_function,
|
||||
HashLength::Default.value(),
|
||||
responder_kem.mc_eliece_encapsulation_key().as_ref(),
|
||||
);
|
||||
|
||||
// Mutual - MlKem
|
||||
{
|
||||
let ciphersuite = Ciphersuite::resolve_ciphersuite(
|
||||
KEM::MlKem768,
|
||||
hash_function,
|
||||
SignatureScheme::Ed25519,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let (mut initiator, request) = KKTInitiator::generate_mutual_request(
|
||||
&mut rng,
|
||||
ciphersuite,
|
||||
initiator_kem
|
||||
.encoded_encapsulation_key(KEM::MlKem768)
|
||||
.unwrap(),
|
||||
&responder_x25519_keypair.pk,
|
||||
&r_dir_hash_mlkem,
|
||||
1u8,
|
||||
Some(payload.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let processed_request = responder.process_request(request, payload.len()).unwrap();
|
||||
|
||||
assert_eq!(processed_request.request_payload, payload);
|
||||
assert_eq!(
|
||||
processed_request
|
||||
.remote_encapsulation_key
|
||||
.unwrap()
|
||||
.as_bytes(),
|
||||
initiator_kem
|
||||
.encapsulation_key(KEM::MlKem768)
|
||||
.unwrap()
|
||||
.as_bytes()
|
||||
);
|
||||
|
||||
let processed_response = initiator
|
||||
.process_response(processed_request.response, 0)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
processed_response.encapsulation_key.as_bytes(),
|
||||
responder_kem.ml_kem768_encapsulation_key().as_slice(),
|
||||
)
|
||||
}
|
||||
|
||||
// Mutual - McEliece is not supported due to the key being too large
|
||||
{
|
||||
let ciphersuite = Ciphersuite::resolve_ciphersuite(
|
||||
KEM::McEliece,
|
||||
@@ -200,9 +257,12 @@ mod test {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let (mut initiator, request) = KKTInitiator::generate_one_way_request(
|
||||
let (mut initiator, request) = KKTInitiator::generate_mutual_request(
|
||||
&mut rng,
|
||||
ciphersuite,
|
||||
initiator_kem
|
||||
.encoded_encapsulation_key(KEM::McEliece)
|
||||
.unwrap(),
|
||||
&responder_x25519_keypair.pk,
|
||||
&r_dir_hash_mceliece,
|
||||
1u8,
|
||||
@@ -213,9 +273,16 @@ mod test {
|
||||
let processed_request = responder.process_request(request, payload.len()).unwrap();
|
||||
|
||||
assert_eq!(processed_request.request_payload, payload);
|
||||
|
||||
// if we keep unverified keys, this should change
|
||||
assert!(processed_request.remote_encapsulation_key.is_none());
|
||||
assert_eq!(
|
||||
processed_request
|
||||
.remote_encapsulation_key
|
||||
.unwrap()
|
||||
.as_bytes(),
|
||||
initiator_kem
|
||||
.encapsulation_key(KEM::McEliece)
|
||||
.unwrap()
|
||||
.as_bytes()
|
||||
);
|
||||
|
||||
let processed_response = initiator
|
||||
.process_response(processed_request.response, 0)
|
||||
|
||||
@@ -114,14 +114,14 @@ impl KKTRequestPlaintext {
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut out = Vec::with_capacity(x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN);
|
||||
let mut out = Vec::with_capacity(Self::SIZE);
|
||||
out.extend_from_slice(self.dh_pubkey.as_ref());
|
||||
out.extend_from_slice(self.masked_version_bytes.as_slice());
|
||||
out
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_bytes(b: &[u8]) -> Result<Self, KKTError> {
|
||||
if b.len() != x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN {
|
||||
if b.len() != Self::SIZE {
|
||||
return Err(KKTError::FrameDecodingError {
|
||||
info: "the KKTRequest frame has invalid length".to_string(),
|
||||
});
|
||||
|
||||
@@ -4,15 +4,13 @@
|
||||
//! Post-Quantum Re-Key Protocol
|
||||
|
||||
/// This module implements a stateless post-quantum re-keying protocol in one round-trip.
|
||||
/// We currently support MlKem768 and XWing.
|
||||
/// We currently support MlKem768.
|
||||
///
|
||||
/// This protocol is safe if it runs under a trusted secure channel.
|
||||
///
|
||||
/// Bandwidth costs:
|
||||
/// Request (MlKem768): 1216 bytes
|
||||
/// Response (MlKem768): 1088 bytes
|
||||
/// Request (XWing): 1248 bytes
|
||||
/// Response (XWing): 1120 bytes
|
||||
use libcrux_kem::*;
|
||||
use nym_crypto::hkdf::blake3::derive_key_blake3;
|
||||
use nym_kkt_ciphersuite::{KEM, mceliece, ml_kem768, x25519, xwing};
|
||||
@@ -60,7 +58,7 @@ impl RekeyInitiator {
|
||||
///
|
||||
/// Inputs:
|
||||
/// rng: something that implements CryptoRng + RngCore
|
||||
/// kem: a KEM algorithm (we currently support MlKem768 and XWing)
|
||||
/// kem: a KEM algorithm (we currently support MlKem768 only)
|
||||
///
|
||||
/// Outputs:
|
||||
/// RekeyInitiator: A struct which contains the decapsulation key, the salt and the kem algorithm in use.
|
||||
@@ -171,7 +169,7 @@ where
|
||||
Some(num) => match num {
|
||||
// If message length is 1216 (32 + 1184) then the algorithm should be MlKem768
|
||||
ml_kem768::PUBLIC_KEY_LENGTH => Algorithm::MlKem768,
|
||||
// If message length is 1248 (32 + 1216) then the algorithm should be MlKem768
|
||||
// If message length is 1248 (32 + 1216) then the algorithm should be xwing
|
||||
xwing::PUBLIC_KEY_LENGTH => Algorithm::XWingKemDraft06,
|
||||
// We don't support McEliece because the keys are massive.
|
||||
// If this is a deal-breaker, users can start a new session with PSQ which can use McEliece.
|
||||
|
||||
@@ -10,7 +10,8 @@ use crate::{
|
||||
frame::KKTFrame,
|
||||
};
|
||||
use libcrux_psq::handshake::types::DHKeyPair;
|
||||
use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, SignatureScheme};
|
||||
use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, KEM, KEMKeyDigests, SignatureScheme};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Representation of a KKT Responder
|
||||
pub struct KKTResponder<'a> {
|
||||
@@ -20,6 +21,9 @@ pub struct KKTResponder<'a> {
|
||||
/// KEM keys of this responder
|
||||
kem_keys: &'a KEMKeys,
|
||||
|
||||
/// Digests of the initiator's kem key
|
||||
expected_initiator_kem_digests: &'a BTreeMap<KEM, KEMKeyDigests>,
|
||||
|
||||
/// List of supported Hash Functions by this Responder
|
||||
supported_hash_functions: Vec<HashFunction>,
|
||||
|
||||
@@ -34,6 +38,7 @@ impl<'a> KKTResponder<'a> {
|
||||
pub fn new(
|
||||
x25519_keypair: &'a DHKeyPair,
|
||||
kem_keys: &'a KEMKeys,
|
||||
expected_initiator_kem_digests: &'a BTreeMap<KEM, KEMKeyDigests>,
|
||||
supported_hash_functions: &[HashFunction],
|
||||
supported_signature_schemes: &[SignatureScheme],
|
||||
supported_outer_protocol_versions: &[u8],
|
||||
@@ -59,12 +64,28 @@ impl<'a> KKTResponder<'a> {
|
||||
Ok(Self {
|
||||
x25519_keypair,
|
||||
kem_keys,
|
||||
expected_initiator_kem_digests,
|
||||
supported_hash_functions: supported_hash_functions.to_vec(),
|
||||
supported_signature_schemes: supported_signature_schemes.to_vec(),
|
||||
supported_outer_protocol_versions: supported_outer_protocol_versions.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempt to retrieve expected KEM key hash of the initiator based on the received `Ciphersuite`
|
||||
pub(crate) fn expected_initiator_kem_digest(
|
||||
&self,
|
||||
ciphersuite: Ciphersuite,
|
||||
) -> Result<&Vec<u8>, KKTError> {
|
||||
let kem = ciphersuite.kem();
|
||||
let hash_function = ciphersuite.hash_function();
|
||||
|
||||
self.expected_initiator_kem_digests
|
||||
.get(&kem)
|
||||
.ok_or(KKTError::NoKnownKEMKeyDigests { kem, hash_function })?
|
||||
.get(&hash_function)
|
||||
.ok_or(KKTError::NoKnownKEMKeyDigests { kem, hash_function })
|
||||
}
|
||||
|
||||
fn check_ciphersuite_compatiblity(
|
||||
&self,
|
||||
remote_ciphersuite: Ciphersuite,
|
||||
@@ -102,6 +123,7 @@ impl<'a> KKTResponder<'a> {
|
||||
)?;
|
||||
|
||||
let remote_context = *processed_req.remote_context();
|
||||
|
||||
let remote_frame = processed_req.remote_frame;
|
||||
let request_payload = remote_frame.payload().to_vec();
|
||||
let mut carrier = processed_req.carrier;
|
||||
@@ -111,12 +133,8 @@ impl<'a> KKTResponder<'a> {
|
||||
let (local_context, remote_encapsulation_key) = match remote_context.mode() {
|
||||
KKTMode::OneWay => responder_ingest_message(None, remote_frame)?,
|
||||
KKTMode::Mutual => {
|
||||
// So we can either fetch the remote hash here using some async call to the directory,
|
||||
// which might make registration hang or accept the sent key then verify later.
|
||||
|
||||
// If we choose to not accept, the response's status will be KKTStatus::UnverifiedKEMKey.
|
||||
// The response would still contain the responder's encapsulation key.
|
||||
responder_ingest_message(None, remote_frame)?
|
||||
let digest = self.expected_initiator_kem_digest(remote_context.ciphersuite())?;
|
||||
responder_ingest_message(Some(digest), remote_frame)?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,7 +146,7 @@ impl<'a> KKTResponder<'a> {
|
||||
};
|
||||
|
||||
// for now the response payload is empty
|
||||
let response_payload = Vec::with_capacity(0);
|
||||
let response_payload = Vec::new();
|
||||
|
||||
let frame = KKTFrame::new(local_context, kem_key, response_payload);
|
||||
|
||||
@@ -162,14 +180,6 @@ pub fn responder_ingest_message(
|
||||
own_context.update_status(KKTStatus::UnverifiedKEMKey);
|
||||
// we don't store an unverified key
|
||||
// changing the status notifies the initiator that we didn't
|
||||
|
||||
// we could still keep it here and then verify later...
|
||||
// let received_encapsulation_key = EncapsulationKey::decode(
|
||||
// own_context.ciphersuite().kem(),
|
||||
// remote_frame.body_ref(),
|
||||
// )?;
|
||||
// Ok((own_context, Some(received_encapsulation_key)))
|
||||
//
|
||||
return Ok((own_context, None));
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
[package]
|
||||
name = "nym-lp"
|
||||
version = "0.1.0"
|
||||
edition = { workspace = true }
|
||||
license = { workspace = true }
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
version.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
@@ -11,11 +17,11 @@ bs58 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
rand09 = { workspace = true }
|
||||
tls_codec = { workspace = true }
|
||||
tls_codec = { workspace = true }
|
||||
tokio = { workspace = true, features = ["net", "io-util"] }
|
||||
|
||||
nym-crypto = { path = "../crypto", features = ["hashing"] }
|
||||
nym-kkt = { path = "../nym-kkt" }
|
||||
nym-crypto = { workspace = true, features = ["hashing"] }
|
||||
nym-kkt = { workspace = true }
|
||||
nym-kkt-ciphersuite = { workspace = true }
|
||||
|
||||
# libcrux dependencies for PSQ (Post-Quantum PSK derivation)
|
||||
@@ -28,7 +34,7 @@ zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
nym-test-utils = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
criterion = { workspace = true, features = ["html_reports"] }
|
||||
nym-test-utils = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::LpError;
|
||||
use crate::packet::{EncryptedLpPacket, InnerHeader, LpHeader, LpMessage, LpPacket};
|
||||
use crate::packet::{EncryptedLpPacket, InnerHeader, LpFrame, LpHeader, LpPacket};
|
||||
use bytes::BytesMut;
|
||||
use libcrux_psq::Channel;
|
||||
|
||||
@@ -46,9 +46,9 @@ pub(crate) fn encrypt_lp_packet(
|
||||
packet: LpPacket,
|
||||
transport: &mut libcrux_psq::session::Transport,
|
||||
) -> Result<EncryptedLpPacket, LpError> {
|
||||
let mut plaintext = BytesMut::with_capacity(InnerHeader::SIZE + packet.message().len());
|
||||
let mut plaintext = BytesMut::with_capacity(InnerHeader::SIZE + packet.frame().len());
|
||||
packet.header().inner.encode(&mut plaintext);
|
||||
packet.message().encode(&mut plaintext);
|
||||
packet.frame().encode(&mut plaintext);
|
||||
|
||||
let ciphertext = encrypt_data(plaintext.as_ref(), transport)?;
|
||||
|
||||
@@ -67,14 +67,14 @@ pub(crate) fn decrypt_lp_packet(
|
||||
|
||||
let inner_header = InnerHeader::parse(&plaintext)?;
|
||||
let payload = &plaintext[InnerHeader::SIZE..];
|
||||
let message = LpMessage::decode(payload)?;
|
||||
let frame = LpFrame::decode(payload)?;
|
||||
|
||||
Ok(LpPacket::new(
|
||||
LpHeader {
|
||||
outer: packet.outer_header(),
|
||||
inner: inner_header,
|
||||
},
|
||||
message,
|
||||
frame,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ pub(crate) fn decrypt_lp_packet(
|
||||
mod tests {
|
||||
use crate::LpError;
|
||||
use crate::codec::{decrypt_data, decrypt_lp_packet, encrypt_data, encrypt_lp_packet};
|
||||
use crate::packet::{EncryptedLpPacket, LpHeader, LpMessage, LpPacket};
|
||||
use crate::packet::{EncryptedLpPacket, LpFrame, LpHeader, LpPacket};
|
||||
use crate::peer::mock_peers;
|
||||
use crate::psq::initiator::{build_psq_ciphersuite, build_psq_principal};
|
||||
use crate::psq::{PSQ_MSG2_SIZE, psq_msg1_size, responder};
|
||||
@@ -261,7 +261,7 @@ mod tests {
|
||||
// happy path
|
||||
let packet = LpPacket::new(
|
||||
LpHeader::new(123, 0, 1),
|
||||
LpMessage::new_opaque(b"foomp".to_vec()),
|
||||
LpFrame::new_opaque(b"foomp".to_vec()),
|
||||
);
|
||||
|
||||
let ciphertext = encrypt_lp_packet(packet.clone(), &mut init_transport).unwrap();
|
||||
@@ -273,7 +273,7 @@ mod tests {
|
||||
// incomplete ciphertext
|
||||
let packet = LpPacket::new(
|
||||
LpHeader::new(123, 1, 1),
|
||||
LpMessage::new_opaque(b"foomp".to_vec()),
|
||||
LpFrame::new_opaque(b"foomp".to_vec()),
|
||||
);
|
||||
let ciphertext2 = encrypt_lp_packet(packet, &mut init_transport).unwrap();
|
||||
let l = ciphertext2.ciphertext().len();
|
||||
@@ -285,7 +285,7 @@ mod tests {
|
||||
// too small buffer
|
||||
let packet = LpPacket::new(
|
||||
LpHeader::new(123, 1, 1),
|
||||
LpMessage::new_opaque(b"foomp".to_vec()),
|
||||
LpFrame::new_opaque(b"foomp".to_vec()),
|
||||
);
|
||||
let ciphertext3 = encrypt_lp_packet(packet, &mut resp_transport).unwrap();
|
||||
let malformed = EncryptedLpPacket::new(ciphertext3.outer_header(), vec![]);
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Configuration for LP protocol.
|
||||
//!
|
||||
//! LP security stack = KKT (key fetch) → PSQ (PQ PSK) → Noise (transport).
|
||||
//! KEM algorithm selection affects only PSQ layer. Noise always uses X25519 DH.
|
||||
//! Migration to PQ KEMs (MlKem768, XWing) requires only config change.
|
||||
|
||||
use nym_kkt::ciphersuite::KEM;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Default PSK time-to-live (1 hour, matches psk.rs implementation).
|
||||
pub const DEFAULT_PSK_TTL_SECS: u64 = 3600;
|
||||
|
||||
/// Configuration for LP protocol.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpConfig {
|
||||
/// KEM algorithm for PSQ key encapsulation.
|
||||
/// Supported KEMs: MlKem768, McEliece
|
||||
#[serde(with = "kem_serde")]
|
||||
pub kem_algorithm: KEM,
|
||||
|
||||
/// PSK time-to-live in seconds.
|
||||
pub psk_ttl_secs: u64,
|
||||
|
||||
/// Enable KKT for authenticated key distribution.
|
||||
pub enable_kkt: bool,
|
||||
}
|
||||
|
||||
impl Default for LpConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
kem_algorithm: KEM::MlKem768,
|
||||
psk_ttl_secs: DEFAULT_PSK_TTL_SECS,
|
||||
enable_kkt: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LpConfig {
|
||||
/// Returns PSK TTL as Duration.
|
||||
pub fn psk_ttl(&self) -> Duration {
|
||||
Duration::from_secs(self.psk_ttl_secs)
|
||||
}
|
||||
}
|
||||
|
||||
mod kem_serde {
|
||||
use nym_kkt::ciphersuite::KEM;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub fn serialize<S>(kem: &KEM, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match kem {
|
||||
KEM::MlKem768 => "MlKem768",
|
||||
KEM::McEliece => "McEliece",
|
||||
KEM::X25519 => return Err(serde::ser::Error::custom("Unsupported KEM: X25519")),
|
||||
KEM::XWing => return Err(serde::ser::Error::custom("Unsupported KEM: XWing")),
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<KEM, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
match s.as_str() {
|
||||
"MlKem768" => Ok(KEM::MlKem768),
|
||||
"McEliece" => Ok(KEM::McEliece),
|
||||
"X25519" => Err(serde::de::Error::custom("Unsupported KEM: X25519")),
|
||||
"XWing" => Err(serde::de::Error::custom("Unsupported KEM: XWing")),
|
||||
_ => Err(serde::de::Error::custom(format!("Unknown KEM: {}", s))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,12 +65,12 @@ pub enum LpError {
|
||||
#[error("State machine not found for lp_id: {0}")]
|
||||
StateMachineNotFound(LpReceiverIndex),
|
||||
|
||||
// /// Ed25519 to X25519 conversion error.
|
||||
// #[error("Ed25519 key conversion error: {0}")]
|
||||
// Ed25519RecoveryError(#[from] Ed25519RecoveryError),
|
||||
#[error("attempted to create an LP responder without providing a valid KEM keys")]
|
||||
ResponderWithMissingKEMKeys,
|
||||
|
||||
#[error("attempted to create an LP mutual initiator without providing a valid KEM key")]
|
||||
PSQMutualInitiatorMissingKemKey,
|
||||
|
||||
#[error(
|
||||
"there are no known digests for remote's KEM key with {kem} KEM and {hash_function} hash function"
|
||||
)]
|
||||
|
||||
@@ -11,7 +11,6 @@ pub mod replay;
|
||||
pub mod session;
|
||||
mod session_integration;
|
||||
pub mod session_manager;
|
||||
pub mod state_machine;
|
||||
pub mod transport;
|
||||
|
||||
pub use error::LpError;
|
||||
@@ -21,9 +20,8 @@ pub use nym_kkt_ciphersuite::{
|
||||
|
||||
#[cfg(any(feature = "mock", test))]
|
||||
pub use replay::{ReceivingKeyCounterValidator, ReplayError};
|
||||
pub use session::LpSession;
|
||||
pub use session::LpTransportSession;
|
||||
pub use session_manager::SessionManager;
|
||||
pub use state_machine::LpStateMachine;
|
||||
|
||||
#[cfg(any(feature = "mock", test))]
|
||||
use nym_test_utils::helpers::u64_seeded_rng_09;
|
||||
@@ -39,8 +37,8 @@ use libcrux_psq::{Channel, IntoSession};
|
||||
|
||||
#[cfg(any(feature = "mock", test))]
|
||||
pub struct SessionsMock {
|
||||
pub initiator: LpSession,
|
||||
pub responder: LpSession,
|
||||
pub initiator: LpTransportSession,
|
||||
pub responder: LpTransportSession,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "mock", test))]
|
||||
@@ -109,17 +107,18 @@ impl SessionsMock {
|
||||
initiator_authenticator,
|
||||
responder_ecdh_pk: resp_remote.x25519_public,
|
||||
responder_pq_pk: Some(encapsulation_key),
|
||||
initiator_pq_pk: None,
|
||||
};
|
||||
|
||||
SessionsMock {
|
||||
initiator: LpSession::new(
|
||||
initiator: LpTransportSession::new(
|
||||
initiator.into_session().unwrap(),
|
||||
binding.clone(),
|
||||
receiver_index,
|
||||
1,
|
||||
)
|
||||
.unwrap(),
|
||||
responder: LpSession::new(
|
||||
responder: LpTransportSession::new(
|
||||
responder.into_session().unwrap(),
|
||||
binding,
|
||||
receiver_index,
|
||||
@@ -134,18 +133,18 @@ impl SessionsMock {
|
||||
}
|
||||
|
||||
// we just need a dummy 'valid' session for simpler tests
|
||||
pub fn mock_initiator() -> LpSession {
|
||||
pub fn mock_initiator() -> LpTransportSession {
|
||||
Self::mock_post_handshake(KEM::default()).initiator
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "mock", test))]
|
||||
pub fn sessions_for_tests() -> (LpSession, LpSession) {
|
||||
pub fn sessions_for_tests() -> (LpTransportSession, LpTransportSession) {
|
||||
let sessions = SessionsMock::mock_post_handshake(KEM::default());
|
||||
(sessions.initiator, sessions.responder)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "mock", test))]
|
||||
pub fn mock_session_for_test() -> LpSession {
|
||||
pub fn mock_session_for_test() -> LpTransportSession {
|
||||
SessionsMock::mock_initiator()
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ pub enum MalformedLpPacketError {
|
||||
#[error("provided insufficient data to fully deserialise the struct")]
|
||||
InsufficientData,
|
||||
|
||||
#[error("{0} is not a valid LpDataKind")]
|
||||
InvalidLpDataKind(u16),
|
||||
#[error("{0} is not a valid LpFrameKind value")]
|
||||
InvalidLpFrameKind(u16),
|
||||
|
||||
#[error("invalid payload size: expected {expected}, got {actual}")]
|
||||
InvalidPayloadSize { expected: usize, actual: usize },
|
||||
@@ -27,7 +27,7 @@ pub enum MalformedLpPacketError {
|
||||
}
|
||||
|
||||
impl MalformedLpPacketError {
|
||||
pub fn invalid_data_kind(message_type: u16) -> Self {
|
||||
MalformedLpPacketError::InvalidLpDataKind(message_type)
|
||||
pub fn invalid_data_kind(frame_kind: u16) -> Self {
|
||||
MalformedLpPacketError::InvalidLpFrameKind(frame_kind)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,32 +7,32 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LpMessageHeader {
|
||||
pub kind: LpMessageType,
|
||||
pub message_attributes: [u8; 14],
|
||||
pub struct LpFrameHeader {
|
||||
pub kind: LpFrameKind,
|
||||
pub frame_attributes: [u8; 14],
|
||||
}
|
||||
|
||||
impl LpMessageHeader {
|
||||
impl LpFrameHeader {
|
||||
pub const SIZE: usize = 16; // message_kind(2) + message_attributes(14)
|
||||
|
||||
pub fn new(kind: LpMessageType, message_attributes: [u8; 14]) -> Self {
|
||||
pub fn new(kind: LpFrameKind, frame_attributes: [u8; 14]) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
message_attributes,
|
||||
frame_attributes,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_no_attributes(kind: LpMessageType) -> Self {
|
||||
pub fn new_no_attributes(kind: LpFrameKind) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
message_attributes: [0; 14],
|
||||
frame_attributes: [0; 14],
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode directly into a BytesMut buffer
|
||||
pub fn encode(&self, dst: &mut BytesMut) {
|
||||
dst.put_u16_le(self.kind as u16);
|
||||
dst.put_slice(&self.message_attributes);
|
||||
dst.put_slice(&self.frame_attributes);
|
||||
}
|
||||
|
||||
pub fn parse(src: &[u8]) -> Result<Self, MalformedLpPacketError> {
|
||||
@@ -41,35 +41,35 @@ impl LpMessageHeader {
|
||||
}
|
||||
let raw_kind = u16::from_le_bytes([src[0], src[1]]);
|
||||
|
||||
let kind = LpMessageType::try_from(raw_kind)
|
||||
let kind = LpFrameKind::try_from(raw_kind)
|
||||
.map_err(|_| MalformedLpPacketError::invalid_data_kind(raw_kind))?;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let message_attributes = src[2..16].try_into().unwrap();
|
||||
Ok(Self {
|
||||
kind,
|
||||
message_attributes,
|
||||
frame_attributes: message_attributes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represent application data being sent in Transport mode
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LpMessage {
|
||||
pub header: LpMessageHeader,
|
||||
pub struct LpFrame {
|
||||
pub header: LpFrameHeader,
|
||||
pub content: Bytes,
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for LpMessage {
|
||||
impl AsRef<[u8]> for LpFrame {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.content
|
||||
}
|
||||
}
|
||||
|
||||
impl LpMessage {
|
||||
pub fn new(kind: LpMessageType, content: impl Into<Bytes>) -> Self {
|
||||
impl LpFrame {
|
||||
pub fn new(kind: LpFrameKind, content: impl Into<Bytes>) -> Self {
|
||||
Self {
|
||||
header: LpMessageHeader::new_no_attributes(kind),
|
||||
header: LpFrameHeader::new_no_attributes(kind),
|
||||
content: content.into(),
|
||||
}
|
||||
}
|
||||
@@ -81,40 +81,103 @@ impl LpMessage {
|
||||
}
|
||||
|
||||
pub fn decode(src: &[u8]) -> Result<Self, MalformedLpPacketError> {
|
||||
let header = LpMessageHeader::parse(src)?;
|
||||
let content = src[LpMessageHeader::SIZE..].to_vec().into();
|
||||
let header = LpFrameHeader::parse(src)?;
|
||||
let content = src[LpFrameHeader::SIZE..].to_vec().into();
|
||||
|
||||
Ok(Self { header, content })
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> LpMessageType {
|
||||
pub fn kind(&self) -> LpFrameKind {
|
||||
self.header.kind
|
||||
}
|
||||
|
||||
pub fn new_opaque(content: impl Into<Bytes>) -> Self {
|
||||
Self::new(LpMessageType::Opaque, content)
|
||||
Self::new(LpFrameKind::Opaque, content)
|
||||
}
|
||||
|
||||
pub fn new_registration(data: impl Into<Bytes>) -> Self {
|
||||
Self::new(LpMessageType::Registration, data)
|
||||
Self::new(LpFrameKind::Registration, data)
|
||||
}
|
||||
|
||||
pub fn new_forward(data: impl Into<Bytes>) -> Self {
|
||||
Self::new(LpMessageType::Forward, data)
|
||||
Self::new(LpFrameKind::Forward, data)
|
||||
}
|
||||
|
||||
pub fn new_stream(attrs: StreamFrameAttributes, content: impl Into<Bytes>) -> Self {
|
||||
Self {
|
||||
header: LpFrameHeader::new(LpFrameKind::Stream, attrs.encode()),
|
||||
content: content.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
LpMessageHeader::SIZE + self.content.len()
|
||||
LpFrameHeader::SIZE + self.content.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represent kind of application data being sent in Transport mode
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u16)]
|
||||
pub enum LpMessageType {
|
||||
pub enum LpFrameKind {
|
||||
Opaque = 0,
|
||||
Registration = 1,
|
||||
Forward = 2,
|
||||
Stream = 3,
|
||||
}
|
||||
|
||||
/// Message type within a `LpFrameKind::Stream` frame.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum StreamMsgType {
|
||||
/// Open a new stream. Content is optional initial data.
|
||||
Open = 0,
|
||||
/// Data on an existing stream.
|
||||
Data = 1,
|
||||
}
|
||||
|
||||
/// Parsed form of the 14-byte `frame_attributes` for `LpFrameKind::Stream`.
|
||||
///
|
||||
/// Wire layout (big-endian):
|
||||
/// ```text
|
||||
/// [0..8 ) stream_id : u64
|
||||
/// [8 ) msg_type : u8 (0 = Open, 1 = Data)
|
||||
/// [9..13) sequence_num : u32
|
||||
/// [13 ) reserved : u8
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct StreamFrameAttributes {
|
||||
pub stream_id: u64,
|
||||
pub msg_type: StreamMsgType,
|
||||
pub sequence_num: u32,
|
||||
}
|
||||
|
||||
impl StreamFrameAttributes {
|
||||
pub fn encode(&self) -> [u8; 14] {
|
||||
let mut buf = [0u8; 14];
|
||||
buf[0..8].copy_from_slice(&self.stream_id.to_be_bytes());
|
||||
buf[8] = self.msg_type as u8;
|
||||
buf[9..13].copy_from_slice(&self.sequence_num.to_be_bytes());
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn parse(attrs: &[u8; 14]) -> Result<Self, MalformedLpPacketError> {
|
||||
let stream_id = u64::from_be_bytes(attrs[0..8].try_into().unwrap());
|
||||
let msg_type = match attrs[8] {
|
||||
0 => StreamMsgType::Open,
|
||||
1 => StreamMsgType::Data,
|
||||
other => {
|
||||
return Err(MalformedLpPacketError::DeserialisationFailure(format!(
|
||||
"invalid stream msg_type: {other}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
let sequence_num = u32::from_be_bytes(attrs[9..13].try_into().unwrap());
|
||||
Ok(Self {
|
||||
stream_id,
|
||||
msg_type,
|
||||
sequence_num,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -6,12 +6,12 @@ use bytes::{BufMut, BytesMut};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
pub use error::MalformedLpPacketError;
|
||||
pub use frame::{ForwardPacketData, LpFrame};
|
||||
pub use header::{InnerHeader, LpHeader, OuterHeader};
|
||||
pub use message::{ForwardPacketData, LpMessage};
|
||||
|
||||
pub mod error;
|
||||
pub mod frame;
|
||||
pub mod header;
|
||||
pub mod message;
|
||||
pub mod replay;
|
||||
pub mod utils;
|
||||
|
||||
@@ -81,7 +81,7 @@ impl EncryptedLpPacket {
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct LpPacket {
|
||||
pub(crate) header: LpHeader,
|
||||
pub(crate) message: LpMessage,
|
||||
pub(crate) frame: LpFrame,
|
||||
}
|
||||
|
||||
impl Debug for LpPacket {
|
||||
@@ -91,16 +91,16 @@ impl Debug for LpPacket {
|
||||
}
|
||||
|
||||
impl LpPacket {
|
||||
pub fn new(header: LpHeader, message: LpMessage) -> Self {
|
||||
Self { header, message }
|
||||
pub fn new(header: LpHeader, frame: LpFrame) -> Self {
|
||||
Self { header, frame }
|
||||
}
|
||||
|
||||
pub fn message(&self) -> &LpMessage {
|
||||
&self.message
|
||||
pub fn frame(&self) -> &LpFrame {
|
||||
&self.frame
|
||||
}
|
||||
|
||||
pub fn into_message(self) -> LpMessage {
|
||||
self.message
|
||||
pub fn into_frame(self) -> LpFrame {
|
||||
self.frame
|
||||
}
|
||||
|
||||
pub fn header(&self) -> &LpHeader {
|
||||
@@ -115,6 +115,6 @@ impl LpPacket {
|
||||
|
||||
pub(crate) fn dbg_encode(&self, dst: &mut BytesMut) {
|
||||
self.header.dbg_encode(dst);
|
||||
self.message.encode(dst)
|
||||
self.frame.encode(dst)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::LpError;
|
||||
use nym_kkt::keys::EncapsulationKey;
|
||||
use nym_kkt_ciphersuite::{Ciphersuite, KEM, KEMKeyDigests};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Debug;
|
||||
@@ -41,6 +42,18 @@ impl LpLocalPeer {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn kem_key(&self, kem: KEM) -> Option<EncapsulationKey> {
|
||||
self.kem_keypairs
|
||||
.as_ref()
|
||||
.and_then(|k| k.encapsulation_key(kem))
|
||||
}
|
||||
|
||||
pub fn encoded_kem_key(&self, kem: KEM) -> Option<&[u8]> {
|
||||
self.kem_keypairs
|
||||
.as_ref()
|
||||
.and_then(|k| k.encoded_encapsulation_key(kem))
|
||||
}
|
||||
|
||||
pub fn x25519(&self) -> &Arc<DHKeyPair> {
|
||||
&self.x25519
|
||||
}
|
||||
@@ -69,7 +82,10 @@ impl Debug for LpLocalPeer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("LpLocalPeer")
|
||||
.field("ciphersuite", &self.ciphersuite)
|
||||
.field("x25519", &self.x25519.pk)
|
||||
.field(
|
||||
"x25519",
|
||||
&bs58::encode(self.x25519.pk.as_ref()).into_string(),
|
||||
)
|
||||
.field("kem_keypairs", &self.kem_keypairs)
|
||||
.finish()
|
||||
}
|
||||
@@ -127,6 +143,10 @@ impl LpRemotePeer {
|
||||
.ok_or(LpError::NoKnownKEMKeyDigests { kem, hash_function })
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn kem_key_digests(&self) -> &BTreeMap<KEM, KEMKeyDigests> {
|
||||
&self.expected_kem_key_digests
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DHPublicKey> for LpRemotePeer {
|
||||
|
||||
@@ -29,7 +29,7 @@ pub struct LpPeerConfig {
|
||||
|
||||
// Determine the hop id.
|
||||
// Should be 0 if node_initiator is true
|
||||
// Should be > 1 if is_exit is true
|
||||
// Should be > 1 && < 16 if is_exit is true
|
||||
hop_id: u8,
|
||||
|
||||
// Determine if the recipient should be an exit node
|
||||
@@ -65,6 +65,7 @@ impl LpPeerConfig {
|
||||
rng.random(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new client to exit config.
|
||||
/// Inputs:
|
||||
/// hop_id: this value must be in the range (1..=15). This function returns an error if this is not the case.
|
||||
@@ -79,6 +80,7 @@ impl LpPeerConfig {
|
||||
{
|
||||
Self::new(rng, hop_id, true, false, censorship_resistance)
|
||||
}
|
||||
|
||||
/// Creates a new client to an intermediate node config.
|
||||
/// Inputs:
|
||||
/// hop_id: this value must be in the range (1..=14). This function returns an error if this is not the case.
|
||||
@@ -130,6 +132,7 @@ impl LpPeerConfig {
|
||||
rng.random(),
|
||||
)
|
||||
}
|
||||
|
||||
fn build(
|
||||
hop_id: u8,
|
||||
is_exit: bool,
|
||||
@@ -147,6 +150,7 @@ impl LpPeerConfig {
|
||||
seed,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_checked(
|
||||
hop_id: u8,
|
||||
is_exit: bool,
|
||||
@@ -198,37 +202,37 @@ impl LpPeerConfig {
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> [u8; LP_PEER_CONFIG_SIZE] {
|
||||
let mut output_bytes: [u8; LP_PEER_CONFIG_SIZE] = [0u8; LP_PEER_CONFIG_SIZE];
|
||||
output_bytes[0..4].copy_from_slice(self.pack_config().as_slice());
|
||||
let mut output_bytes = [0u8; LP_PEER_CONFIG_SIZE];
|
||||
output_bytes[0..4].copy_from_slice(&self.pack_config());
|
||||
output_bytes[4..].copy_from_slice(&self.seed);
|
||||
output_bytes
|
||||
}
|
||||
|
||||
pub fn deserialize(bytes: &[u8]) -> Result<Self, LpError> {
|
||||
if bytes.len() != LP_PEER_CONFIG_SIZE {
|
||||
Err(LpError::DeserializationError(format!(
|
||||
return Err(LpError::DeserializationError(format!(
|
||||
"Invalid Lp Config Length ({}), expected ({})",
|
||||
bytes.len(),
|
||||
LP_PEER_CONFIG_SIZE
|
||||
)))
|
||||
} else {
|
||||
let (hop_id, is_exit, node_initiator, censorship_resistance) =
|
||||
Self::unpack_first_byte(bytes[0]);
|
||||
|
||||
let mut filler: [u8; FILLER_LEN] = [0u8; FILLER_LEN];
|
||||
filler.copy_from_slice(&bytes[CONFIG_LEN..CONFIG_LEN + FILLER_LEN]);
|
||||
|
||||
let mut seed: [u8; SEED_LEN] = [0u8; SEED_LEN];
|
||||
seed.copy_from_slice(&bytes[CONFIG_LEN + FILLER_LEN..LP_PEER_CONFIG_SIZE]);
|
||||
|
||||
Self::build_checked(
|
||||
hop_id,
|
||||
is_exit,
|
||||
node_initiator,
|
||||
censorship_resistance,
|
||||
seed,
|
||||
filler,
|
||||
)
|
||||
)));
|
||||
}
|
||||
let (hop_id, is_exit, node_initiator, censorship_resistance) =
|
||||
Self::unpack_first_byte(bytes[0]);
|
||||
|
||||
let mut filler = [0u8; FILLER_LEN];
|
||||
filler.copy_from_slice(&bytes[CONFIG_LEN..CONFIG_LEN + FILLER_LEN]);
|
||||
|
||||
let mut seed = [0u8; SEED_LEN];
|
||||
seed.copy_from_slice(&bytes[CONFIG_LEN + FILLER_LEN..LP_PEER_CONFIG_SIZE]);
|
||||
|
||||
Self::build_checked(
|
||||
hop_id,
|
||||
is_exit,
|
||||
node_initiator,
|
||||
censorship_resistance,
|
||||
seed,
|
||||
filler,
|
||||
)
|
||||
}
|
||||
|
||||
fn pack_config(&self) -> [u8; 4] {
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::psq::{
|
||||
};
|
||||
use crate::session::PersistentSessionBinding;
|
||||
use crate::transport::traits::LpHandshakeChannel;
|
||||
use crate::{LpError, LpSession};
|
||||
use crate::{LpError, LpTransportSession};
|
||||
use libcrux_psq::handshake::RegistrationInitiator;
|
||||
use libcrux_psq::handshake::builders::{
|
||||
CiphersuiteBuilder, InitiatorCiphersuite, PrincipalBuilder,
|
||||
@@ -24,9 +24,31 @@ use nym_kkt::message::{KKTRequest, KKTResponse};
|
||||
use rand09::SeedableRng;
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum HandshakeMode {
|
||||
// Client <> Entry
|
||||
OneWayEntry,
|
||||
|
||||
// Client <> Exit
|
||||
OneWayExit,
|
||||
|
||||
// Entry <> Exit
|
||||
MutualInternode,
|
||||
// in the future more variants will be supported (such as individual mix hops)
|
||||
}
|
||||
|
||||
impl HandshakeMode {
|
||||
pub fn is_mutual(&self) -> bool {
|
||||
matches!(self, HandshakeMode::MutualInternode)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PSQHandshakeStateInitiator<'a, S> {
|
||||
pub(super) inner_state: PSQHandshakeState<'a, S>,
|
||||
pub(super) initiator_data: InitiatorData,
|
||||
|
||||
/// The mode of the handshake (mutual node-node, client-entry, entry-exit)
|
||||
pub(super) mode: HandshakeMode,
|
||||
}
|
||||
|
||||
pub(crate) fn build_psq_principal<R>(
|
||||
@@ -77,6 +99,25 @@ impl<'a, S> PSQHandshakeStateInitiator<'a, S>
|
||||
where
|
||||
S: LpHandshakeChannel + Unpin,
|
||||
{
|
||||
fn lp_peer_config<R>(&self, rng: &mut R) -> Result<LpPeerConfig, LpError>
|
||||
where
|
||||
R: rand09::CryptoRng,
|
||||
{
|
||||
// for now we don't support censorship resistance flag
|
||||
let censorship_resistance = false;
|
||||
|
||||
match self.mode {
|
||||
HandshakeMode::OneWayEntry => Ok(LpPeerConfig::new_client_to_entry(
|
||||
rng,
|
||||
censorship_resistance,
|
||||
)),
|
||||
HandshakeMode::OneWayExit => {
|
||||
LpPeerConfig::new_client_to_exit(rng, 1, censorship_resistance)
|
||||
}
|
||||
HandshakeMode::MutualInternode => LpPeerConfig::new_node_to_node(rng),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to send KKT request to begin the handshake
|
||||
async fn send_kkt_request(&mut self, request: KKTRequest) -> Result<(), LpError> {
|
||||
let kem = self.inner_state.local_peer.ciphersuite.kem();
|
||||
@@ -103,7 +144,7 @@ where
|
||||
Ok(resp.into())
|
||||
}
|
||||
|
||||
pub async fn complete_handshake(self) -> Result<LpSession, LpError>
|
||||
pub async fn complete_handshake(self) -> Result<LpTransportSession, LpError>
|
||||
where
|
||||
S: LpHandshakeChannel + Unpin,
|
||||
{
|
||||
@@ -111,7 +152,10 @@ where
|
||||
self.complete_handshake_with_rng(&mut rng).await
|
||||
}
|
||||
|
||||
pub async fn complete_handshake_with_rng<R>(mut self, rng: &mut R) -> Result<LpSession, LpError>
|
||||
pub async fn complete_handshake_with_rng<R>(
|
||||
mut self,
|
||||
rng: &mut R,
|
||||
) -> Result<LpTransportSession, LpError>
|
||||
where
|
||||
S: LpHandshakeChannel + Unpin,
|
||||
R: rand09::CryptoRng,
|
||||
@@ -119,7 +163,7 @@ where
|
||||
let ciphersuite = self.inner_state.local_peer.ciphersuite();
|
||||
let kem = ciphersuite.kem();
|
||||
|
||||
let lp_peer_config = LpPeerConfig::new_client_to_entry(rng, false);
|
||||
let lp_peer_config = self.lp_peer_config(rng)?;
|
||||
|
||||
// 1. retrieve the expected kem key hash. if we don't know it,
|
||||
let dir_hash = self
|
||||
@@ -128,16 +172,34 @@ where
|
||||
.expected_kem_key_hash(ciphersuite)?;
|
||||
|
||||
// 2. prepare and send KKT request
|
||||
let (mut initiator, kkt_request) = KKTInitiator::generate_one_way_request(
|
||||
rng,
|
||||
ciphersuite,
|
||||
self.initiator_data.remote_peer.x25519(),
|
||||
&dir_hash,
|
||||
self.initiator_data.protocol_version,
|
||||
Some(Vec::from(lp_peer_config.serialize())),
|
||||
)?;
|
||||
// derive the receiver index from the request
|
||||
// let receiver_index = kkt_request
|
||||
let (mut initiator, kkt_request) = if self.mode.is_mutual() {
|
||||
// this has been verified when setting the mutual flag
|
||||
let Some(local_encapsulation_key) = self.inner_state.local_peer.encoded_kem_key(kem)
|
||||
else {
|
||||
return Err(LpError::PSQMutualInitiatorMissingKemKey);
|
||||
};
|
||||
|
||||
KKTInitiator::generate_mutual_request(
|
||||
rng,
|
||||
ciphersuite,
|
||||
local_encapsulation_key,
|
||||
self.initiator_data.remote_peer.x25519(),
|
||||
&dir_hash,
|
||||
self.initiator_data.protocol_version,
|
||||
Some(Vec::from(lp_peer_config.serialize())),
|
||||
)?
|
||||
} else {
|
||||
KKTInitiator::generate_one_way_request(
|
||||
rng,
|
||||
ciphersuite,
|
||||
self.initiator_data.remote_peer.x25519(),
|
||||
&dir_hash,
|
||||
self.initiator_data.protocol_version,
|
||||
Some(Vec::from(lp_peer_config.serialize())),
|
||||
)?
|
||||
};
|
||||
|
||||
let init_kem_key = self.inner_state.local_peer.kem_key(kem);
|
||||
|
||||
debug!("sending KKT request");
|
||||
self.send_kkt_request(kkt_request).await?;
|
||||
@@ -154,7 +216,7 @@ where
|
||||
let conn = self.inner_state.connection;
|
||||
|
||||
// note: the clone is cheap due to internal Arcs
|
||||
let encapsulation_key = response.encapsulation_key.clone();
|
||||
let resp_encapsulation_key = response.encapsulation_key.clone();
|
||||
|
||||
// build the PSQ initiator
|
||||
let initiator_ciphersuite = build_psq_ciphersuite(
|
||||
@@ -191,17 +253,18 @@ where
|
||||
|
||||
let initiator_authenticator = Authenticator::Dh(self.inner_state.local_peer.x25519().pk);
|
||||
|
||||
let receiver_index =
|
||||
lp_peer_config.derive_receiver_index(&initiator_authenticator, &encapsulation_key)?;
|
||||
let receiver_index = lp_peer_config
|
||||
.derive_receiver_index(&initiator_authenticator, &resp_encapsulation_key)?;
|
||||
|
||||
let binding = PersistentSessionBinding {
|
||||
initiator_authenticator,
|
||||
responder_ecdh_pk: self.initiator_data.remote_peer.x25519_public,
|
||||
responder_pq_pk: Some(encapsulation_key),
|
||||
responder_pq_pk: Some(resp_encapsulation_key),
|
||||
initiator_pq_pk: init_kem_key,
|
||||
};
|
||||
|
||||
let psq_session = psq_initiator.into_session()?;
|
||||
LpSession::new(psq_session, binding, receiver_index, protocol)
|
||||
LpTransportSession::new(psq_session, binding, receiver_index, protocol)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +281,7 @@ mod tests {
|
||||
use nym_test_utils::helpers::{DeterministicRng09Send, u64_seeded_rng_09};
|
||||
use nym_test_utils::mocks::async_read_write::MockIOStream;
|
||||
use nym_test_utils::traits::{Leak, Timeboxed};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn initiator_test_plain() -> anyhow::Result<()> {
|
||||
@@ -225,6 +289,8 @@ mod tests {
|
||||
let conn_init = MockIOStream::default();
|
||||
let conn_resp = conn_init.try_get_remote_handle();
|
||||
|
||||
let dir_hash_init = BTreeMap::new();
|
||||
|
||||
// leak the connections (JUST FOR THE PURPOSE OF THIS TEST!)
|
||||
// so they'd get 'static lifetime
|
||||
let conn_init = conn_init.leak();
|
||||
@@ -238,8 +304,8 @@ mod tests {
|
||||
resp.ciphersuite = ciphersuite;
|
||||
let initiator_data = InitiatorData::new(1, resp_remote);
|
||||
|
||||
let handshake_init =
|
||||
PSQHandshakeState::new(conn_init, init).as_initiator(initiator_data);
|
||||
let handshake_init = PSQHandshakeState::new(conn_init, init)
|
||||
.as_initiator(initiator_data, HandshakeMode::OneWayEntry)?;
|
||||
|
||||
let mut init_rng = DeterministicRng09Send::new(u64_seeded_rng_09(1));
|
||||
|
||||
@@ -264,6 +330,7 @@ mod tests {
|
||||
let kkt_responder = KKTResponder::new(
|
||||
responder_x25519_keypair,
|
||||
resp_keys,
|
||||
&dir_hash_init,
|
||||
&supported_hash,
|
||||
&supported_sigs,
|
||||
&[1],
|
||||
@@ -337,4 +404,126 @@ mod tests {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn initiator_test_plain_mutual() -> anyhow::Result<()> {
|
||||
for kem in KEM::iter() {
|
||||
let conn_init = MockIOStream::default();
|
||||
let conn_resp = conn_init.try_get_remote_handle();
|
||||
|
||||
// leak the connections (JUST FOR THE PURPOSE OF THIS TEST!)
|
||||
// so they'd get 'static lifetime
|
||||
let conn_init = conn_init.leak();
|
||||
let conn_resp = conn_resp.leak();
|
||||
|
||||
let (mut init, mut resp) = mock_peers();
|
||||
let resp_remote = resp.as_remote();
|
||||
let init_remote = init.as_remote();
|
||||
let dir_hash_init = init_remote.expected_kem_key_digests;
|
||||
|
||||
let ciphersuite = Ciphersuite::default().with_kem(kem);
|
||||
init.ciphersuite = ciphersuite;
|
||||
resp.ciphersuite = ciphersuite;
|
||||
let initiator_data = InitiatorData::new(1, resp_remote);
|
||||
|
||||
let handshake_init = PSQHandshakeState::new(conn_init, init)
|
||||
.as_initiator(initiator_data, HandshakeMode::MutualInternode)?;
|
||||
|
||||
let mut init_rng = DeterministicRng09Send::new(u64_seeded_rng_09(1));
|
||||
|
||||
let init_fut = tokio::spawn(async move {
|
||||
handshake_init
|
||||
.complete_handshake_with_rng(&mut init_rng)
|
||||
.timeboxed()
|
||||
.await
|
||||
});
|
||||
|
||||
// responder:
|
||||
let supported_sigs = [SignatureScheme::Ed25519];
|
||||
let supported_hash = [
|
||||
HashFunction::Blake3,
|
||||
HashFunction::Shake256,
|
||||
HashFunction::Shake128,
|
||||
HashFunction::SHA256,
|
||||
];
|
||||
let resp_keys = resp.kem_keypairs.as_ref().unwrap();
|
||||
let responder_x25519_keypair = resp.x25519();
|
||||
|
||||
let kkt_responder = KKTResponder::new(
|
||||
responder_x25519_keypair,
|
||||
resp_keys,
|
||||
&dir_hash_init,
|
||||
&supported_hash,
|
||||
&supported_sigs,
|
||||
&[1],
|
||||
)?;
|
||||
|
||||
// 1. read KKT request
|
||||
let raw_kkt_req: handshake_message::KKTRequest = conn_resp
|
||||
.receive_handshake_message(
|
||||
KKTRequest::size_excluding_payload(KKTMode::Mutual, kem) + LP_PEER_CONFIG_SIZE,
|
||||
)
|
||||
.timeboxed()
|
||||
.await??;
|
||||
let req = raw_kkt_req.into();
|
||||
|
||||
// 2. process
|
||||
let processed_req = kkt_responder.process_request(req, LP_PEER_CONFIG_SIZE)?;
|
||||
conn_resp
|
||||
.send_handshake_message::<handshake_message::KKTResponse>(
|
||||
processed_req.response.into(),
|
||||
kem,
|
||||
)
|
||||
.timeboxed()
|
||||
.await??;
|
||||
|
||||
// 3. read PSQ req
|
||||
let responder_ciphersuite = responder::build_psq_ciphersuite(&resp, kem)?;
|
||||
let mut responder =
|
||||
responder::build_psq_principal(rand09::rng(), 1, responder_ciphersuite)?;
|
||||
let response_len = psq_msg1_size(kem);
|
||||
|
||||
let msg: PSQMsg1 = conn_resp
|
||||
.receive_handshake_message(response_len)
|
||||
.timeboxed()
|
||||
.await??;
|
||||
responder.read_message(&msg, &mut []).unwrap();
|
||||
|
||||
// 4 send PSQ response
|
||||
let mut buf = vec![0u8; PSQ_MSG2_SIZE];
|
||||
let n = responder.write_message(&[], &mut buf).unwrap();
|
||||
assert_eq!(n, buf.len());
|
||||
let msg = PSQMsg2::new(buf);
|
||||
conn_resp
|
||||
.send_handshake_message(msg, kem)
|
||||
.timeboxed()
|
||||
.await??;
|
||||
|
||||
assert!(responder.is_handshake_finished());
|
||||
|
||||
let mut session_init = init_fut.await???;
|
||||
|
||||
let mut r_transport = responder.into_session().unwrap();
|
||||
|
||||
// test serialization, deserialization
|
||||
let channel_i = session_init.active_transport();
|
||||
let mut channel_r = r_transport.transport_channel().unwrap();
|
||||
|
||||
assert_eq!(channel_i.identifier(), channel_r.identifier());
|
||||
|
||||
let app_data_i = b"Derived session hey".as_slice();
|
||||
let app_data_r = b"Derived session ho".as_slice();
|
||||
|
||||
let ct_i = encrypt_data(app_data_i, channel_i)?;
|
||||
let pt_r = decrypt_data(&ct_i, &mut channel_r)?;
|
||||
|
||||
assert_eq!(app_data_i, pt_r);
|
||||
|
||||
let ct_r = encrypt_data(app_data_r, &mut channel_r)?;
|
||||
let pt_i = decrypt_data(&ct_r, channel_i)?;
|
||||
|
||||
assert_eq!(app_data_r, pt_i);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
use crate::packet::version;
|
||||
use crate::peer::{LpLocalPeer, LpRemotePeer};
|
||||
use crate::transport::traits::LpHandshakeChannel;
|
||||
use nym_kkt_ciphersuite::{HashFunction, IntoEnumIterator, KEM, SignatureScheme};
|
||||
use nym_kkt_ciphersuite::{HashFunction, IntoEnumIterator, KEM, KEMKeyDigests, SignatureScheme};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub(crate) mod handshake_message;
|
||||
mod helpers;
|
||||
pub mod initiator;
|
||||
pub mod responder;
|
||||
|
||||
use crate::LpError;
|
||||
use crate::psq::initiator::HandshakeMode;
|
||||
pub use initiator::PSQHandshakeStateInitiator;
|
||||
pub use responder::PSQHandshakeStateResponder;
|
||||
|
||||
@@ -68,6 +71,19 @@ pub struct ResponderData {
|
||||
|
||||
/// List of supported outer (LP) protocol version by this Responder
|
||||
pub supported_outer_protocol_versions: Vec<u8>,
|
||||
|
||||
/// Expected KEM hashes of the initiator.
|
||||
/// It is only expected to be populated for the mutual mode of the KKT.
|
||||
/// Otherwise the map is empty.
|
||||
pub initiator_kem_hashes: BTreeMap<KEM, KEMKeyDigests>,
|
||||
}
|
||||
|
||||
impl ResponderData {
|
||||
#[must_use]
|
||||
pub fn with_initiator_kem_hashes(mut self, kem_hashes: BTreeMap<KEM, KEMKeyDigests>) -> Self {
|
||||
self.initiator_kem_hashes = kem_hashes;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ResponderData {
|
||||
@@ -77,6 +93,7 @@ impl Default for ResponderData {
|
||||
supported_hash_functions: HashFunction::iter().collect(),
|
||||
supported_signature_schemes: SignatureScheme::iter().collect(),
|
||||
supported_outer_protocol_versions: vec![version::CURRENT],
|
||||
initiator_kem_hashes: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,11 +109,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_initiator(self, initiator_data: InitiatorData) -> PSQHandshakeStateInitiator<'a, S> {
|
||||
PSQHandshakeStateInitiator {
|
||||
pub fn as_initiator(
|
||||
self,
|
||||
initiator_data: InitiatorData,
|
||||
mode: HandshakeMode,
|
||||
) -> Result<PSQHandshakeStateInitiator<'a, S>, LpError> {
|
||||
if mode.is_mutual() && self.local_peer.kem_keypairs.is_none() {
|
||||
return Err(LpError::PSQMutualInitiatorMissingKemKey);
|
||||
}
|
||||
|
||||
Ok(PSQHandshakeStateInitiator {
|
||||
initiator_data,
|
||||
inner_state: self,
|
||||
}
|
||||
mode,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn as_responder(self, responder_data: ResponderData) -> PSQHandshakeStateResponder<'a, S> {
|
||||
@@ -124,6 +150,7 @@ mod tests {
|
||||
};
|
||||
use nym_test_utils::mocks::async_read_write::MockIOStream;
|
||||
use nym_test_utils::traits::{Leak, TimeboxedSpawnable};
|
||||
use std::collections::BTreeMap;
|
||||
use tokio::join;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -143,8 +170,10 @@ mod tests {
|
||||
resp.ciphersuite = ciphersuite;
|
||||
let resp_remote = resp.as_remote();
|
||||
|
||||
let handshake_init = PSQHandshakeState::new(conn_init, init)
|
||||
.as_initiator(InitiatorData::new(1, resp_remote));
|
||||
let handshake_init = PSQHandshakeState::new(conn_init, init).as_initiator(
|
||||
InitiatorData::new(1, resp_remote),
|
||||
HandshakeMode::OneWayEntry,
|
||||
)?;
|
||||
let handshake_resp =
|
||||
PSQHandshakeState::new(conn_resp, resp).as_responder(ResponderData::default());
|
||||
|
||||
@@ -197,6 +226,82 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn e2e_psq_mutual_handshake() -> anyhow::Result<()> {
|
||||
for kem in KEM::iter() {
|
||||
let conn_init = MockIOStream::default();
|
||||
let conn_resp = conn_init.try_get_remote_handle();
|
||||
|
||||
// leak the connections (JUST FOR THE PURPOSE OF THIS TEST!)
|
||||
// so they'd get 'static lifetime
|
||||
let conn_init = conn_init.leak();
|
||||
let conn_resp = conn_resp.leak();
|
||||
let ciphersuite = Ciphersuite::default().with_kem(kem);
|
||||
|
||||
let (mut init, mut resp) = mock_peers();
|
||||
init.ciphersuite = ciphersuite;
|
||||
resp.ciphersuite = ciphersuite;
|
||||
let resp_remote = resp.as_remote();
|
||||
let init_remote = init.as_remote();
|
||||
|
||||
let handshake_init = PSQHandshakeState::new(conn_init, init).as_initiator(
|
||||
InitiatorData::new(1, resp_remote),
|
||||
HandshakeMode::MutualInternode,
|
||||
)?;
|
||||
let handshake_resp = PSQHandshakeState::new(conn_resp, resp).as_responder(
|
||||
ResponderData::default()
|
||||
.with_initiator_kem_hashes(init_remote.expected_kem_key_digests),
|
||||
);
|
||||
|
||||
let init_rng = DeterministicRng09Send::new(u64_seeded_rng_09(1));
|
||||
let resp_rng = DeterministicRng09Send::new(u64_seeded_rng_09(2));
|
||||
|
||||
// similarly leak the rngs to get the static lifetimes
|
||||
let init_rng = init_rng.leak();
|
||||
let resp_rng = resp_rng.leak();
|
||||
|
||||
let init_fut = handshake_init
|
||||
.complete_handshake_with_rng(init_rng)
|
||||
.spawn_timeboxed();
|
||||
let resp_fut = handshake_resp
|
||||
.complete_handshake_with_rng(resp_rng)
|
||||
.spawn_timeboxed();
|
||||
|
||||
let (session_init, session_resp) = join!(init_fut, resp_fut);
|
||||
|
||||
let mut session_init = session_init???;
|
||||
let mut session_resp = session_resp???;
|
||||
|
||||
assert_eq!(session_init.receiver_index(), session_resp.receiver_index());
|
||||
|
||||
assert_eq!(
|
||||
session_init.session_identifier(),
|
||||
session_resp.session_identifier()
|
||||
);
|
||||
|
||||
// test serialization, deserialization
|
||||
let channel_i = session_init.active_transport();
|
||||
let channel_r = session_resp.active_transport();
|
||||
|
||||
assert_eq!(channel_i.identifier(), channel_r.identifier());
|
||||
|
||||
let app_data_i = b"Derived session hey".as_slice();
|
||||
let app_data_r = b"Derived session ho".as_slice();
|
||||
|
||||
let ct_i = encrypt_data(app_data_i, channel_i)?;
|
||||
let pt_r = decrypt_data(&ct_i, channel_r)?;
|
||||
|
||||
assert_eq!(app_data_i, pt_r);
|
||||
|
||||
let ct_r = encrypt_data(app_data_r, channel_r)?;
|
||||
let pt_i = decrypt_data(&ct_r, channel_i)?;
|
||||
|
||||
assert_eq!(app_data_r, pt_i);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// plain test without any wrappers
|
||||
#[test]
|
||||
fn e2e_test_plain() {
|
||||
@@ -209,6 +314,7 @@ mod tests {
|
||||
init.ciphersuite = Ciphersuite::default().with_kem(kem);
|
||||
let resp_remote = resp.as_remote();
|
||||
let dir_hash = resp_remote.expected_kem_key_hash(init.ciphersuite).unwrap();
|
||||
let dir_hash_init = BTreeMap::new();
|
||||
|
||||
let resp_keys = resp.kem_keypairs.as_ref().unwrap();
|
||||
let responder_x25519_keypair = resp.x25519();
|
||||
@@ -223,6 +329,7 @@ mod tests {
|
||||
let kkt_responder = KKTResponder::new(
|
||||
responder_x25519_keypair,
|
||||
resp_keys,
|
||||
&dir_hash_init,
|
||||
&supported_hash,
|
||||
&supported_sigs,
|
||||
&[protocol_version],
|
||||
@@ -369,4 +476,188 @@ mod tests {
|
||||
assert_eq!(app_data_r, pt_i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e2e_test_plain_mutual() {
|
||||
let mut rng = deterministic_rng_09();
|
||||
|
||||
for kem in KEM::iter() {
|
||||
// SETUP START:
|
||||
let protocol_version = 1;
|
||||
let (mut init, resp) = mock_peers();
|
||||
init.ciphersuite = Ciphersuite::default().with_kem(kem);
|
||||
|
||||
let init_remote = init.as_remote();
|
||||
let resp_remote = resp.as_remote();
|
||||
|
||||
let dir_hash_init = init_remote.expected_kem_key_digests.clone();
|
||||
let dir_hash_resp = resp_remote.expected_kem_key_hash(init.ciphersuite).unwrap();
|
||||
|
||||
let resp_keys = resp.kem_keypairs.as_ref().unwrap();
|
||||
let responder_x25519_keypair = resp.x25519();
|
||||
|
||||
let init_keys = init.kem_keypairs.as_ref().unwrap();
|
||||
let init_kem = init_keys.encoded_encapsulation_key(kem).unwrap();
|
||||
|
||||
let supported_sigs = [SignatureScheme::Ed25519];
|
||||
let supported_hash = [
|
||||
HashFunction::Blake3,
|
||||
HashFunction::Shake256,
|
||||
HashFunction::Shake128,
|
||||
HashFunction::SHA256,
|
||||
];
|
||||
let kkt_responder = KKTResponder::new(
|
||||
responder_x25519_keypair,
|
||||
resp_keys,
|
||||
&dir_hash_init,
|
||||
&supported_hash,
|
||||
&supported_sigs,
|
||||
&[protocol_version],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// SETUP END
|
||||
|
||||
let lp_peer_config = LpPeerConfig::new_client_to_entry(&mut rng, false);
|
||||
|
||||
// OneWay - MlKem
|
||||
let (mut initiator, request) = KKTInitiator::generate_mutual_request(
|
||||
&mut rng,
|
||||
init.ciphersuite,
|
||||
init_kem,
|
||||
&responder_x25519_keypair.pk,
|
||||
&dir_hash_resp,
|
||||
protocol_version,
|
||||
Some(Vec::from(lp_peer_config.serialize())),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let processed_req = kkt_responder
|
||||
.process_request(request, LP_PEER_CONFIG_SIZE)
|
||||
.unwrap();
|
||||
|
||||
let init_key = processed_req.remote_encapsulation_key.unwrap();
|
||||
assert_eq!(init_key.as_bytes(), init_kem);
|
||||
|
||||
let response = initiator
|
||||
.process_response(processed_req.response, 0)
|
||||
.unwrap();
|
||||
let encapsulation_key = response.encapsulation_key;
|
||||
|
||||
let mut payload_buf_responder = vec![0u8; 4096];
|
||||
let mut payload_buf_initiator = vec![0u8; 4096];
|
||||
|
||||
let initiator_ciphersuite =
|
||||
initiator::build_psq_ciphersuite(&init, &resp_remote, &encapsulation_key).unwrap();
|
||||
let mut initiator = initiator::build_psq_principal(
|
||||
rand09::rng(),
|
||||
protocol_version,
|
||||
initiator_ciphersuite,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let responder_ciphersuite = responder::build_psq_ciphersuite(&resp, kem).unwrap();
|
||||
let mut responder = responder::build_psq_principal(
|
||||
rand09::rng(),
|
||||
protocol_version,
|
||||
responder_ciphersuite,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Send first message
|
||||
let mut buf = vec![0u8; psq_msg1_size(kem)];
|
||||
let len_i = initiator.write_message(&[], &mut buf).unwrap();
|
||||
assert_eq!(len_i, buf.len());
|
||||
|
||||
// Read first message
|
||||
let (_, _) = responder
|
||||
.read_message(&buf, &mut payload_buf_responder)
|
||||
.unwrap();
|
||||
|
||||
// Get the authenticator out here, so we can deserialize the session later.
|
||||
let Some(initiator_authenticator) = responder.initiator_authenticator() else {
|
||||
panic!("No initiator authenticator found")
|
||||
};
|
||||
|
||||
// Respond
|
||||
let mut buf = [0u8; PSQ_MSG2_SIZE];
|
||||
let len_r = responder.write_message(&[], &mut buf).unwrap();
|
||||
assert_eq!(len_r, buf.len());
|
||||
|
||||
// Finalize on registration initiator
|
||||
let (len_i_deserialized, _) = initiator
|
||||
.read_message(&buf, &mut payload_buf_initiator)
|
||||
.unwrap();
|
||||
|
||||
// We read the same amount of data.
|
||||
assert_eq!(len_r, len_i_deserialized);
|
||||
|
||||
// Ready for transport mode
|
||||
assert!(initiator.is_handshake_finished());
|
||||
assert!(responder.is_handshake_finished());
|
||||
|
||||
let i_transport = initiator.into_session().unwrap();
|
||||
let r_transport = responder.into_session().unwrap();
|
||||
|
||||
// test serialization, deserialization
|
||||
let mut session_storage = vec![0u8; 4096];
|
||||
i_transport
|
||||
.serialize(
|
||||
&mut session_storage,
|
||||
SessionBinding {
|
||||
initiator_authenticator: &Authenticator::Dh(init.x25519().pk),
|
||||
responder_ecdh_pk: &responder_x25519_keypair.pk,
|
||||
responder_pq_pk: Some(encapsulation_key.as_pq_encapsulation_key()),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let mut i_transport = Session::deserialize(
|
||||
&session_storage,
|
||||
SessionBinding {
|
||||
initiator_authenticator: &Authenticator::Dh(init.x25519().pk),
|
||||
responder_ecdh_pk: &responder_x25519_keypair.pk,
|
||||
responder_pq_pk: Some(encapsulation_key.as_pq_encapsulation_key()),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
r_transport
|
||||
.serialize(
|
||||
&mut session_storage,
|
||||
SessionBinding {
|
||||
initiator_authenticator: &initiator_authenticator,
|
||||
responder_ecdh_pk: &responder_x25519_keypair.pk,
|
||||
responder_pq_pk: Some(encapsulation_key.as_pq_encapsulation_key()),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let mut r_transport = Session::deserialize(
|
||||
&session_storage,
|
||||
SessionBinding {
|
||||
initiator_authenticator: &initiator_authenticator,
|
||||
responder_ecdh_pk: &responder_x25519_keypair.pk,
|
||||
responder_pq_pk: Some(encapsulation_key.as_pq_encapsulation_key()),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut channel_i = i_transport.transport_channel().unwrap();
|
||||
let mut channel_r = r_transport.transport_channel().unwrap();
|
||||
|
||||
assert_eq!(channel_i.identifier(), channel_r.identifier());
|
||||
|
||||
let app_data_i = b"Derived session hey".as_slice();
|
||||
let app_data_r = b"Derived session ho".as_slice();
|
||||
|
||||
let ct_i = encrypt_data(app_data_i, &mut channel_i).unwrap();
|
||||
let pt_r = decrypt_data(&ct_i, &mut channel_r).unwrap();
|
||||
|
||||
assert_eq!(app_data_i, pt_r);
|
||||
|
||||
let ct_r = encrypt_data(app_data_r, &mut channel_r).unwrap();
|
||||
let pt_i = decrypt_data(&ct_r, &mut channel_i).unwrap();
|
||||
|
||||
assert_eq!(app_data_r, pt_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::psq::{
|
||||
};
|
||||
use crate::session::PersistentSessionBinding;
|
||||
use crate::transport::traits::{HandshakeMessage, LpHandshakeChannel};
|
||||
use crate::{LpError, LpSession};
|
||||
use crate::{LpError, LpTransportSession};
|
||||
use libcrux_psq::handshake::Responder;
|
||||
use libcrux_psq::handshake::builders::{
|
||||
CiphersuiteBuilder, PrincipalBuilder, ResponderCiphersuite,
|
||||
@@ -77,12 +77,14 @@ impl<'a, S> PSQHandshakeStateResponder<'a, S>
|
||||
where
|
||||
S: LpHandshakeChannel + Unpin,
|
||||
{
|
||||
/// Attempt to receive a KKT request from a one-way client
|
||||
async fn receive_one_way_kkt_request(&mut self) -> Result<KKTRequest, LpError> {
|
||||
let packet_len = KKTRequest::size_excluding_payload(
|
||||
KKTMode::OneWay,
|
||||
self.inner_state.local_peer.ciphersuite.kem(),
|
||||
) + LP_PEER_CONFIG_SIZE;
|
||||
async fn receive_kkt_request(&mut self, mode: KKTMode) -> Result<KKTRequest, LpError> {
|
||||
let packet_len =
|
||||
KKTRequest::size_excluding_payload(mode, self.inner_state.local_peer.ciphersuite.kem())
|
||||
+ LP_PEER_CONFIG_SIZE;
|
||||
|
||||
// TODO: we have an issue here: if initiator sends us a KEM key of different type
|
||||
// than our ciphersuite, we will fail to receive it.
|
||||
// Surely this won't blow up in our faces later... right?
|
||||
|
||||
let req = self
|
||||
.inner_state
|
||||
@@ -93,6 +95,16 @@ where
|
||||
Ok(req.into())
|
||||
}
|
||||
|
||||
/// Attempt to receive a KKT request from a one-way client
|
||||
async fn receive_one_way_kkt_request(&mut self) -> Result<KKTRequest, LpError> {
|
||||
Self::receive_kkt_request(self, KKTMode::OneWay).await
|
||||
}
|
||||
|
||||
/// Attempt to receive a KKT request from a mutual client
|
||||
async fn receive_mutual_kkt_request(&mut self) -> Result<KKTRequest, LpError> {
|
||||
Self::receive_kkt_request(self, KKTMode::Mutual).await
|
||||
}
|
||||
|
||||
/// Attempt to process the received KKT request
|
||||
fn process_kkt_request(&self, kkt_request: KKTRequest) -> Result<ProcessedKKTRequest, LpError> {
|
||||
let kem_keys = &self
|
||||
@@ -105,6 +117,7 @@ where
|
||||
let processed_req = KKTResponder::new(
|
||||
&self.inner_state.local_peer.x25519,
|
||||
kem_keys,
|
||||
&self.responder_data.initiator_kem_hashes,
|
||||
&self.responder_data.supported_hash_functions,
|
||||
&self.responder_data.supported_signature_schemes,
|
||||
&self.responder_data.supported_outer_protocol_versions,
|
||||
@@ -133,7 +146,7 @@ where
|
||||
Ok(msg.into_bytes())
|
||||
}
|
||||
|
||||
pub async fn complete_handshake(self) -> Result<LpSession, LpError>
|
||||
pub async fn complete_handshake(self) -> Result<LpTransportSession, LpError>
|
||||
where
|
||||
S: LpHandshakeChannel + Unpin,
|
||||
{
|
||||
@@ -141,17 +154,27 @@ where
|
||||
self.complete_handshake_with_rng(&mut rng).await
|
||||
}
|
||||
|
||||
pub async fn complete_handshake_with_rng<R>(mut self, rng: &mut R) -> Result<LpSession, LpError>
|
||||
pub async fn complete_handshake_with_rng<R>(
|
||||
mut self,
|
||||
rng: &mut R,
|
||||
) -> Result<LpTransportSession, LpError>
|
||||
where
|
||||
S: LpHandshakeChannel + Unpin,
|
||||
R: rand09::CryptoRng,
|
||||
{
|
||||
// 1. receive and process KKTRequest
|
||||
let kkt_request = self.receive_one_way_kkt_request().await?;
|
||||
let kkt_request = if self.responder_data.initiator_kem_hashes.is_empty() {
|
||||
debug!("expecting one way KKT request");
|
||||
self.receive_one_way_kkt_request().await?
|
||||
} else {
|
||||
debug!("expecting mutual KKT request");
|
||||
self.receive_mutual_kkt_request().await?
|
||||
};
|
||||
debug!("received KKT request");
|
||||
|
||||
let processed_req = self.process_kkt_request(kkt_request)?;
|
||||
let kem = processed_req.requested_kem;
|
||||
let init_kem = processed_req.remote_encapsulation_key;
|
||||
|
||||
let lp_peer_config = LpPeerConfig::deserialize(&processed_req.request_payload)?;
|
||||
|
||||
@@ -205,10 +228,11 @@ where
|
||||
initiator_authenticator,
|
||||
responder_ecdh_pk: self.inner_state.local_peer.x25519().pk,
|
||||
responder_pq_pk: Some(kem_key),
|
||||
initiator_pq_pk: init_kem,
|
||||
};
|
||||
|
||||
let psq_session = psq_responder.into_session()?;
|
||||
LpSession::new(
|
||||
LpTransportSession::new(
|
||||
psq_session,
|
||||
binding,
|
||||
receiver_index,
|
||||
@@ -348,4 +372,124 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn responder_test_plain_mutual() -> anyhow::Result<()> {
|
||||
for kem in KEM::iter() {
|
||||
let conn_init = MockIOStream::default();
|
||||
let conn_resp = conn_init.try_get_remote_handle();
|
||||
|
||||
// SETUP START:
|
||||
// leak the connections (JUST FOR THE PURPOSE OF THIS TEST!)
|
||||
// so they'd get 'static lifetime
|
||||
let conn_init = conn_init.leak();
|
||||
let conn_resp = conn_resp.leak();
|
||||
|
||||
let (mut init, mut resp) = mock_peers();
|
||||
let resp_remote = resp.as_remote();
|
||||
let init_remote = init.as_remote();
|
||||
|
||||
let ciphersuite = Ciphersuite::default().with_kem(kem);
|
||||
init.ciphersuite = ciphersuite;
|
||||
resp.ciphersuite = ciphersuite;
|
||||
|
||||
let responder_data = ResponderData::default()
|
||||
.with_initiator_kem_hashes(init_remote.expected_kem_key_digests);
|
||||
let handshake_resp =
|
||||
PSQHandshakeState::new(conn_resp, resp).as_responder(responder_data);
|
||||
|
||||
let mut resp_rng = DeterministicRng09Send::new(u64_seeded_rng_09(2));
|
||||
let resp_fut = tokio::spawn(async move {
|
||||
handshake_resp
|
||||
.complete_handshake_with_rng(&mut resp_rng)
|
||||
.timeboxed()
|
||||
.await
|
||||
});
|
||||
|
||||
// initiator:
|
||||
|
||||
let mut rng = deterministic_rng_09();
|
||||
let dir_hash = resp_remote.expected_kem_key_hash(init.ciphersuite)?;
|
||||
|
||||
let lp_peer_config = LpPeerConfig::new_client_to_entry(&mut rng, false);
|
||||
|
||||
// Mutual - MlKem
|
||||
let (mut initiator, request) = KKTInitiator::generate_mutual_request(
|
||||
&mut rng,
|
||||
init.ciphersuite,
|
||||
init.encoded_kem_key(kem).unwrap(),
|
||||
&resp_remote.x25519_public,
|
||||
&dir_hash,
|
||||
1,
|
||||
Some(Vec::from(lp_peer_config.serialize())),
|
||||
)?;
|
||||
|
||||
// 1. send kkt request
|
||||
conn_init
|
||||
.send_handshake_message::<handshake_message::KKTRequest>(request.into(), kem)
|
||||
.timeboxed()
|
||||
.await??;
|
||||
|
||||
// 2. receive KKT response
|
||||
let response_len = KKTResponse::size_excluding_payload(kem);
|
||||
let resp: handshake_message::KKTResponse = conn_init
|
||||
.receive_handshake_message(response_len)
|
||||
.timeboxed()
|
||||
.await??;
|
||||
let kkt_response = resp.into();
|
||||
|
||||
let response = initiator.process_response(kkt_response, 0)?;
|
||||
let encapsulation_key = response.encapsulation_key;
|
||||
|
||||
let initiator_ciphersuite =
|
||||
initiator::build_psq_ciphersuite(&init, &resp_remote, &encapsulation_key)?;
|
||||
let mut initiator =
|
||||
initiator::build_psq_principal(rand09::rng(), 1, initiator_ciphersuite)?;
|
||||
|
||||
// 3. send PSQ msg1
|
||||
// Send first message
|
||||
let mut buf = vec![0u8; psq_msg1_size(kem)];
|
||||
let n = initiator.write_message(&[], &mut buf).unwrap();
|
||||
assert_eq!(n, buf.len());
|
||||
let msg = PSQMsg1::new(buf);
|
||||
conn_init
|
||||
.send_handshake_message(msg, kem)
|
||||
.timeboxed()
|
||||
.await??;
|
||||
|
||||
// 4. receive PSQ msg2
|
||||
let msg: PSQMsg2 = conn_init
|
||||
.receive_handshake_message(PSQ_MSG2_SIZE)
|
||||
.timeboxed()
|
||||
.await??;
|
||||
initiator.read_message(&msg, &mut []).unwrap();
|
||||
|
||||
assert!(initiator.is_handshake_finished());
|
||||
|
||||
let mut session_resp = resp_fut.await???;
|
||||
|
||||
let mut i_transport = initiator.into_session().unwrap();
|
||||
|
||||
// test serialization, deserialization
|
||||
let mut channel_i = i_transport.transport_channel().unwrap();
|
||||
let channel_r = session_resp.active_transport();
|
||||
|
||||
assert_eq!(channel_i.identifier(), channel_r.identifier());
|
||||
|
||||
let app_data_i = b"Derived session hey".as_slice();
|
||||
let app_data_r = b"Derived session ho".as_slice();
|
||||
|
||||
let ct_i = encrypt_data(app_data_i, &mut channel_i)?;
|
||||
let pt_r = decrypt_data(&ct_i, channel_r)?;
|
||||
|
||||
assert_eq!(app_data_i, pt_r);
|
||||
|
||||
let ct_r = encrypt_data(app_data_r, channel_r)?;
|
||||
let pt_i = decrypt_data(&ct_r, &mut channel_i)?;
|
||||
|
||||
assert_eq!(app_data_r, pt_i);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+171
-20
@@ -6,9 +6,10 @@
|
||||
//! This module implements session management functionality, including replay protection
|
||||
|
||||
use crate::codec::{decrypt_lp_packet, encrypt_lp_packet};
|
||||
use crate::packet::{EncryptedLpPacket, LpHeader, LpMessage, LpPacket};
|
||||
use crate::packet::{EncryptedLpPacket, LpFrame, LpHeader, LpPacket};
|
||||
use crate::peer::{LpLocalPeer, LpRemotePeer};
|
||||
use crate::peer_config::LpReceiverIndex;
|
||||
use crate::psq::initiator::HandshakeMode;
|
||||
use crate::psq::{
|
||||
InitiatorData, PSQHandshakeState, PSQHandshakeStateInitiator, PSQHandshakeStateResponder,
|
||||
ResponderData,
|
||||
@@ -19,15 +20,38 @@ use crate::{LpError, replay::ReceivingKeyCounterValidator};
|
||||
use libcrux_psq::handshake::types::{Authenticator, DHPublicKey};
|
||||
use libcrux_psq::session::{Session, SessionBinding};
|
||||
use nym_kkt::keys::EncapsulationKey;
|
||||
use nym_kkt_ciphersuite::{KEM, KEMKeyDigests};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
/// Represents inputs that drive the state machine transitions.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum LpInput {
|
||||
/// Received an encrypted LP Packet from the network.
|
||||
ReceivePacket(EncryptedLpPacket),
|
||||
|
||||
/// Application wants to send data (only valid in Transport state).
|
||||
SendFrame(LpFrame),
|
||||
}
|
||||
|
||||
/// Represents actions the state machine requests the environment to perform.
|
||||
#[derive(Debug)]
|
||||
pub enum LpAction {
|
||||
/// Send an LP Packet over the network.
|
||||
SendPacket(EncryptedLpPacket),
|
||||
|
||||
/// Deliver decrypted application data received from the peer.
|
||||
DeliverFrame(LpFrame),
|
||||
}
|
||||
|
||||
pub type SessionId = [u8; 32];
|
||||
|
||||
/// A session in the Lewes Protocol, handling connection state with Noise.
|
||||
/// A session in the Lewes Protocol..
|
||||
///
|
||||
/// Sessions manage connection state, including LP replay protection.
|
||||
/// Each session has a unique receiving index and sending index for connection identification.
|
||||
pub struct LpSession {
|
||||
pub struct LpTransportSession {
|
||||
/// The underlying established session
|
||||
psq_session: Session,
|
||||
|
||||
@@ -62,6 +86,9 @@ pub struct PersistentSessionBinding {
|
||||
|
||||
/// The responder's long term PQ-KEM public key (if any).
|
||||
pub responder_pq_pk: Option<EncapsulationKey>,
|
||||
|
||||
/// The initiator's long term PQ-KEM public key (if any).
|
||||
pub initiator_pq_pk: Option<EncapsulationKey>,
|
||||
}
|
||||
|
||||
impl Debug for PersistentSessionBinding {
|
||||
@@ -87,7 +114,7 @@ impl<'a> From<&'a PersistentSessionBinding> for SessionBinding<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for LpSession {
|
||||
impl Debug for LpTransportSession {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("LpSession")
|
||||
.field("session_id", &self.psq_session.identifier())
|
||||
@@ -100,7 +127,7 @@ impl Debug for LpSession {
|
||||
}
|
||||
}
|
||||
|
||||
impl LpSession {
|
||||
impl LpTransportSession {
|
||||
/// Creates a new session after completed KTT/PSQ exchange
|
||||
pub fn new(
|
||||
mut psq_session: Session,
|
||||
@@ -113,7 +140,7 @@ impl LpSession {
|
||||
.transport_channel()
|
||||
.map_err(|inner| LpError::TransportDerivationFailure { inner })?;
|
||||
|
||||
Ok(LpSession {
|
||||
Ok(LpTransportSession {
|
||||
psq_session,
|
||||
session_binding,
|
||||
active_transport: transport,
|
||||
@@ -130,12 +157,34 @@ impl LpSession {
|
||||
local_peer: LpLocalPeer,
|
||||
remote_peer: LpRemotePeer,
|
||||
remote_protocol_version: u8,
|
||||
) -> PSQHandshakeStateInitiator<'_, S>
|
||||
mode: HandshakeMode,
|
||||
) -> Result<PSQHandshakeStateInitiator<'_, S>, LpError>
|
||||
where
|
||||
S: LpHandshakeChannel + Unpin,
|
||||
{
|
||||
PSQHandshakeState::new(connection, local_peer)
|
||||
.as_initiator(InitiatorData::new(remote_protocol_version, remote_peer))
|
||||
PSQHandshakeState::new(connection, local_peer).as_initiator(
|
||||
InitiatorData::new(remote_protocol_version, remote_peer),
|
||||
mode,
|
||||
)
|
||||
}
|
||||
|
||||
/// Helper function to create `PSQHandshakeState` for the handshake initiator for mutual KKT
|
||||
pub fn psq_handshake_initiator_mutual_internode<S>(
|
||||
connection: &'_ mut S,
|
||||
local_peer: LpLocalPeer,
|
||||
remote_peer: LpRemotePeer,
|
||||
remote_protocol_version: u8,
|
||||
) -> Result<PSQHandshakeStateInitiator<'_, S>, LpError>
|
||||
where
|
||||
S: LpHandshakeChannel + Unpin,
|
||||
{
|
||||
Self::psq_handshake_initiator(
|
||||
connection,
|
||||
local_peer,
|
||||
remote_peer,
|
||||
remote_protocol_version,
|
||||
HandshakeMode::MutualInternode,
|
||||
)
|
||||
}
|
||||
|
||||
/// Helper function to create `PSQHandshakeState` for the handshake responder
|
||||
@@ -149,6 +198,19 @@ impl LpSession {
|
||||
PSQHandshakeState::new(connection, local_peer).as_responder(ResponderData::default())
|
||||
}
|
||||
|
||||
/// Helper function to create `PSQHandshakeState` for the handshake responder for mutual KKT
|
||||
pub fn psq_handshake_responder_mutual<S>(
|
||||
connection: &'_ mut S,
|
||||
local_peer: LpLocalPeer,
|
||||
initiator_kem_hashes: BTreeMap<KEM, KEMKeyDigests>,
|
||||
) -> PSQHandshakeStateResponder<'_, S>
|
||||
where
|
||||
S: LpHandshakeChannel + Unpin,
|
||||
{
|
||||
PSQHandshakeState::new(connection, local_peer)
|
||||
.as_responder(ResponderData::default().with_initiator_kem_hashes(initiator_kem_hashes))
|
||||
}
|
||||
|
||||
pub fn session_binding(&self) -> &PersistentSessionBinding {
|
||||
&self.session_binding
|
||||
}
|
||||
@@ -172,10 +234,10 @@ impl LpSession {
|
||||
self.protocol_version
|
||||
}
|
||||
|
||||
pub fn next_packet(&mut self, message: LpMessage) -> Result<LpPacket, LpError> {
|
||||
pub fn next_packet(&mut self, frame: LpFrame) -> Result<LpPacket, LpError> {
|
||||
let counter = self.next_counter();
|
||||
let header = LpHeader::new(self.receiver_index(), counter, self.protocol_version);
|
||||
let packet = LpPacket::new(header, message);
|
||||
let packet = LpPacket::new(header, frame);
|
||||
Ok(packet)
|
||||
}
|
||||
|
||||
@@ -237,22 +299,19 @@ impl LpSession {
|
||||
self.receiving_counter.current_packet_cnt()
|
||||
}
|
||||
|
||||
/// Encrypts a produced application using the established transport session
|
||||
/// and produce an `EncryptedLpPacket`
|
||||
/// Wrap the provided `LpFrame` into an `LpPacket` and encrypt its content using the established transport session
|
||||
/// to produce an `EncryptedLpPacket`
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - plaintext data to encrypt
|
||||
/// * `frame` - structured `LpFrame` to wrap and encrypt
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(EncryptedLpPacket)` containing the encrypted message ciphertext.
|
||||
/// * `Err(LpError)` if the session is not in transport mode or encryption fails.
|
||||
pub(crate) fn encrypt_application_data(
|
||||
&mut self,
|
||||
data: LpMessage,
|
||||
) -> Result<EncryptedLpPacket, LpError> {
|
||||
let packet = self.next_packet(data)?;
|
||||
pub(crate) fn wrap_lp_frame(&mut self, frame: LpFrame) -> Result<EncryptedLpPacket, LpError> {
|
||||
let packet = self.next_packet(frame)?;
|
||||
encrypt_lp_packet(packet, &mut self.active_transport)
|
||||
}
|
||||
|
||||
@@ -260,7 +319,7 @@ impl LpSession {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ciphertext` - The encrypted packet
|
||||
/// * `packet` - The encrypted packet
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
@@ -272,6 +331,41 @@ impl LpSession {
|
||||
) -> Result<LpPacket, LpError> {
|
||||
decrypt_lp_packet(packet, &mut self.active_transport)
|
||||
}
|
||||
|
||||
/// Processes an input event and returns an action to perform.
|
||||
pub fn process_input(&mut self, input: LpInput) -> Result<LpAction, LpError> {
|
||||
match input {
|
||||
LpInput::ReceivePacket(packet) => {
|
||||
// Check if packet lp_id matches our session
|
||||
if packet.outer_header().receiver_idx != self.receiver_index() {
|
||||
return Err(LpError::UnknownSessionId(
|
||||
packet.outer_header().receiver_idx,
|
||||
));
|
||||
}
|
||||
|
||||
let ctr = packet.outer_header().counter;
|
||||
|
||||
// 1. Check replay protection
|
||||
self.receiving_counter_quick_check(ctr)?;
|
||||
|
||||
// 2. decrypt the packet and attempt to deliver data
|
||||
let packet = self.decrypt_packet(packet)?;
|
||||
|
||||
// 3. Mark counter as received
|
||||
self.receiving_counter_mark(ctr)?;
|
||||
|
||||
// 4. deliver the message
|
||||
Ok(LpAction::DeliverFrame(packet.frame))
|
||||
}
|
||||
LpInput::SendFrame(data) => {
|
||||
// Encrypt and send application data
|
||||
match self.wrap_lp_frame(data) {
|
||||
Ok(packet) => Ok(LpAction::SendPacket(packet)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -361,4 +455,61 @@ mod tests {
|
||||
assert_eq!(packet_count.received, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_state_machine_simplified_flow() {
|
||||
for kem in KEM::iter() {
|
||||
let mock_sessions = SessionsMock::mock_post_handshake(kem);
|
||||
let receiver_index = mock_sessions.responder.receiver_index();
|
||||
|
||||
// Create state machines (already in Transport)
|
||||
let mut initiator = mock_sessions.initiator;
|
||||
let mut responder = mock_sessions.responder;
|
||||
|
||||
assert_eq!(
|
||||
initiator.session_identifier(),
|
||||
responder.session_identifier()
|
||||
);
|
||||
|
||||
// --- Transport Phase ---
|
||||
println!("--- Step 1: Initiator sends data ---");
|
||||
let data_to_send_1 = LpFrame::new_opaque(b"hello responder".to_vec());
|
||||
let init_actions_4 =
|
||||
initiator.process_input(LpInput::SendFrame(data_to_send_1.clone()));
|
||||
let data_packet_1 = if let Ok(LpAction::SendPacket(packet)) = init_actions_4 {
|
||||
packet.clone()
|
||||
} else {
|
||||
panic!("Initiator should send data packet");
|
||||
};
|
||||
assert_eq!(data_packet_1.outer_header().receiver_idx, receiver_index);
|
||||
|
||||
println!("--- Step 2: Responder receives data ---");
|
||||
let resp_actions_5 = responder.process_input(LpInput::ReceivePacket(data_packet_1));
|
||||
let resp_data_1 = if let Ok(LpAction::DeliverFrame(data)) = resp_actions_5 {
|
||||
data
|
||||
} else {
|
||||
panic!("Responder should deliver data");
|
||||
};
|
||||
assert_eq!(resp_data_1, data_to_send_1);
|
||||
|
||||
println!("--- Step 3: Responder sends data ---");
|
||||
let data_to_send_2 = LpFrame::new_opaque(b"hello initiator".to_vec());
|
||||
let resp_actions_6 =
|
||||
responder.process_input(LpInput::SendFrame(data_to_send_2.clone()));
|
||||
let data_packet_2 = if let Ok(LpAction::SendPacket(packet)) = resp_actions_6 {
|
||||
packet.clone()
|
||||
} else {
|
||||
panic!("Responder should send data packet");
|
||||
};
|
||||
assert_eq!(data_packet_2.outer_header().receiver_idx, receiver_index);
|
||||
|
||||
println!("--- Step 4: Initiator receives data ---");
|
||||
let init_actions_5 = initiator.process_input(LpInput::ReceivePacket(data_packet_2));
|
||||
if let Ok(LpAction::DeliverFrame(data)) = init_actions_5 {
|
||||
assert_eq!(data, data_to_send_2);
|
||||
} else {
|
||||
panic!("Initiator should deliver data");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::packet::{EncryptedLpPacket, LpMessage};
|
||||
use crate::state_machine::{LpAction, LpInput, LpStateBare};
|
||||
use crate::packet::{EncryptedLpPacket, LpFrame};
|
||||
use crate::session::{LpAction, LpInput};
|
||||
use crate::{LpError, SessionManager, SessionsMock};
|
||||
use nym_kkt_ciphersuite::{IntoEnumIterator, KEM};
|
||||
|
||||
@@ -9,7 +9,7 @@ mod tests {
|
||||
trait ActionExtract {
|
||||
fn ciphertext(self) -> EncryptedLpPacket;
|
||||
|
||||
fn data(self) -> LpMessage;
|
||||
fn data(self) -> LpFrame;
|
||||
}
|
||||
|
||||
impl ActionExtract for LpAction {
|
||||
@@ -21,8 +21,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn data(self) -> LpMessage {
|
||||
if let LpAction::DeliverData(data) = self {
|
||||
fn data(self) -> LpFrame {
|
||||
if let LpAction::DeliverFrame(data) = self {
|
||||
data
|
||||
} else {
|
||||
panic!("invalid action");
|
||||
@@ -41,10 +41,10 @@ mod tests {
|
||||
|
||||
// 2. Create sessions using the pre-built Noise states
|
||||
let peer_a_sm = session_manager_1
|
||||
.create_session_state_machine(sessions.initiator)
|
||||
.insert_session(sessions.initiator)
|
||||
.unwrap();
|
||||
let peer_b_sm = session_manager_2
|
||||
.create_session_state_machine(sessions.responder)
|
||||
.insert_session(sessions.responder)
|
||||
.unwrap();
|
||||
|
||||
// 3. Send multiple encrypted messages both ways
|
||||
@@ -54,7 +54,7 @@ mod tests {
|
||||
// --- A sends to B ---
|
||||
let plaintext_a = format!("A->B Message {i}").into_bytes();
|
||||
let ciphertext_a = session_manager_1
|
||||
.send_data(peer_a_sm, LpMessage::new_opaque(plaintext_a.clone()))
|
||||
.send_frame(peer_a_sm, LpFrame::new_opaque(plaintext_a.clone()))
|
||||
.unwrap()
|
||||
.ciphertext();
|
||||
|
||||
@@ -62,14 +62,13 @@ mod tests {
|
||||
let decrypted_payload = session_manager_2
|
||||
.receive_packet(peer_b_sm, ciphertext_a)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.data();
|
||||
assert_eq!(decrypted_payload.content, plaintext_a);
|
||||
|
||||
// --- B sends to A ---
|
||||
let plaintext_b = format!("B->A Message {i}").into_bytes();
|
||||
let ciphertext_b = session_manager_2
|
||||
.send_data(peer_b_sm, LpMessage::new_opaque(plaintext_b.clone()))
|
||||
.send_frame(peer_b_sm, LpFrame::new_opaque(plaintext_b.clone()))
|
||||
.unwrap()
|
||||
.ciphertext();
|
||||
|
||||
@@ -77,7 +76,6 @@ mod tests {
|
||||
let decrypted_payload = session_manager_1
|
||||
.receive_packet(peer_a_sm, ciphertext_b)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.data();
|
||||
assert_eq!(decrypted_payload.content, plaintext_b);
|
||||
}
|
||||
@@ -131,24 +129,24 @@ mod tests {
|
||||
let session2 = sessions.responder;
|
||||
|
||||
// 2. Create a session (using real noise state)
|
||||
let _session = session_manager.create_session_state_machine(session1);
|
||||
let _session = session_manager.insert_session(session1);
|
||||
|
||||
// 3. Try to get a non-existent session
|
||||
let result = session_manager.state_machine_exists(non_existent);
|
||||
let result = session_manager.session_exists(non_existent);
|
||||
assert!(!result, "Non-existent session should return None");
|
||||
|
||||
// 4. Try to remove a non-existent session
|
||||
let result = session_manager.remove_state_machine(non_existent);
|
||||
let result = session_manager.remove_session(non_existent);
|
||||
assert!(
|
||||
!result,
|
||||
"Remove session should not remove a non-existent session"
|
||||
);
|
||||
|
||||
// 5. Create and immediately remove a session
|
||||
let _temp_session = session_manager.create_session_state_machine(session2);
|
||||
let _temp_session = session_manager.insert_session(session2);
|
||||
|
||||
assert!(
|
||||
session_manager.remove_state_machine(session_id),
|
||||
session_manager.remove_session(session_id),
|
||||
"Should remove the session"
|
||||
);
|
||||
}
|
||||
@@ -172,39 +170,26 @@ mod tests {
|
||||
|
||||
// 2. Create sessions state machines
|
||||
session_manager_1
|
||||
.create_session_state_machine(sessions.initiator)
|
||||
.insert_session(sessions.initiator)
|
||||
.unwrap();
|
||||
session_manager_2
|
||||
.create_session_state_machine(sessions.responder)
|
||||
.insert_session(sessions.responder)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(session_manager_1.session_count(), 1);
|
||||
assert_eq!(session_manager_2.session_count(), 1);
|
||||
assert!(session_manager_1.state_machine_exists(session_id));
|
||||
assert!(session_manager_2.state_machine_exists(session_id));
|
||||
|
||||
// Verify initial states are Transport
|
||||
assert_eq!(
|
||||
session_manager_1.get_state(session_id).unwrap(),
|
||||
LpStateBare::Transport
|
||||
);
|
||||
assert_eq!(
|
||||
session_manager_2.get_state(session_id).unwrap(),
|
||||
LpStateBare::Transport
|
||||
);
|
||||
assert!(session_manager_1.session_exists(session_id));
|
||||
assert!(session_manager_2.session_exists(session_id));
|
||||
|
||||
// --- 3. Simulate Data Transfer via process_input ---
|
||||
println!("Starting data transfer simulation via process_input...");
|
||||
let plaintext_a_to_b =
|
||||
LpMessage::new_opaque(b"Hello from A via process_input!".to_vec());
|
||||
let plaintext_b_to_a =
|
||||
LpMessage::new_opaque(b"Hello from B via process_input!".to_vec());
|
||||
let plaintext_a_to_b = LpFrame::new_opaque(b"Hello from A via process_input!".to_vec());
|
||||
let plaintext_b_to_a = LpFrame::new_opaque(b"Hello from B via process_input!".to_vec());
|
||||
|
||||
// --- A sends to B ---
|
||||
println!(" A sends to B");
|
||||
let action_a_send = session_manager_1
|
||||
.process_input(session_id, LpInput::SendData(plaintext_a_to_b.clone()))
|
||||
.expect("A SendData should produce action")
|
||||
.process_input(session_id, LpInput::SendFrame(plaintext_a_to_b.clone()))
|
||||
.expect("A SendData failed");
|
||||
|
||||
let data_packet_a = action_a_send.ciphertext();
|
||||
@@ -213,10 +198,9 @@ mod tests {
|
||||
println!(" B receives from A");
|
||||
let action_b_recv = session_manager_2
|
||||
.process_input(session_id, LpInput::ReceivePacket(data_packet_a))
|
||||
.expect("B ReceivePacket (data) should produce action")
|
||||
.expect("B ReceivePacket (data) failed");
|
||||
|
||||
if let LpAction::DeliverData(data) = action_b_recv {
|
||||
if let LpAction::DeliverFrame(data) = action_b_recv {
|
||||
assert_eq!(data, plaintext_a_to_b, "Decrypted data mismatch A->B");
|
||||
println!(
|
||||
" B successfully decrypted: {:?}",
|
||||
@@ -229,8 +213,7 @@ mod tests {
|
||||
// --- B sends to A ---
|
||||
println!(" B sends to A");
|
||||
let action_b_send = session_manager_2
|
||||
.process_input(session_id, LpInput::SendData(plaintext_b_to_a.clone()))
|
||||
.expect("B SendData should produce action")
|
||||
.process_input(session_id, LpInput::SendFrame(plaintext_b_to_a.clone()))
|
||||
.expect("B SendData failed");
|
||||
|
||||
let data_packet_b = action_b_send.ciphertext();
|
||||
@@ -242,10 +225,9 @@ mod tests {
|
||||
println!(" A receives from B");
|
||||
let action_a_recv = session_manager_1
|
||||
.process_input(session_id, LpInput::ReceivePacket(data_packet_b))
|
||||
.expect("A ReceivePacket (data) should produce action")
|
||||
.expect("A ReceivePacket (data) failed");
|
||||
|
||||
if let LpAction::DeliverData(data) = action_a_recv {
|
||||
if let LpAction::DeliverFrame(data) = action_a_recv {
|
||||
assert_eq!(data, plaintext_b_to_a, "Decrypted data mismatch B->A");
|
||||
println!(
|
||||
" A successfully decrypted: {:?}",
|
||||
@@ -274,12 +256,11 @@ mod tests {
|
||||
println!("Testing out-of-order reception via process_input...");
|
||||
|
||||
// A prepares N+1 then N
|
||||
let data_n_plus_1 = LpMessage::new_opaque(b"Message N+1".to_vec());
|
||||
let data_n = LpMessage::new_opaque(b"Message N".to_vec());
|
||||
let data_n_plus_1 = LpFrame::new_opaque(b"Message N+1".to_vec());
|
||||
let data_n = LpFrame::new_opaque(b"Message N".to_vec());
|
||||
|
||||
let action_send_n1 = session_manager_1
|
||||
.process_input(session_id, LpInput::SendData(data_n_plus_1.clone()))
|
||||
.unwrap()
|
||||
.process_input(session_id, LpInput::SendFrame(data_n_plus_1.clone()))
|
||||
.unwrap();
|
||||
let packet_n1 = match action_send_n1 {
|
||||
LpAction::SendPacket(p) => p,
|
||||
@@ -287,8 +268,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let action_send_n = session_manager_1
|
||||
.process_input(session_id, LpInput::SendData(data_n.clone()))
|
||||
.unwrap()
|
||||
.process_input(session_id, LpInput::SendFrame(data_n.clone()))
|
||||
.unwrap();
|
||||
let packet_n = match action_send_n {
|
||||
LpAction::SendPacket(p) => p,
|
||||
@@ -300,10 +280,9 @@ mod tests {
|
||||
println!(" B receives N+1");
|
||||
let action_recv_n1 = session_manager_2
|
||||
.process_input(session_id, LpInput::ReceivePacket(packet_n1))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
match action_recv_n1 {
|
||||
LpAction::DeliverData(d) => assert_eq!(d, data_n_plus_1, "Data N+1 mismatch"),
|
||||
LpAction::DeliverFrame(d) => assert_eq!(d, data_n_plus_1, "Data N+1 mismatch"),
|
||||
_ => panic!("Expected DeliverData for N+1"),
|
||||
}
|
||||
|
||||
@@ -311,10 +290,9 @@ mod tests {
|
||||
println!(" B receives N");
|
||||
let action_recv_n = session_manager_2
|
||||
.process_input(session_id, LpInput::ReceivePacket(packet_n))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
match action_recv_n {
|
||||
LpAction::DeliverData(d) => assert_eq!(d, data_n, "Data N mismatch"),
|
||||
LpAction::DeliverFrame(d) => assert_eq!(d, data_n, "Data N mismatch"),
|
||||
_ => panic!("Expected DeliverData for N"),
|
||||
}
|
||||
|
||||
@@ -329,64 +307,16 @@ mod tests {
|
||||
);
|
||||
println!("Out-of-order test passed.");
|
||||
|
||||
// --- 6. Close Test ---
|
||||
println!("Testing close via process_input...");
|
||||
|
||||
// A closes
|
||||
let action_a_close = session_manager_1
|
||||
.process_input(session_id, LpInput::Close)
|
||||
.expect("A Close should produce action")
|
||||
.expect("A Close failed");
|
||||
assert!(matches!(action_a_close, LpAction::ConnectionClosed));
|
||||
assert_eq!(
|
||||
session_manager_1.get_state(session_id).unwrap(),
|
||||
LpStateBare::Closed
|
||||
);
|
||||
|
||||
// Further actions on A fail
|
||||
let send_after_close_a = session_manager_1.process_input(
|
||||
session_id,
|
||||
LpInput::SendData(LpMessage::new_opaque(b"fail".to_vec())),
|
||||
);
|
||||
assert!(send_after_close_a.is_err());
|
||||
assert!(matches!(
|
||||
send_after_close_a.err().unwrap(),
|
||||
LpError::LpSessionClosed
|
||||
));
|
||||
|
||||
// B closes
|
||||
let action_b_close = session_manager_2
|
||||
.process_input(session_id, LpInput::Close)
|
||||
.expect("B Close should produce action")
|
||||
.expect("B Close failed");
|
||||
assert!(matches!(action_b_close, LpAction::ConnectionClosed));
|
||||
assert_eq!(
|
||||
session_manager_2.get_state(session_id).unwrap(),
|
||||
LpStateBare::Closed
|
||||
);
|
||||
|
||||
// Further actions on B fail
|
||||
let send_after_close_b = session_manager_2.process_input(
|
||||
session_id,
|
||||
LpInput::SendData(LpMessage::new_opaque(b"fail".to_vec())),
|
||||
);
|
||||
assert!(send_after_close_b.is_err());
|
||||
assert!(matches!(
|
||||
send_after_close_b.err().unwrap(),
|
||||
LpError::LpSessionClosed
|
||||
));
|
||||
println!("Close test passed.");
|
||||
|
||||
// --- 7. Session Removal ---
|
||||
assert!(session_manager_1.remove_state_machine(session_id));
|
||||
// --- 6. Session Removal ---
|
||||
assert!(session_manager_1.remove_session(session_id));
|
||||
assert_eq!(session_manager_1.session_count(), 0);
|
||||
assert!(!session_manager_1.state_machine_exists(session_id));
|
||||
assert!(!session_manager_1.session_exists(session_id));
|
||||
|
||||
// B's session manager still has it until removed
|
||||
assert!(session_manager_2.state_machine_exists(session_id));
|
||||
assert!(session_manager_2.remove_state_machine(session_id));
|
||||
assert!(session_manager_2.session_exists(session_id));
|
||||
assert!(session_manager_2.remove_session(session_id));
|
||||
assert_eq!(session_manager_2.session_count(), 0);
|
||||
assert!(!session_manager_2.state_machine_exists(session_id));
|
||||
assert!(!session_manager_2.session_exists(session_id));
|
||||
println!("Session removal test passed.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,33 +6,28 @@
|
||||
//! This module implements session lifecycle management functionality, handling
|
||||
//! creation, retrieval, and storage of sessions.
|
||||
|
||||
use crate::packet::{EncryptedLpPacket, LpMessage};
|
||||
use crate::packet::{EncryptedLpPacket, LpFrame};
|
||||
use crate::peer_config::LpReceiverIndex;
|
||||
use crate::state_machine::{LpAction, LpInput, LpStateBare};
|
||||
use crate::{LpError, LpSession, LpStateMachine};
|
||||
use crate::{LpError, LpTransportSession};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use crate::replay::validator::PacketCount;
|
||||
use crate::session::{LpAction, LpInput};
|
||||
|
||||
/// Manages the lifecycle of Lewes Protocol sessions.
|
||||
///
|
||||
/// The SessionManager is responsible for creating, storing, and retrieving sessions
|
||||
#[derive(Default)]
|
||||
pub struct SessionManager {
|
||||
/// Manages state machines directly, keyed by lp_id
|
||||
state_machines: HashMap<LpReceiverIndex, LpStateMachine>,
|
||||
}
|
||||
|
||||
impl Default for SessionManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
sessions: HashMap<LpReceiverIndex, LpTransportSession>,
|
||||
}
|
||||
|
||||
impl SessionManager {
|
||||
/// Creates a new session manager with empty session storage.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state_machines: HashMap::new(),
|
||||
sessions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,61 +35,48 @@ impl SessionManager {
|
||||
&mut self,
|
||||
lp_id: LpReceiverIndex,
|
||||
input: LpInput,
|
||||
) -> Result<Option<LpAction>, LpError> {
|
||||
self.with_state_machine_mut(lp_id, |sm| sm.process_input(input).transpose())?
|
||||
) -> Result<LpAction, LpError> {
|
||||
self.with_session_mut(lp_id, |sm| sm.process_input(input))?
|
||||
}
|
||||
|
||||
pub fn send_data(
|
||||
pub fn send_frame(
|
||||
&mut self,
|
||||
lp_id: LpReceiverIndex,
|
||||
data: LpMessage,
|
||||
frame: LpFrame,
|
||||
) -> Result<LpAction, LpError> {
|
||||
self.process_input(lp_id, LpInput::SendData(data))?
|
||||
.ok_or(LpError::NotInTransport)
|
||||
self.process_input(lp_id, LpInput::SendFrame(frame))
|
||||
}
|
||||
|
||||
pub fn receive_packet(
|
||||
&mut self,
|
||||
lp_id: LpReceiverIndex,
|
||||
packet: EncryptedLpPacket,
|
||||
) -> Result<Option<LpAction>, LpError> {
|
||||
) -> Result<LpAction, LpError> {
|
||||
self.process_input(lp_id, LpInput::ReceivePacket(packet))
|
||||
}
|
||||
|
||||
pub fn closed(&self, lp_id: LpReceiverIndex) -> Result<bool, LpError> {
|
||||
Ok(self.get_state(lp_id)? == LpStateBare::Closed)
|
||||
}
|
||||
|
||||
pub fn transport(&self, lp_id: LpReceiverIndex) -> Result<bool, LpError> {
|
||||
Ok(self.get_state(lp_id)? == LpStateBare::Transport)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn get_state_machine_id(&self, lp_id: LpReceiverIndex) -> Result<LpReceiverIndex, LpError> {
|
||||
self.with_state_machine(lp_id, |sm| sm.receiver_index())?
|
||||
}
|
||||
|
||||
pub fn get_state(&self, lp_id: LpReceiverIndex) -> Result<LpStateBare, LpError> {
|
||||
self.with_state_machine(lp_id, |sm| Ok(sm.bare_state()))?
|
||||
fn get_session_id(&self, lp_id: LpReceiverIndex) -> Result<LpReceiverIndex, LpError> {
|
||||
self.with_session(lp_id, |sm| sm.receiver_index())
|
||||
}
|
||||
|
||||
pub fn current_packet_cnt(&self, lp_id: LpReceiverIndex) -> Result<PacketCount, LpError> {
|
||||
self.with_state_machine(lp_id, |sm| Ok(sm.session()?.current_packet_cnt()))?
|
||||
self.with_session(lp_id, |sm| Ok(sm.current_packet_cnt()))?
|
||||
}
|
||||
|
||||
pub fn session_count(&self) -> usize {
|
||||
self.state_machines.len()
|
||||
self.sessions.len()
|
||||
}
|
||||
|
||||
pub fn state_machine_exists(&self, lp_id: LpReceiverIndex) -> bool {
|
||||
self.state_machines.contains_key(&lp_id)
|
||||
pub fn session_exists(&self, lp_id: LpReceiverIndex) -> bool {
|
||||
self.sessions.contains_key(&lp_id)
|
||||
}
|
||||
|
||||
pub fn with_state_machine<F, R>(&self, lp_id: LpReceiverIndex, f: F) -> Result<R, LpError>
|
||||
pub fn with_session<F, R>(&self, lp_id: LpReceiverIndex, f: F) -> Result<R, LpError>
|
||||
where
|
||||
F: FnOnce(&LpStateMachine) -> R,
|
||||
F: FnOnce(&LpTransportSession) -> R,
|
||||
{
|
||||
if let Some(sm) = self.state_machines.get(&lp_id) {
|
||||
if let Some(sm) = self.sessions.get(&lp_id) {
|
||||
Ok(f(sm))
|
||||
} else {
|
||||
Err(LpError::StateMachineNotFound(lp_id))
|
||||
@@ -102,39 +84,34 @@ impl SessionManager {
|
||||
}
|
||||
|
||||
// For mutable access (like running process_input)
|
||||
pub fn with_state_machine_mut<F, R>(
|
||||
&mut self,
|
||||
lp_id: LpReceiverIndex,
|
||||
f: F,
|
||||
) -> Result<R, LpError>
|
||||
pub fn with_session_mut<F, R>(&mut self, lp_id: LpReceiverIndex, f: F) -> Result<R, LpError>
|
||||
where
|
||||
F: FnOnce(&mut LpStateMachine) -> R, // Closure takes mutable ref
|
||||
F: FnOnce(&mut LpTransportSession) -> R, // Closure takes mutable ref
|
||||
{
|
||||
if let Some(sm) = self.state_machines.get_mut(&lp_id) {
|
||||
if let Some(sm) = self.sessions.get_mut(&lp_id) {
|
||||
Ok(f(sm))
|
||||
} else {
|
||||
Err(LpError::StateMachineNotFound(lp_id))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_session_state_machine(
|
||||
pub fn insert_session(
|
||||
&mut self,
|
||||
lp_session: LpSession,
|
||||
lp_session: LpTransportSession,
|
||||
) -> Result<LpReceiverIndex, LpError> {
|
||||
let session_id = lp_session.receiver_index();
|
||||
|
||||
if self.state_machines.contains_key(&session_id) {
|
||||
if self.sessions.contains_key(&session_id) {
|
||||
return Err(LpError::DuplicateSessionId(session_id));
|
||||
}
|
||||
|
||||
let sm = LpStateMachine::new(lp_session);
|
||||
self.state_machines.insert(session_id, sm);
|
||||
self.sessions.insert(session_id, lp_session);
|
||||
Ok(session_id)
|
||||
}
|
||||
|
||||
/// Method to remove a state machine
|
||||
pub fn remove_state_machine(&mut self, lp_id: LpReceiverIndex) -> bool {
|
||||
let removed = self.state_machines.remove(&lp_id);
|
||||
pub fn remove_session(&mut self, lp_id: LpReceiverIndex) -> bool {
|
||||
let removed = self.sessions.remove(&lp_id);
|
||||
|
||||
removed.is_some()
|
||||
}
|
||||
@@ -152,13 +129,13 @@ mod tests {
|
||||
let local_session = mock_session_for_test();
|
||||
let id = local_session.receiver_index();
|
||||
|
||||
let sm_1_id = manager.create_session_state_machine(local_session).unwrap();
|
||||
let sm_1_id = manager.insert_session(local_session).unwrap();
|
||||
assert_eq!(sm_1_id, id);
|
||||
|
||||
let retrieved = manager.state_machine_exists(id);
|
||||
let retrieved = manager.session_exists(id);
|
||||
assert!(retrieved);
|
||||
|
||||
let not_found = manager.state_machine_exists(123);
|
||||
let not_found = manager.session_exists(123);
|
||||
assert!(!not_found);
|
||||
}
|
||||
|
||||
@@ -166,13 +143,13 @@ mod tests {
|
||||
fn test_session_manager_remove() {
|
||||
let mut manager = SessionManager::new();
|
||||
let local_session = mock_session_for_test();
|
||||
let sm_1_id = manager.create_session_state_machine(local_session).unwrap();
|
||||
let sm_1_id = manager.insert_session(local_session).unwrap();
|
||||
|
||||
let removed = manager.remove_state_machine(sm_1_id);
|
||||
let removed = manager.remove_session(sm_1_id);
|
||||
assert!(removed);
|
||||
assert_eq!(manager.session_count(), 0);
|
||||
|
||||
let removed_again = manager.remove_state_machine(sm_1_id);
|
||||
let removed_again = manager.remove_session(sm_1_id);
|
||||
assert!(!removed_again);
|
||||
}
|
||||
|
||||
@@ -184,15 +161,15 @@ mod tests {
|
||||
let session2 = SessionsMock::mock_seeded_post_handshake(124, kem).initiator;
|
||||
let session3 = SessionsMock::mock_seeded_post_handshake(125, kem).initiator;
|
||||
|
||||
let sm_1 = manager.create_session_state_machine(session1).unwrap();
|
||||
let sm_2 = manager.create_session_state_machine(session2).unwrap();
|
||||
let sm_3 = manager.create_session_state_machine(session3).unwrap();
|
||||
let sm_1 = manager.insert_session(session1).unwrap();
|
||||
let sm_2 = manager.insert_session(session2).unwrap();
|
||||
let sm_3 = manager.insert_session(session3).unwrap();
|
||||
|
||||
assert_eq!(manager.session_count(), 3);
|
||||
|
||||
let retrieved1 = manager.get_state_machine_id(sm_1).unwrap();
|
||||
let retrieved2 = manager.get_state_machine_id(sm_2).unwrap();
|
||||
let retrieved3 = manager.get_state_machine_id(sm_3).unwrap();
|
||||
let retrieved1 = manager.get_session_id(sm_1).unwrap();
|
||||
let retrieved2 = manager.get_session_id(sm_2).unwrap();
|
||||
let retrieved3 = manager.get_session_id(sm_3).unwrap();
|
||||
|
||||
assert_eq!(retrieved1, sm_1);
|
||||
assert_eq!(retrieved2, sm_2);
|
||||
@@ -206,10 +183,10 @@ mod tests {
|
||||
|
||||
let sesion = mock_session_for_test();
|
||||
|
||||
let sm = manager.create_session_state_machine(sesion).unwrap();
|
||||
let sm = manager.insert_session(sesion).unwrap();
|
||||
assert_eq!(manager.session_count(), 1);
|
||||
|
||||
let retrieved = manager.get_state_machine_id(sm);
|
||||
let retrieved = manager.get_session_id(sm);
|
||||
assert!(retrieved.is_ok());
|
||||
assert_eq!(retrieved.unwrap(), sm);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user