Compare commits

..

1 Commits

Author SHA1 Message Date
benedettadavico 1fa1b67c8d debug 2026-03-09 09:28:51 +01:00
429 changed files with 15743 additions and 36822 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
build:
runs-on: arc-ubuntu-22.04
env:
NEXT_PUBLIC_SITE_URL: https://nym.com/docs
NEXT_PUBLIC_SITE_URL: https://nymtech.net/docs
defaults:
run:
working-directory: documentation/docs
-2
View File
@@ -48,8 +48,6 @@ 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
+1 -1
View File
@@ -8,7 +8,7 @@ on:
jobs:
sonarqube:
name: SonarQube
runs-on: arc-linux-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
+2
View File
@@ -2,6 +2,8 @@ name: nightly-build
on:
workflow_dispatch:
schedule:
- cron: '14 1 * * *'
jobs:
build:
@@ -9,7 +9,7 @@ on:
jobs:
integration-tests:
runs-on: arc-linux-latest
runs-on: ubuntu-latest
env:
API_BASE_URL: http://localhost:8000
+1 -1
View File
@@ -23,7 +23,7 @@ env:
jobs:
check-milestone:
name: Check Milestone
runs-on: arc-linux-latest
runs-on: ubuntu-latest
steps:
- if: github.event.pull_request.milestone == null && contains( env.LABELS, 'no-milestone' ) == false
run: exit 1
-3
View File
@@ -36,9 +36,6 @@ 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
-1
View File
@@ -46,7 +46,6 @@ storybook-static
**/.DS_Store
cpu-cycles/libcpucycles/build
foxyfox.env
scratch.txt
.next
ppa-private-key.b64
-46
View File
@@ -4,52 +4,6 @@ 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
+1375 -1910
View File
File diff suppressed because it is too large Load Diff
+9 -15
View File
@@ -157,8 +157,8 @@ members = [
"tools/internal/mixnet-connectivity-check",
# "tools/internal/sdk-version-bump",
"tools/internal/ssl-inject",
"tools/internal/localnet-orchestrator",
"tools/internal/localnet-orchestrator/dkg-bypass-contract",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/validator-status-check",
"tools/nym-cli",
"tools/nym-id-cli",
@@ -171,10 +171,9 @@ 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 = [
@@ -191,8 +190,7 @@ default-members = [
"service-providers/ip-packet-router",
"service-providers/network-requester",
"tools/nymvisor",
"nym-registration-client",
"tools/internal/localnet-orchestrator"
"nym-registration-client"
]
exclude = ["contracts", "nym-wallet", "cpu-cycles"]
@@ -204,7 +202,7 @@ homepage = "https://nymtech.net"
documentation = "https://nymtech.net"
edition = "2024"
license = "Apache-2.0"
rust-version = "1.87.0"
rust-version = "1.85"
readme = "README.md"
version = "1.20.4"
@@ -234,7 +232,6 @@ 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"
@@ -350,8 +347,8 @@ si-scale = "0.2.3"
snow = "0.9.6"
sphinx-packet = "=0.6.0"
sqlx = "0.8.6"
strum = "0.28.0"
strum_macros = "0.28.0"
strum = "0.27.2"
strum_macros = "0.27.2"
subtle-encoding = "0.5"
syn = "2"
sysinfo = "0.37.0"
@@ -378,7 +375,7 @@ tracing-opentelemetry = "0.32.1"
tracing-subscriber = "0.3.20"
tracing-indicatif = "0.3.9"
tracing-test = "0.2.5"
ts-rs = "12.0.1"
ts-rs = "10.1.0"
tungstenite = { version = "0.20.1", default-features = false }
typed-builder = "0.23.0"
uniffi = "0.29.2"
@@ -441,7 +438,6 @@ 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" }
@@ -452,10 +448,8 @@ nym-http-api-common = { version = "1.20.4", path = "common/http-api-common", def
nym-id = { version = "1.20.4", path = "common/nym-id" }
nym-ip-packet-client = { version = "1.20.4", path = "nym-ip-packet-client" }
nym-ip-packet-requests = { version = "1.20.4", path = "common/ip-packet-requests" }
nym-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" }
-3
View File
@@ -30,11 +30,8 @@ client ───► Gateway ──┘ mix │ mix ┌─►mix ───►
```
<!-- This is broken
[![Build Status](https://img.shields.io/github/actions/workflow/status/nymtech/nym/build.yml?branch=develop&style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
-->
> This project integrates with the Midnight Network
### Building
+13 -89
View File
@@ -1,38 +1,32 @@
---
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"
###############################################################################
## GLOBAL VARS
## These values will be used globally unless overwritten per node in inventory/all
###############################################################################
# NOTE: 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
@@ -48,73 +42,3 @@ 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
@@ -1,38 +0,0 @@
---
- 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
@@ -1,21 +0,0 @@
---
- 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
@@ -1,20 +0,0 @@
---
- 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
@@ -1,3 +0,0 @@
---
fstrim_timer_dropin_dir: "/etc/systemd/system/fstrim.timer.d"
fstrim_every_calendar: "*:0/15"
@@ -1,31 +0,0 @@
---
- 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
@@ -1,3 +0,0 @@
---
journal_vacuum_size: "100M"
journal_vacuum_time: "3days"
@@ -1,6 +0,0 @@
---
- 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 }}"
@@ -1,8 +0,0 @@
---
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"
@@ -1,5 +0,0 @@
---
- name: Restart journald
systemd:
name: systemd-journald
state: restarted
@@ -1,20 +0,0 @@
---
- 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
@@ -1,7 +0,0 @@
---
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"
@@ -1,26 +0,0 @@
---
- 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
@@ -1,8 +0,0 @@
---
- name: Show journal disk usage
command: journalctl --disk-usage
register: journal_usage
changed_when: false
- debug:
var: journal_usage.stdout
@@ -1,13 +0,0 @@
---
- 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
@@ -1,10 +0,0 @@
---
- 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
+1 -1
View File
@@ -2,7 +2,7 @@
name = "nym-client-core"
version.workspace = true
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2024"
edition = "2021"
rust-version = "1.85"
license.workspace = true
description = "Crate containing core client functionality and configs, used by all other Nym client implentations"
@@ -32,7 +32,6 @@ 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
@@ -556,11 +555,6 @@ 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,
@@ -589,7 +583,6 @@ impl Default for Topology {
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
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,7 +159,6 @@ 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,10 +160,7 @@ 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::MixnetClientStorage;
use crate::client::base_client::storage::helpers::store_client_keys;
use crate::client::base_client::storage::MixnetClientStorage;
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::ClientKeys;
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ClientKeys;
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::ShutdownTracker;
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_topology::HardcodedTopologyProvider;
use nym_task::ShutdownTracker;
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::{UserAgent, nyxd::contract_traits::DkgQueryClient};
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, UserAgent};
use rand::prelude::SliceRandom;
use rand::rngs::OsRng;
use rand::thread_rng;
@@ -220,7 +220,6 @@ 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>,
@@ -251,7 +250,6 @@ 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,
@@ -307,12 +305,6 @@ 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,
@@ -682,7 +674,6 @@ 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 =
@@ -703,46 +694,6 @@ 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 \
@@ -751,15 +702,30 @@ where
return Err(ClientCoreError::InsufficientNetworkTopology(err));
}
// 4. check if the gateway exists (in case we were NOT waiting for it)
let gateway_wait_timeout = if wait_for_gateway {
Some(topology_config.max_startup_gateway_waiting_period)
} else {
None
};
if let Err(err) = topology_refresher
.ensure_contains_routable_egress(local_gateway)
.await
{
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 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());
}
}
if !topology_config.disable_refreshing {
@@ -1058,7 +1024,6 @@ where
shared_topology_accessor.clone(),
self_address.gateway(),
self.wait_for_gateway,
self.wait_for_initial_topology,
&shutdown_tracker.clone(),
)
.await?;
@@ -1230,11 +1195,9 @@ 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]
@@ -1247,13 +1210,11 @@ 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::{CombinedReplyStorage, ReplyStorageBackend, fs_backend},
client::replies::reply_storage::{fs_backend, CombinedReplyStorage, ReplyStorageBackend},
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::{QueryHttpRpcNyxdClient, nyxd};
use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient};
use std::{io, path::Path};
use time::OffsetDateTime;
use tracing::{error, info, trace};
@@ -24,9 +24,7 @@ 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),
});
@@ -95,9 +93,7 @@ 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::ClientKeys;
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ClientKeys;
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::CommonClientPaths;
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
use crate::config::disk_persistence::CommonClientPaths;
use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
use crate::error::ClientCoreError;
@@ -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::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
use rand::{CryptoRng, Rng, rngs::OsRng};
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
use rand::{rngs::OsRng, CryptoRng, Rng};
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,9 +179,7 @@ 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::KeyPairPath;
#[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_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::GatewayClient;
use nym_gateway_client::error::GatewayClientError;
use nym_gateway_client::GatewayClient;
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
use nym_gateway_requests::ClientRequest;
use nym_sphinx::forwarding::packet::MixPacket;
@@ -2,13 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use super::action_controller::{AckActionSender, Action};
use nym_statistics_common::clients::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
use futures::StreamExt;
use nym_gateway_client::AcknowledgementReceiver;
use nym_sphinx::{
acknowledgements::{AckKey, identifier::recover_identifier},
chunking::fragment::{COVER_FRAG_ID, FragmentIdentifier},
acknowledgements::{identifier::recover_identifier, AckKey},
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
};
use nym_task::ShutdownToken;
use std::sync::Arc;
@@ -3,11 +3,11 @@
use super::PendingAcknowledgement;
use crate::client::real_messages_control::acknowledgement_control::RetransmissionRequestSender;
use futures::StreamExt;
use futures::channel::mpsc;
use futures::StreamExt;
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey};
use nym_sphinx::Delay as SphinxDelay;
use nym_sphinx::chunking::fragment::FragmentIdentifier;
use nym_sphinx::Delay as SphinxDelay;
use nym_task::ShutdownToken;
use std::collections::HashMap;
use std::sync::Arc;
@@ -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::ShutdownToken;
use nym_task::connections::TransmissionLane;
use nym_task::ShutdownToken;
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,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use super::{
PendingAcknowledgement, RetransmissionRequestReceiver,
action_controller::{AckActionSender, Action},
PendingAcknowledgement, RetransmissionRequestReceiver,
};
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::{ShutdownToken, connections::TransmissionLane};
use nym_task::{connections::TransmissionLane, ShutdownToken};
use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak};
use tracing::*;
@@ -1,10 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::SentPacketNotificationReceiver;
use super::action_controller::{AckActionSender, Action};
use super::SentPacketNotificationReceiver;
use futures::StreamExt;
use nym_sphinx::chunking::fragment::{COVER_FRAG_ID, FragmentIdentifier};
use nym_sphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
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::ReplySurbWithKeyRotation;
use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessage, ReplyMessage};
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
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_task::ShutdownToken;
use nym_sphinx::Delay;
use nym_task::connections::TransmissionLane;
use nym_task::ShutdownToken;
use nym_topology::{NymRouteProvider, NymTopologyError};
use rand::{CryptoRng, Rng};
use std::collections::HashMap;
@@ -272,9 +272,7 @@ 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::ShutdownToken;
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
use rand::{CryptoRng, Rng, rngs::OsRng};
use nym_task::ShutdownToken;
use rand::{rngs::OsRng, CryptoRng, Rng};
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::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
use nym_task::ShutdownToken;
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
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,9 +230,7 @@ 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
@@ -246,9 +244,7 @@ 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;
}
};
@@ -440,7 +436,7 @@ where
}
}
if let Some(next_delay) = &mut self.next_delay {
if let Some(ref mut 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,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::helpers::{Instant, get_time_now};
use crate::client::helpers::{get_time_now, Instant};
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 nym_crypto::Digest;
use futures::StreamExt;
use nym_crypto::asymmetric::x25519;
use nym_crypto::Digest;
use nym_gateway_client::MixnetMessageReceiver;
use nym_sphinx::anonymous_replies::requests::{
RepliableMessage, RepliableMessageContent, ReplyMessage, ReplyMessageContent,
};
use nym_sphinx::anonymous_replies::{SurbEncryptionKey, encryption_key::EncryptionKeyDigest};
use nym_sphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey};
use nym_sphinx::message::{NymMessage, PlainMessage};
use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
use nym_statistics_common::clients::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
use nym_task::ShutdownToken;
use std::collections::HashSet;
use std::sync::Arc;
@@ -78,19 +78,14 @@ 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;
}
@@ -98,9 +93,7 @@ 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) {
@@ -151,9 +144,7 @@ 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,
@@ -284,9 +275,7 @@ 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) => {
@@ -315,9 +304,7 @@ 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)
}
};
@@ -393,9 +380,7 @@ 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::Config;
use crate::client::replies::reply_controller::key_rotation_helpers::SurbRefreshState;
use crate::client::replies::reply_controller::Config;
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::ReplySurbWithKeyRotation;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
use nym_sphinx::chunking::fragment::FragmentIdentifier;
use nym_task::connections::{ConnectionId, TransmissionLane};
use nym_topology::NymTopologyMetadata;
@@ -50,9 +50,7 @@ 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
}
@@ -202,9 +200,7 @@ 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
@@ -229,13 +225,9 @@ 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;
}
@@ -391,9 +383,7 @@ 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;
};
@@ -469,9 +459,7 @@ 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;
};
@@ -555,9 +543,7 @@ 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;
}
};
@@ -671,13 +657,9 @@ 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}")
}
}
}
@@ -699,10 +681,7 @@ 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;
};
@@ -723,9 +702,7 @@ 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;
}
@@ -733,10 +710,7 @@ 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::ReplySurbWithKeyRotation;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
use nym_task::connections::{ConnectionId, TransmissionLane};
use std::sync::Weak;
@@ -43,9 +43,7 @@ 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;
}
@@ -56,12 +54,7 @@ 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::{ShutdownToken, ShutdownTracker, connections::TransmissionLane};
use nym_task::{connections::TransmissionLane, ShutdownToken, ShutdownTracker};
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::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::{Notify, RwLock, RwLockReadGuard};
#[derive(Debug)]
@@ -63,9 +63,7 @@ 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;
@@ -140,35 +138,6 @@ 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::NymTopology;
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
use nym_topology::NymTopology;
use nym_validator_client::nym_api::NymApiClientExt;
use rand::prelude::SliceRandom;
use rand::thread_rng;
@@ -82,9 +82,7 @@ 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;
}
@@ -157,10 +155,7 @@ 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::{Instant, get_time_now};
use crate::client::helpers::{get_time_now, Instant};
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use nym_sphinx::chunking::fragment::Fragment;
use nym_task::connections::TransmissionLane;
use rand::{Rng, seq::SliceRandom};
use rand::{seq::SliceRandom, Rng};
use std::{
collections::{HashMap, HashSet, VecDeque},
time::Duration,
+5 -13
View File
@@ -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,9 +56,7 @@ 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")]
@@ -129,9 +127,7 @@ 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(
@@ -155,9 +151,7 @@ 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}`")]
@@ -233,9 +227,7 @@ 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 -5
View File
@@ -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::GatewayClient;
use nym_gateway_client::client::GatewayListeners;
use nym_gateway_client::GatewayClient;
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 rand::{Rng, seq::SliceRandom};
use nym_validator_client::UserAgent;
use rand::{seq::SliceRandom, Rng};
#[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::Instant;
#[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_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(target_arch = "wasm32")]
use wasmtimer::std::Instant;
+1 -1
View File
@@ -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::ClientKeys;
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ClientKeys;
use crate::error::ClientCoreError;
use crate::init::helpers::{
choose_gateway_by_latency, get_specified_gateway, uniformly_random_gateway,
+2 -2
View File
@@ -1,8 +1,8 @@
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::key_manager::ClientKeys;
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ClientKeys;
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::SharedSymmetricKey;
use nym_gateway_client::client::{GatewayListeners, InitGatewayClient};
use nym_gateway_client::SharedSymmetricKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_topology::node::RoutingNode;
use nym_validator_client::client::IdentityKey;
-1
View File
@@ -1,4 +1,3 @@
#![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,7 +1,6 @@
// 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, SkimmedNodeV1, SkimmedNodesWithMetadata,
NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNode, 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<SkimmedNodeV1>, ValidatorClientError> {
pub async fn get_basic_mixnodes(&self) -> Result<Vec<SkimmedNode>, 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<SkimmedNodeV1>, ValidatorClientError> {
pub async fn get_basic_gateways(&self) -> Result<Vec<SkimmedNode>, 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<SkimmedNodeV1>, ValidatorClientError> {
) -> Result<Vec<SkimmedNode>, 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<SkimmedNodeV1>, ValidatorClientError> {
) -> Result<Vec<SkimmedNode>, 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<SkimmedNodeV1>, ValidatorClientError> {
) -> Result<Vec<SkimmedNode>, 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<SkimmedNodeV1>, ValidatorClientError> {
pub async fn get_all_basic_nodes(&self) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
self.get_all_basic_nodes_with_metadata()
.await
.map(|res| res.nodes)
@@ -4,7 +4,6 @@
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,
@@ -21,14 +20,11 @@ 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,
@@ -40,14 +36,17 @@ pub use nym_api_requests::{
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
MixnodeUptimeHistoryResponse, StakeSaturationResponse, UptimeResponse,
},
nym_nodes::{
CachedNodesResponse, NodesByAddressesRequestBody, NodesByAddressesResponse,
PaginatedCachedNodesResponseV1, PaginatedCachedNodesResponseV2, SemiSkimmedNodeV1,
SemiSkimmedNodeV3, SemiSkimmedNodesWithMetadata, SkimmedNodeV1,
},
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata, SkimmedNode},
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;
@@ -391,7 +390,7 @@ pub trait NymApiClientExt: ApiClient {
#[deprecated]
#[tracing::instrument(level = "debug", skip_all)]
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNodeV1>, NymAPIError> {
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
self.get_json(
&[
routes::V1_API_VERSION,
@@ -407,7 +406,7 @@ pub trait NymApiClientExt: ApiClient {
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_basic_gateways(&self) -> Result<CachedNodesResponse<SkimmedNodeV1>, NymAPIError> {
async fn get_basic_gateways(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
self.get_json(
&[
routes::V1_API_VERSION,
@@ -444,7 +443,7 @@ pub trait NymApiClientExt: ApiClient {
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if no_legacy {
@@ -486,7 +485,7 @@ pub trait NymApiClientExt: ApiClient {
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if no_legacy {
@@ -528,7 +527,7 @@ pub trait NymApiClientExt: ApiClient {
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if no_legacy {
@@ -570,7 +569,7 @@ pub trait NymApiClientExt: ApiClient {
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if no_legacy {
@@ -613,7 +612,7 @@ pub trait NymApiClientExt: ApiClient {
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if no_legacy {
@@ -655,7 +654,7 @@ pub trait NymApiClientExt: ApiClient {
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if no_legacy {
@@ -696,7 +695,7 @@ pub trait NymApiClientExt: ApiClient {
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if no_legacy {
@@ -734,7 +733,7 @@ pub trait NymApiClientExt: ApiClient {
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if no_legacy {
@@ -771,7 +770,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
) -> Result<PaginatedCachedNodesResponseV2<SemiSkimmedNodeV1>, NymAPIError> {
) -> Result<PaginatedCachedNodesResponseV2<SemiSkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if no_legacy {
@@ -798,21 +797,6 @@ 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", &params)
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnode_report(
@@ -3,7 +3,6 @@
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";
@@ -130,7 +130,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
let req = QueryBalanceRequest {
address: address.to_string(),
denom: search_denom,
denom: search_denom.to_string(),
};
let res = self
@@ -199,18 +199,6 @@ 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,8 +1,6 @@
// 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;
-5
View File
@@ -4,7 +4,6 @@
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use strum::IntoEnumIterator as _;
use thiserror::Error;
use time::{Date, OffsetDateTime};
@@ -316,10 +315,6 @@ impl TicketType {
_ => Err(UnknownTicketType),
}
}
pub fn exposed_iter() -> impl Iterator<Item = TicketType> {
TicketType::iter()
}
}
impl From<TicketType> for TicketTypeRepr {
+4 -2
View File
@@ -511,12 +511,14 @@ mod tests {
#[test]
fn test_key_conversion() {
let dalek_kp = KeyPair::new(&mut rand::thread_rng());
let dalek_kp = super::KeyPair::new(&mut rand::thread_rng());
let mut dalek_private_key_bytes = dalek_kp.private_key().as_bytes().to_owned();
libcrux_curve25519::clamp(&mut dalek_private_key_bytes);
let libcrux_private_key = DHPrivateKey::from_bytes(&dalek_private_key_bytes).unwrap();
let libcrux_private_key =
libcrux_psq::handshake::types::DHPrivateKey::from_bytes(&dalek_private_key_bytes)
.unwrap();
let libcrux_public_key = libcrux_private_key.to_public();
assert_eq!(libcrux_public_key.as_ref(), dalek_kp.public_key.as_bytes());
-2
View File
@@ -1,8 +1,6 @@
// 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
View File
@@ -1,6 +1,5 @@
// 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;
-4
View File
@@ -1,9 +1,6 @@
// 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,
@@ -1404,7 +1401,6 @@ 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,
+5 -11
View File
@@ -22,16 +22,6 @@ 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 {
@@ -191,7 +181,11 @@ impl NymNetworkDetails {
// Consider caching this process (lazy static)
NymNetworkDetails {
network_name: mainnet::NETWORK_NAME.into(),
chain_details: ChainDetails::mainnet(),
chain_details: ChainDetails {
bech32_account_prefix: mainnet::BECH32_PREFIX.into(),
mix_denom: mainnet::MIX_DENOM.into(),
stake_denom: mainnet::STAKE_DENOM.into(),
},
endpoints: mainnet::validators(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS),
+2 -3
View File
@@ -12,9 +12,9 @@ num_enum = { workspace = true }
strum = { workspace = true }
# internal
nym-crypto = { workspace = true, features = ["hashing"] }
nym-crypto = { path = "../crypto", features = ["hashing"] }
nym-kkt-ciphersuite = { workspace = true, features = ["digests"] }
nym-kkt-context = { workspace = true }
nym-kkt-context = { path = "../nym-kkt-context" }
nym-pemstore = { workspace = true }
libcrux-kem = { workspace = true }
@@ -30,7 +30,6 @@ libcrux-ml-kem = { workspace = true }
[dev-dependencies]
rand_chacha = "0.9.0"
anyhow = { workspace = true }
nym-test-utils = { workspace = true }
[lints]
+2 -3
View File
@@ -10,7 +10,6 @@ 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 {
@@ -108,7 +107,7 @@ impl Carrier {
&self.tx_key,
plaintext,
&mut output_buffer,
CARRIER_KKT_AAD,
b"kkt-carrier-v1",
&as_nonce_bytes(self.tx_counter),
)?;
@@ -127,7 +126,7 @@ impl Carrier {
&self.rx_key,
&mut output_buffer,
ciphertext,
CARRIER_KKT_AAD,
b"kkt-carrier-v1",
&as_nonce_bytes(self.rx_counter),
)?;
+11 -20
View File
@@ -3,7 +3,6 @@
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;
@@ -16,40 +15,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")]
@@ -63,14 +62,6 @@ 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),
}
+8 -1
View File
@@ -140,7 +140,14 @@ pub fn initiator_process(
},
};
Ok(KKTFrame::new(context, body, payload.unwrap_or_default()))
Ok(KKTFrame::new(
context,
body,
match payload {
Some(payload_vec) => payload_vec,
None => Vec::with_capacity(0),
},
))
}
pub fn initiator_ingest_response(
+57 -124
View File
@@ -16,6 +16,9 @@ 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,
@@ -25,13 +28,9 @@ 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_one_way_encrypted_carrier() {
fn test_kkt_psq_e2e_encrypted_carrier() {
let mut rng = rand09::rng();
let mut payload: Vec<u8> = vec![0u8; 900_000];
@@ -47,6 +46,7 @@ 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,13 +63,24 @@ 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 init_hashes = BTreeMap::new();
let _i_dir_hash_mlkem = hash_encapsulation_key(
hash_function,
HashLength::Default.value(),
initiator_mlkem_keypair.public_key().as_slice(),
);
let _i_dir_hash_mceliece = hash_encapsulation_key(
hash_function,
HashLength::Default.value(),
initiator_mceliece_keypair.pk.as_ref(),
);
let responder = KKTResponder::new(
&responder_x25519_keypair,
&responder_kem,
&init_hashes,
&[
HashFunction::Blake3,
HashFunction::SHA256,
@@ -113,6 +124,41 @@ 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
{
@@ -145,110 +191,7 @@ 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,
@@ -257,12 +200,9 @@ mod test {
None,
)
.unwrap();
let (mut initiator, request) = KKTInitiator::generate_mutual_request(
let (mut initiator, request) = KKTInitiator::generate_one_way_request(
&mut rng,
ciphersuite,
initiator_kem
.encoded_encapsulation_key(KEM::McEliece)
.unwrap(),
&responder_x25519_keypair.pk,
&r_dir_hash_mceliece,
1u8,
@@ -273,16 +213,9 @@ mod test {
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::McEliece)
.unwrap()
.as_bytes()
);
// 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)
+2 -2
View File
@@ -114,14 +114,14 @@ impl KKTRequestPlaintext {
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(Self::SIZE);
let mut out = Vec::with_capacity(x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN);
out.extend_from_slice(self.dh_pubkey.as_ref());
out.extend_from_slice(self.masked_version_bytes.as_slice());
out
}
pub(crate) fn try_from_bytes(b: &[u8]) -> Result<Self, KKTError> {
if b.len() != Self::SIZE {
if b.len() != x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN {
return Err(KKTError::FrameDecodingError {
info: "the KKTRequest frame has invalid length".to_string(),
});
+5 -3
View File
@@ -4,13 +4,15 @@
//! Post-Quantum Re-Key Protocol
/// This module implements a stateless post-quantum re-keying protocol in one round-trip.
/// We currently support MlKem768.
/// We currently support MlKem768 and XWing.
///
/// This protocol is safe if it runs under a trusted secure channel.
///
/// Bandwidth costs:
/// Request (MlKem768): 1216 bytes
/// Response (MlKem768): 1088 bytes
/// Request (XWing): 1248 bytes
/// Response (XWing): 1120 bytes
use libcrux_kem::*;
use nym_crypto::hkdf::blake3::derive_key_blake3;
use nym_kkt_ciphersuite::{KEM, mceliece, ml_kem768, x25519, xwing};
@@ -58,7 +60,7 @@ impl RekeyInitiator {
///
/// Inputs:
/// rng: something that implements CryptoRng + RngCore
/// kem: a KEM algorithm (we currently support MlKem768 only)
/// kem: a KEM algorithm (we currently support MlKem768 and XWing)
///
/// Outputs:
/// RekeyInitiator: A struct which contains the decapsulation key, the salt and the kem algorithm in use.
@@ -169,7 +171,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 xwing
// If message length is 1248 (32 + 1216) then the algorithm should be MlKem768
xwing::PUBLIC_KEY_LENGTH => Algorithm::XWingKemDraft06,
// We don't support McEliece because the keys are massive.
// If this is a deal-breaker, users can start a new session with PSQ which can use McEliece.
+16 -26
View File
@@ -10,8 +10,7 @@ use crate::{
frame::KKTFrame,
};
use libcrux_psq::handshake::types::DHKeyPair;
use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, KEM, KEMKeyDigests, SignatureScheme};
use std::collections::BTreeMap;
use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, SignatureScheme};
/// Representation of a KKT Responder
pub struct KKTResponder<'a> {
@@ -21,9 +20,6 @@ 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>,
@@ -38,7 +34,6 @@ 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],
@@ -64,28 +59,12 @@ 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,
@@ -123,7 +102,6 @@ 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;
@@ -133,8 +111,12 @@ impl<'a> KKTResponder<'a> {
let (local_context, remote_encapsulation_key) = match remote_context.mode() {
KKTMode::OneWay => responder_ingest_message(None, remote_frame)?,
KKTMode::Mutual => {
let digest = self.expected_initiator_kem_digest(remote_context.ciphersuite())?;
responder_ingest_message(Some(digest), remote_frame)?
// 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)?
}
};
@@ -146,7 +128,7 @@ impl<'a> KKTResponder<'a> {
};
// for now the response payload is empty
let response_payload = Vec::new();
let response_payload = Vec::with_capacity(0);
let frame = KKTFrame::new(local_context, kem_key, response_payload);
@@ -180,6 +162,14 @@ 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));
};
+7 -13
View File
@@ -1,14 +1,8 @@
[package]
name = "nym-lp"
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
version = "0.1.0"
edition = { workspace = true }
license = { workspace = true }
publish = false
[dependencies]
@@ -17,11 +11,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 = { workspace = true, features = ["hashing"] }
nym-kkt = { workspace = true }
nym-crypto = { path = "../crypto", features = ["hashing"] }
nym-kkt = { path = "../nym-kkt" }
nym-kkt-ciphersuite = { workspace = true }
# libcrux dependencies for PSQ (Post-Quantum PSK derivation)
@@ -34,7 +28,7 @@ zeroize = { workspace = true, features = ["zeroize_derive"] }
nym-test-utils = { workspace = true, optional = true }
[dev-dependencies]
criterion = { workspace = true, features = ["html_reports"] }
criterion = { version = "0.5", features = ["html_reports"] }
nym-test-utils = { workspace = true }
anyhow = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
+9 -9
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::LpError;
use crate::packet::{EncryptedLpPacket, InnerHeader, LpFrame, LpHeader, LpPacket};
use crate::packet::{EncryptedLpPacket, InnerHeader, LpHeader, LpMessage, 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.frame().len());
let mut plaintext = BytesMut::with_capacity(InnerHeader::SIZE + packet.message().len());
packet.header().inner.encode(&mut plaintext);
packet.frame().encode(&mut plaintext);
packet.message().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 frame = LpFrame::decode(payload)?;
let message = LpMessage::decode(payload)?;
Ok(LpPacket::new(
LpHeader {
outer: packet.outer_header(),
inner: inner_header,
},
frame,
message,
))
}
@@ -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, LpFrame, LpHeader, LpPacket};
use crate::packet::{EncryptedLpPacket, LpHeader, LpMessage, LpPacket};
use crate::peer::mock_peers;
use crate::psq::initiator::{build_psq_ciphersuite, build_psq_principal};
use crate::psq::{PSQ_MSG2_SIZE, psq_msg1_size, responder};
@@ -261,7 +261,7 @@ mod tests {
// happy path
let packet = LpPacket::new(
LpHeader::new(123, 0, 1),
LpFrame::new_opaque(b"foomp".to_vec()),
LpMessage::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),
LpFrame::new_opaque(b"foomp".to_vec()),
LpMessage::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),
LpFrame::new_opaque(b"foomp".to_vec()),
LpMessage::new_opaque(b"foomp".to_vec()),
);
let ciphertext3 = encrypt_lp_packet(packet, &mut resp_transport).unwrap();
let malformed = EncryptedLpPacket::new(ciphertext3.outer_header(), vec![]);
+79
View File
@@ -0,0 +1,79 @@
// 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))),
}
}
}
+3 -3
View File
@@ -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"
)]
+10 -9
View File
@@ -11,6 +11,7 @@ pub mod replay;
pub mod session;
mod session_integration;
pub mod session_manager;
pub mod state_machine;
pub mod transport;
pub use error::LpError;
@@ -20,8 +21,9 @@ pub use nym_kkt_ciphersuite::{
#[cfg(any(feature = "mock", test))]
pub use replay::{ReceivingKeyCounterValidator, ReplayError};
pub use session::LpTransportSession;
pub use session::LpSession;
pub use session_manager::SessionManager;
pub use state_machine::LpStateMachine;
#[cfg(any(feature = "mock", test))]
use nym_test_utils::helpers::u64_seeded_rng_09;
@@ -37,8 +39,8 @@ use libcrux_psq::{Channel, IntoSession};
#[cfg(any(feature = "mock", test))]
pub struct SessionsMock {
pub initiator: LpTransportSession,
pub responder: LpTransportSession,
pub initiator: LpSession,
pub responder: LpSession,
}
#[cfg(any(feature = "mock", test))]
@@ -107,18 +109,17 @@ impl SessionsMock {
initiator_authenticator,
responder_ecdh_pk: resp_remote.x25519_public,
responder_pq_pk: Some(encapsulation_key),
initiator_pq_pk: None,
};
SessionsMock {
initiator: LpTransportSession::new(
initiator: LpSession::new(
initiator.into_session().unwrap(),
binding.clone(),
receiver_index,
1,
)
.unwrap(),
responder: LpTransportSession::new(
responder: LpSession::new(
responder.into_session().unwrap(),
binding,
receiver_index,
@@ -133,18 +134,18 @@ impl SessionsMock {
}
// we just need a dummy 'valid' session for simpler tests
pub fn mock_initiator() -> LpTransportSession {
pub fn mock_initiator() -> LpSession {
Self::mock_post_handshake(KEM::default()).initiator
}
}
#[cfg(any(feature = "mock", test))]
pub fn sessions_for_tests() -> (LpTransportSession, LpTransportSession) {
pub fn sessions_for_tests() -> (LpSession, LpSession) {
let sessions = SessionsMock::mock_post_handshake(KEM::default());
(sessions.initiator, sessions.responder)
}
#[cfg(any(feature = "mock", test))]
pub fn mock_session_for_test() -> LpTransportSession {
pub fn mock_session_for_test() -> LpSession {
SessionsMock::mock_initiator()
}
+4 -4
View File
@@ -11,8 +11,8 @@ pub enum MalformedLpPacketError {
#[error("provided insufficient data to fully deserialise the struct")]
InsufficientData,
#[error("{0} is not a valid LpFrameKind value")]
InvalidLpFrameKind(u16),
#[error("{0} is not a valid LpDataKind")]
InvalidLpDataKind(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(frame_kind: u16) -> Self {
MalformedLpPacketError::InvalidLpFrameKind(frame_kind)
pub fn invalid_data_kind(message_type: u16) -> Self {
MalformedLpPacketError::InvalidLpDataKind(message_type)
}
}
@@ -7,32 +7,32 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
#[derive(Debug, Clone, PartialEq)]
pub struct LpFrameHeader {
pub kind: LpFrameKind,
pub frame_attributes: [u8; 14],
pub struct LpMessageHeader {
pub kind: LpMessageType,
pub message_attributes: [u8; 14],
}
impl LpFrameHeader {
impl LpMessageHeader {
pub const SIZE: usize = 16; // message_kind(2) + message_attributes(14)
pub fn new(kind: LpFrameKind, frame_attributes: [u8; 14]) -> Self {
pub fn new(kind: LpMessageType, message_attributes: [u8; 14]) -> Self {
Self {
kind,
frame_attributes,
message_attributes,
}
}
pub fn new_no_attributes(kind: LpFrameKind) -> Self {
pub fn new_no_attributes(kind: LpMessageType) -> Self {
Self {
kind,
frame_attributes: [0; 14],
message_attributes: [0; 14],
}
}
/// Encode directly into a BytesMut buffer
pub fn encode(&self, dst: &mut BytesMut) {
dst.put_u16_le(self.kind as u16);
dst.put_slice(&self.frame_attributes);
dst.put_slice(&self.message_attributes);
}
pub fn parse(src: &[u8]) -> Result<Self, MalformedLpPacketError> {
@@ -41,35 +41,35 @@ impl LpFrameHeader {
}
let raw_kind = u16::from_le_bytes([src[0], src[1]]);
let kind = LpFrameKind::try_from(raw_kind)
let kind = LpMessageType::try_from(raw_kind)
.map_err(|_| MalformedLpPacketError::invalid_data_kind(raw_kind))?;
#[allow(clippy::unwrap_used)]
let message_attributes = src[2..16].try_into().unwrap();
Ok(Self {
kind,
frame_attributes: message_attributes,
message_attributes,
})
}
}
/// Represent application data being sent in Transport mode
#[derive(Debug, Clone, PartialEq)]
pub struct LpFrame {
pub header: LpFrameHeader,
pub struct LpMessage {
pub header: LpMessageHeader,
pub content: Bytes,
}
impl AsRef<[u8]> for LpFrame {
impl AsRef<[u8]> for LpMessage {
fn as_ref(&self) -> &[u8] {
&self.content
}
}
impl LpFrame {
pub fn new(kind: LpFrameKind, content: impl Into<Bytes>) -> Self {
impl LpMessage {
pub fn new(kind: LpMessageType, content: impl Into<Bytes>) -> Self {
Self {
header: LpFrameHeader::new_no_attributes(kind),
header: LpMessageHeader::new_no_attributes(kind),
content: content.into(),
}
}
@@ -81,103 +81,40 @@ impl LpFrame {
}
pub fn decode(src: &[u8]) -> Result<Self, MalformedLpPacketError> {
let header = LpFrameHeader::parse(src)?;
let content = src[LpFrameHeader::SIZE..].to_vec().into();
let header = LpMessageHeader::parse(src)?;
let content = src[LpMessageHeader::SIZE..].to_vec().into();
Ok(Self { header, content })
}
pub fn kind(&self) -> LpFrameKind {
pub fn kind(&self) -> LpMessageType {
self.header.kind
}
pub fn new_opaque(content: impl Into<Bytes>) -> Self {
Self::new(LpFrameKind::Opaque, content)
Self::new(LpMessageType::Opaque, content)
}
pub fn new_registration(data: impl Into<Bytes>) -> Self {
Self::new(LpFrameKind::Registration, data)
Self::new(LpMessageType::Registration, data)
}
pub fn new_forward(data: impl Into<Bytes>) -> Self {
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(),
}
Self::new(LpMessageType::Forward, data)
}
pub(crate) fn len(&self) -> usize {
LpFrameHeader::SIZE + self.content.len()
LpMessageHeader::SIZE + self.content.len()
}
}
/// Represent kind of application data being sent in Transport mode
#[derive(Clone, Copy, PartialEq, Eq, Debug, IntoPrimitive, TryFromPrimitive)]
#[repr(u16)]
pub enum LpFrameKind {
pub enum LpMessageType {
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)]
+10 -10
View File
@@ -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) frame: LpFrame,
pub(crate) message: LpMessage,
}
impl Debug for LpPacket {
@@ -91,16 +91,16 @@ impl Debug for LpPacket {
}
impl LpPacket {
pub fn new(header: LpHeader, frame: LpFrame) -> Self {
Self { header, frame }
pub fn new(header: LpHeader, message: LpMessage) -> Self {
Self { header, message }
}
pub fn frame(&self) -> &LpFrame {
&self.frame
pub fn message(&self) -> &LpMessage {
&self.message
}
pub fn into_frame(self) -> LpFrame {
self.frame
pub fn into_message(self) -> LpMessage {
self.message
}
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.frame.encode(dst)
self.message.encode(dst)
}
}
+1 -21
View File
@@ -2,7 +2,6 @@
// 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;
@@ -42,18 +41,6 @@ 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
}
@@ -82,10 +69,7 @@ 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",
&bs58::encode(self.x25519.pk.as_ref()).into_string(),
)
.field("x25519", &self.x25519.pk)
.field("kem_keypairs", &self.kem_keypairs)
.finish()
}
@@ -143,10 +127,6 @@ 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 {
+23 -27
View File
@@ -29,7 +29,7 @@ pub struct LpPeerConfig {
// Determine the hop id.
// Should be 0 if node_initiator is true
// Should be > 1 && < 16 if is_exit is true
// Should be > 1 if is_exit is true
hop_id: u8,
// Determine if the recipient should be an exit node
@@ -65,7 +65,6 @@ 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.
@@ -80,7 +79,6 @@ 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.
@@ -132,7 +130,6 @@ impl LpPeerConfig {
rng.random(),
)
}
fn build(
hop_id: u8,
is_exit: bool,
@@ -150,7 +147,6 @@ impl LpPeerConfig {
seed,
}
}
fn build_checked(
hop_id: u8,
is_exit: bool,
@@ -202,37 +198,37 @@ impl LpPeerConfig {
}
pub fn serialize(&self) -> [u8; LP_PEER_CONFIG_SIZE] {
let mut output_bytes = [0u8; LP_PEER_CONFIG_SIZE];
output_bytes[0..4].copy_from_slice(&self.pack_config());
let mut output_bytes: [u8; LP_PEER_CONFIG_SIZE] = [0u8; LP_PEER_CONFIG_SIZE];
output_bytes[0..4].copy_from_slice(self.pack_config().as_slice());
output_bytes[4..].copy_from_slice(&self.seed);
output_bytes
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, LpError> {
if bytes.len() != LP_PEER_CONFIG_SIZE {
return Err(LpError::DeserializationError(format!(
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] {
+21 -210
View File
@@ -11,7 +11,7 @@ use crate::psq::{
};
use crate::session::PersistentSessionBinding;
use crate::transport::traits::LpHandshakeChannel;
use crate::{LpError, LpTransportSession};
use crate::{LpError, LpSession};
use libcrux_psq::handshake::RegistrationInitiator;
use libcrux_psq::handshake::builders::{
CiphersuiteBuilder, InitiatorCiphersuite, PrincipalBuilder,
@@ -24,31 +24,9 @@ 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>(
@@ -99,25 +77,6 @@ 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();
@@ -144,7 +103,7 @@ where
Ok(resp.into())
}
pub async fn complete_handshake(self) -> Result<LpTransportSession, LpError>
pub async fn complete_handshake(self) -> Result<LpSession, LpError>
where
S: LpHandshakeChannel + Unpin,
{
@@ -152,10 +111,7 @@ where
self.complete_handshake_with_rng(&mut rng).await
}
pub async fn complete_handshake_with_rng<R>(
mut self,
rng: &mut R,
) -> Result<LpTransportSession, LpError>
pub async fn complete_handshake_with_rng<R>(mut self, rng: &mut R) -> Result<LpSession, LpError>
where
S: LpHandshakeChannel + Unpin,
R: rand09::CryptoRng,
@@ -163,7 +119,7 @@ where
let ciphersuite = self.inner_state.local_peer.ciphersuite();
let kem = ciphersuite.kem();
let lp_peer_config = self.lp_peer_config(rng)?;
let lp_peer_config = LpPeerConfig::new_client_to_entry(rng, false);
// 1. retrieve the expected kem key hash. if we don't know it,
let dir_hash = self
@@ -172,34 +128,16 @@ where
.expected_kem_key_hash(ciphersuite)?;
// 2. prepare and send 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);
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
debug!("sending KKT request");
self.send_kkt_request(kkt_request).await?;
@@ -216,7 +154,7 @@ where
let conn = self.inner_state.connection;
// note: the clone is cheap due to internal Arcs
let resp_encapsulation_key = response.encapsulation_key.clone();
let encapsulation_key = response.encapsulation_key.clone();
// build the PSQ initiator
let initiator_ciphersuite = build_psq_ciphersuite(
@@ -253,18 +191,17 @@ where
let initiator_authenticator = Authenticator::Dh(self.inner_state.local_peer.x25519().pk);
let receiver_index = lp_peer_config
.derive_receiver_index(&initiator_authenticator, &resp_encapsulation_key)?;
let receiver_index =
lp_peer_config.derive_receiver_index(&initiator_authenticator, &encapsulation_key)?;
let binding = PersistentSessionBinding {
initiator_authenticator,
responder_ecdh_pk: self.initiator_data.remote_peer.x25519_public,
responder_pq_pk: Some(resp_encapsulation_key),
initiator_pq_pk: init_kem_key,
responder_pq_pk: Some(encapsulation_key),
};
let psq_session = psq_initiator.into_session()?;
LpTransportSession::new(psq_session, binding, receiver_index, protocol)
LpSession::new(psq_session, binding, receiver_index, protocol)
}
}
@@ -281,7 +218,6 @@ 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<()> {
@@ -289,8 +225,6 @@ 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();
@@ -304,8 +238,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, HandshakeMode::OneWayEntry)?;
let handshake_init =
PSQHandshakeState::new(conn_init, init).as_initiator(initiator_data);
let mut init_rng = DeterministicRng09Send::new(u64_seeded_rng_09(1));
@@ -330,7 +264,6 @@ mod tests {
let kkt_responder = KKTResponder::new(
responder_x25519_keypair,
resp_keys,
&dir_hash_init,
&supported_hash,
&supported_sigs,
&[1],
@@ -404,126 +337,4 @@ 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(())
}
}
+6 -297
View File
@@ -4,16 +4,13 @@
use crate::packet::version;
use crate::peer::{LpLocalPeer, LpRemotePeer};
use crate::transport::traits::LpHandshakeChannel;
use nym_kkt_ciphersuite::{HashFunction, IntoEnumIterator, KEM, KEMKeyDigests, SignatureScheme};
use std::collections::BTreeMap;
use nym_kkt_ciphersuite::{HashFunction, IntoEnumIterator, KEM, SignatureScheme};
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;
@@ -71,19 +68,6 @@ 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 {
@@ -93,7 +77,6 @@ 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(),
}
}
}
@@ -109,20 +92,11 @@ where
}
}
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 {
pub fn as_initiator(self, initiator_data: InitiatorData) -> PSQHandshakeStateInitiator<'a, S> {
PSQHandshakeStateInitiator {
initiator_data,
inner_state: self,
mode,
})
}
}
pub fn as_responder(self, responder_data: ResponderData) -> PSQHandshakeStateResponder<'a, S> {
@@ -150,7 +124,6 @@ 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]
@@ -170,10 +143,8 @@ 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),
HandshakeMode::OneWayEntry,
)?;
let handshake_init = PSQHandshakeState::new(conn_init, init)
.as_initiator(InitiatorData::new(1, resp_remote));
let handshake_resp =
PSQHandshakeState::new(conn_resp, resp).as_responder(ResponderData::default());
@@ -226,82 +197,6 @@ 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() {
@@ -314,7 +209,6 @@ 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();
@@ -329,7 +223,6 @@ mod tests {
let kkt_responder = KKTResponder::new(
responder_x25519_keypair,
resp_keys,
&dir_hash_init,
&supported_hash,
&supported_sigs,
&[protocol_version],
@@ -476,188 +369,4 @@ 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 -155
View File
@@ -11,7 +11,7 @@ use crate::psq::{
};
use crate::session::PersistentSessionBinding;
use crate::transport::traits::{HandshakeMessage, LpHandshakeChannel};
use crate::{LpError, LpTransportSession};
use crate::{LpError, LpSession};
use libcrux_psq::handshake::Responder;
use libcrux_psq::handshake::builders::{
CiphersuiteBuilder, PrincipalBuilder, ResponderCiphersuite,
@@ -77,14 +77,12 @@ impl<'a, S> PSQHandshakeStateResponder<'a, S>
where
S: LpHandshakeChannel + Unpin,
{
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?
/// 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;
let req = self
.inner_state
@@ -95,16 +93,6 @@ 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
@@ -117,7 +105,6 @@ 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,
@@ -146,7 +133,7 @@ where
Ok(msg.into_bytes())
}
pub async fn complete_handshake(self) -> Result<LpTransportSession, LpError>
pub async fn complete_handshake(self) -> Result<LpSession, LpError>
where
S: LpHandshakeChannel + Unpin,
{
@@ -154,27 +141,17 @@ where
self.complete_handshake_with_rng(&mut rng).await
}
pub async fn complete_handshake_with_rng<R>(
mut self,
rng: &mut R,
) -> Result<LpTransportSession, LpError>
pub async fn complete_handshake_with_rng<R>(mut self, rng: &mut R) -> Result<LpSession, LpError>
where
S: LpHandshakeChannel + Unpin,
R: rand09::CryptoRng,
{
// 1. receive and process KKTRequest
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?
};
let kkt_request = self.receive_one_way_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)?;
@@ -228,11 +205,10 @@ 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()?;
LpTransportSession::new(
LpSession::new(
psq_session,
binding,
receiver_index,
@@ -372,124 +348,4 @@ 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(())
}
}
+20 -171
View File
@@ -6,10 +6,9 @@
//! This module implements session management functionality, including replay protection
use crate::codec::{decrypt_lp_packet, encrypt_lp_packet};
use crate::packet::{EncryptedLpPacket, LpFrame, LpHeader, LpPacket};
use crate::packet::{EncryptedLpPacket, LpHeader, LpMessage, LpPacket};
use crate::peer::{LpLocalPeer, LpRemotePeer};
use crate::peer_config::LpReceiverIndex;
use crate::psq::initiator::HandshakeMode;
use crate::psq::{
InitiatorData, PSQHandshakeState, PSQHandshakeStateInitiator, PSQHandshakeStateResponder,
ResponderData,
@@ -20,38 +19,15 @@ 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..
/// A session in the Lewes Protocol, handling connection state with Noise.
///
/// Sessions manage connection state, including LP replay protection.
/// Each session has a unique receiving index and sending index for connection identification.
pub struct LpTransportSession {
pub struct LpSession {
/// The underlying established session
psq_session: Session,
@@ -86,9 +62,6 @@ 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 {
@@ -114,7 +87,7 @@ impl<'a> From<&'a PersistentSessionBinding> for SessionBinding<'a> {
}
}
impl Debug for LpTransportSession {
impl Debug for LpSession {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LpSession")
.field("session_id", &self.psq_session.identifier())
@@ -127,7 +100,7 @@ impl Debug for LpTransportSession {
}
}
impl LpTransportSession {
impl LpSession {
/// Creates a new session after completed KTT/PSQ exchange
pub fn new(
mut psq_session: Session,
@@ -140,7 +113,7 @@ impl LpTransportSession {
.transport_channel()
.map_err(|inner| LpError::TransportDerivationFailure { inner })?;
Ok(LpTransportSession {
Ok(LpSession {
psq_session,
session_binding,
active_transport: transport,
@@ -157,34 +130,12 @@ impl LpTransportSession {
local_peer: LpLocalPeer,
remote_peer: LpRemotePeer,
remote_protocol_version: u8,
mode: HandshakeMode,
) -> Result<PSQHandshakeStateInitiator<'_, S>, LpError>
) -> PSQHandshakeStateInitiator<'_, S>
where
S: LpHandshakeChannel + Unpin,
{
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,
)
PSQHandshakeState::new(connection, local_peer)
.as_initiator(InitiatorData::new(remote_protocol_version, remote_peer))
}
/// Helper function to create `PSQHandshakeState` for the handshake responder
@@ -198,19 +149,6 @@ impl LpTransportSession {
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
}
@@ -234,10 +172,10 @@ impl LpTransportSession {
self.protocol_version
}
pub fn next_packet(&mut self, frame: LpFrame) -> Result<LpPacket, LpError> {
pub fn next_packet(&mut self, message: LpMessage) -> Result<LpPacket, LpError> {
let counter = self.next_counter();
let header = LpHeader::new(self.receiver_index(), counter, self.protocol_version);
let packet = LpPacket::new(header, frame);
let packet = LpPacket::new(header, message);
Ok(packet)
}
@@ -299,19 +237,22 @@ impl LpTransportSession {
self.receiving_counter.current_packet_cnt()
}
/// Wrap the provided `LpFrame` into an `LpPacket` and encrypt its content using the established transport session
/// to produce an `EncryptedLpPacket`
/// Encrypts a produced application using the established transport session
/// and produce an `EncryptedLpPacket`
///
/// # Arguments
///
/// * `frame` - structured `LpFrame` to wrap and encrypt
/// * `data` - plaintext data to 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 wrap_lp_frame(&mut self, frame: LpFrame) -> Result<EncryptedLpPacket, LpError> {
let packet = self.next_packet(frame)?;
pub(crate) fn encrypt_application_data(
&mut self,
data: LpMessage,
) -> Result<EncryptedLpPacket, LpError> {
let packet = self.next_packet(data)?;
encrypt_lp_packet(packet, &mut self.active_transport)
}
@@ -319,7 +260,7 @@ impl LpTransportSession {
///
/// # Arguments
///
/// * `packet` - The encrypted packet
/// * `ciphertext` - The encrypted packet
///
/// # Returns
///
@@ -331,41 +272,6 @@ impl LpTransportSession {
) -> 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)]
@@ -455,61 +361,4 @@ 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");
}
}
}
}
+106 -36
View File
@@ -1,7 +1,7 @@
#[cfg(test)]
mod tests {
use crate::packet::{EncryptedLpPacket, LpFrame};
use crate::session::{LpAction, LpInput};
use crate::packet::{EncryptedLpPacket, LpMessage};
use crate::state_machine::{LpAction, LpInput, LpStateBare};
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) -> LpFrame;
fn data(self) -> LpMessage;
}
impl ActionExtract for LpAction {
@@ -21,8 +21,8 @@ mod tests {
}
}
fn data(self) -> LpFrame {
if let LpAction::DeliverFrame(data) = self {
fn data(self) -> LpMessage {
if let LpAction::DeliverData(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
.insert_session(sessions.initiator)
.create_session_state_machine(sessions.initiator)
.unwrap();
let peer_b_sm = session_manager_2
.insert_session(sessions.responder)
.create_session_state_machine(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_frame(peer_a_sm, LpFrame::new_opaque(plaintext_a.clone()))
.send_data(peer_a_sm, LpMessage::new_opaque(plaintext_a.clone()))
.unwrap()
.ciphertext();
@@ -62,13 +62,14 @@ 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_frame(peer_b_sm, LpFrame::new_opaque(plaintext_b.clone()))
.send_data(peer_b_sm, LpMessage::new_opaque(plaintext_b.clone()))
.unwrap()
.ciphertext();
@@ -76,6 +77,7 @@ 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);
}
@@ -129,24 +131,24 @@ mod tests {
let session2 = sessions.responder;
// 2. Create a session (using real noise state)
let _session = session_manager.insert_session(session1);
let _session = session_manager.create_session_state_machine(session1);
// 3. Try to get a non-existent session
let result = session_manager.session_exists(non_existent);
let result = session_manager.state_machine_exists(non_existent);
assert!(!result, "Non-existent session should return None");
// 4. Try to remove a non-existent session
let result = session_manager.remove_session(non_existent);
let result = session_manager.remove_state_machine(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.insert_session(session2);
let _temp_session = session_manager.create_session_state_machine(session2);
assert!(
session_manager.remove_session(session_id),
session_manager.remove_state_machine(session_id),
"Should remove the session"
);
}
@@ -170,26 +172,39 @@ mod tests {
// 2. Create sessions state machines
session_manager_1
.insert_session(sessions.initiator)
.create_session_state_machine(sessions.initiator)
.unwrap();
session_manager_2
.insert_session(sessions.responder)
.create_session_state_machine(sessions.responder)
.unwrap();
assert_eq!(session_manager_1.session_count(), 1);
assert_eq!(session_manager_2.session_count(), 1);
assert!(session_manager_1.session_exists(session_id));
assert!(session_manager_2.session_exists(session_id));
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
);
// --- 3. Simulate Data Transfer via process_input ---
println!("Starting data transfer simulation via process_input...");
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());
let plaintext_a_to_b =
LpMessage::new_opaque(b"Hello from A via process_input!".to_vec());
let plaintext_b_to_a =
LpMessage::new_opaque(b"Hello from B via process_input!".to_vec());
// --- A sends to B ---
println!(" A sends to B");
let action_a_send = session_manager_1
.process_input(session_id, LpInput::SendFrame(plaintext_a_to_b.clone()))
.process_input(session_id, LpInput::SendData(plaintext_a_to_b.clone()))
.expect("A SendData should produce action")
.expect("A SendData failed");
let data_packet_a = action_a_send.ciphertext();
@@ -198,9 +213,10 @@ 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::DeliverFrame(data) = action_b_recv {
if let LpAction::DeliverData(data) = action_b_recv {
assert_eq!(data, plaintext_a_to_b, "Decrypted data mismatch A->B");
println!(
" B successfully decrypted: {:?}",
@@ -213,7 +229,8 @@ mod tests {
// --- B sends to A ---
println!(" B sends to A");
let action_b_send = session_manager_2
.process_input(session_id, LpInput::SendFrame(plaintext_b_to_a.clone()))
.process_input(session_id, LpInput::SendData(plaintext_b_to_a.clone()))
.expect("B SendData should produce action")
.expect("B SendData failed");
let data_packet_b = action_b_send.ciphertext();
@@ -225,9 +242,10 @@ 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::DeliverFrame(data) = action_a_recv {
if let LpAction::DeliverData(data) = action_a_recv {
assert_eq!(data, plaintext_b_to_a, "Decrypted data mismatch B->A");
println!(
" A successfully decrypted: {:?}",
@@ -256,11 +274,12 @@ mod tests {
println!("Testing out-of-order reception via process_input...");
// A prepares N+1 then N
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 data_n_plus_1 = LpMessage::new_opaque(b"Message N+1".to_vec());
let data_n = LpMessage::new_opaque(b"Message N".to_vec());
let action_send_n1 = session_manager_1
.process_input(session_id, LpInput::SendFrame(data_n_plus_1.clone()))
.process_input(session_id, LpInput::SendData(data_n_plus_1.clone()))
.unwrap()
.unwrap();
let packet_n1 = match action_send_n1 {
LpAction::SendPacket(p) => p,
@@ -268,7 +287,8 @@ mod tests {
};
let action_send_n = session_manager_1
.process_input(session_id, LpInput::SendFrame(data_n.clone()))
.process_input(session_id, LpInput::SendData(data_n.clone()))
.unwrap()
.unwrap();
let packet_n = match action_send_n {
LpAction::SendPacket(p) => p,
@@ -280,9 +300,10 @@ 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::DeliverFrame(d) => assert_eq!(d, data_n_plus_1, "Data N+1 mismatch"),
LpAction::DeliverData(d) => assert_eq!(d, data_n_plus_1, "Data N+1 mismatch"),
_ => panic!("Expected DeliverData for N+1"),
}
@@ -290,9 +311,10 @@ 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::DeliverFrame(d) => assert_eq!(d, data_n, "Data N mismatch"),
LpAction::DeliverData(d) => assert_eq!(d, data_n, "Data N mismatch"),
_ => panic!("Expected DeliverData for N"),
}
@@ -307,16 +329,64 @@ mod tests {
);
println!("Out-of-order test passed.");
// --- 6. Session Removal ---
assert!(session_manager_1.remove_session(session_id));
// --- 6. Close Test ---
println!("Testing close via process_input...");
// A closes
let action_a_close = session_manager_1
.process_input(session_id, LpInput::Close)
.expect("A Close should produce action")
.expect("A Close failed");
assert!(matches!(action_a_close, LpAction::ConnectionClosed));
assert_eq!(
session_manager_1.get_state(session_id).unwrap(),
LpStateBare::Closed
);
// Further actions on A fail
let send_after_close_a = session_manager_1.process_input(
session_id,
LpInput::SendData(LpMessage::new_opaque(b"fail".to_vec())),
);
assert!(send_after_close_a.is_err());
assert!(matches!(
send_after_close_a.err().unwrap(),
LpError::LpSessionClosed
));
// B closes
let action_b_close = session_manager_2
.process_input(session_id, LpInput::Close)
.expect("B Close should produce action")
.expect("B Close failed");
assert!(matches!(action_b_close, LpAction::ConnectionClosed));
assert_eq!(
session_manager_2.get_state(session_id).unwrap(),
LpStateBare::Closed
);
// Further actions on B fail
let send_after_close_b = session_manager_2.process_input(
session_id,
LpInput::SendData(LpMessage::new_opaque(b"fail".to_vec())),
);
assert!(send_after_close_b.is_err());
assert!(matches!(
send_after_close_b.err().unwrap(),
LpError::LpSessionClosed
));
println!("Close test passed.");
// --- 7. Session Removal ---
assert!(session_manager_1.remove_state_machine(session_id));
assert_eq!(session_manager_1.session_count(), 0);
assert!(!session_manager_1.session_exists(session_id));
assert!(!session_manager_1.state_machine_exists(session_id));
// B's session manager still has it until removed
assert!(session_manager_2.session_exists(session_id));
assert!(session_manager_2.remove_session(session_id));
assert!(session_manager_2.state_machine_exists(session_id));
assert!(session_manager_2.remove_state_machine(session_id));
assert_eq!(session_manager_2.session_count(), 0);
assert!(!session_manager_2.session_exists(session_id));
assert!(!session_manager_2.state_machine_exists(session_id));
println!("Session removal test passed.");
}
}
+67 -44
View File
@@ -6,28 +6,33 @@
//! This module implements session lifecycle management functionality, handling
//! creation, retrieval, and storage of sessions.
use crate::packet::{EncryptedLpPacket, LpFrame};
use crate::packet::{EncryptedLpPacket, LpMessage};
use crate::peer_config::LpReceiverIndex;
use crate::{LpError, LpTransportSession};
use crate::state_machine::{LpAction, LpInput, LpStateBare};
use crate::{LpError, LpSession, LpStateMachine};
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
sessions: HashMap<LpReceiverIndex, LpTransportSession>,
state_machines: HashMap<LpReceiverIndex, LpStateMachine>,
}
impl Default for SessionManager {
fn default() -> Self {
Self::new()
}
}
impl SessionManager {
/// Creates a new session manager with empty session storage.
pub fn new() -> Self {
Self {
sessions: HashMap::new(),
state_machines: HashMap::new(),
}
}
@@ -35,48 +40,61 @@ impl SessionManager {
&mut self,
lp_id: LpReceiverIndex,
input: LpInput,
) -> Result<LpAction, LpError> {
self.with_session_mut(lp_id, |sm| sm.process_input(input))?
) -> Result<Option<LpAction>, LpError> {
self.with_state_machine_mut(lp_id, |sm| sm.process_input(input).transpose())?
}
pub fn send_frame(
pub fn send_data(
&mut self,
lp_id: LpReceiverIndex,
frame: LpFrame,
data: LpMessage,
) -> Result<LpAction, LpError> {
self.process_input(lp_id, LpInput::SendFrame(frame))
self.process_input(lp_id, LpInput::SendData(data))?
.ok_or(LpError::NotInTransport)
}
pub fn receive_packet(
&mut self,
lp_id: LpReceiverIndex,
packet: EncryptedLpPacket,
) -> Result<LpAction, LpError> {
) -> Result<Option<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_session_id(&self, lp_id: LpReceiverIndex) -> Result<LpReceiverIndex, LpError> {
self.with_session(lp_id, |sm| sm.receiver_index())
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()))?
}
pub fn current_packet_cnt(&self, lp_id: LpReceiverIndex) -> Result<PacketCount, LpError> {
self.with_session(lp_id, |sm| Ok(sm.current_packet_cnt()))?
self.with_state_machine(lp_id, |sm| Ok(sm.session()?.current_packet_cnt()))?
}
pub fn session_count(&self) -> usize {
self.sessions.len()
self.state_machines.len()
}
pub fn session_exists(&self, lp_id: LpReceiverIndex) -> bool {
self.sessions.contains_key(&lp_id)
pub fn state_machine_exists(&self, lp_id: LpReceiverIndex) -> bool {
self.state_machines.contains_key(&lp_id)
}
pub fn with_session<F, R>(&self, lp_id: LpReceiverIndex, f: F) -> Result<R, LpError>
pub fn with_state_machine<F, R>(&self, lp_id: LpReceiverIndex, f: F) -> Result<R, LpError>
where
F: FnOnce(&LpTransportSession) -> R,
F: FnOnce(&LpStateMachine) -> R,
{
if let Some(sm) = self.sessions.get(&lp_id) {
if let Some(sm) = self.state_machines.get(&lp_id) {
Ok(f(sm))
} else {
Err(LpError::StateMachineNotFound(lp_id))
@@ -84,34 +102,39 @@ impl SessionManager {
}
// For mutable access (like running process_input)
pub fn with_session_mut<F, R>(&mut self, lp_id: LpReceiverIndex, f: F) -> Result<R, LpError>
pub fn with_state_machine_mut<F, R>(
&mut self,
lp_id: LpReceiverIndex,
f: F,
) -> Result<R, LpError>
where
F: FnOnce(&mut LpTransportSession) -> R, // Closure takes mutable ref
F: FnOnce(&mut LpStateMachine) -> R, // Closure takes mutable ref
{
if let Some(sm) = self.sessions.get_mut(&lp_id) {
if let Some(sm) = self.state_machines.get_mut(&lp_id) {
Ok(f(sm))
} else {
Err(LpError::StateMachineNotFound(lp_id))
}
}
pub fn insert_session(
pub fn create_session_state_machine(
&mut self,
lp_session: LpTransportSession,
lp_session: LpSession,
) -> Result<LpReceiverIndex, LpError> {
let session_id = lp_session.receiver_index();
if self.sessions.contains_key(&session_id) {
if self.state_machines.contains_key(&session_id) {
return Err(LpError::DuplicateSessionId(session_id));
}
self.sessions.insert(session_id, lp_session);
let sm = LpStateMachine::new(lp_session);
self.state_machines.insert(session_id, sm);
Ok(session_id)
}
/// Method to remove a state machine
pub fn remove_session(&mut self, lp_id: LpReceiverIndex) -> bool {
let removed = self.sessions.remove(&lp_id);
pub fn remove_state_machine(&mut self, lp_id: LpReceiverIndex) -> bool {
let removed = self.state_machines.remove(&lp_id);
removed.is_some()
}
@@ -129,13 +152,13 @@ mod tests {
let local_session = mock_session_for_test();
let id = local_session.receiver_index();
let sm_1_id = manager.insert_session(local_session).unwrap();
let sm_1_id = manager.create_session_state_machine(local_session).unwrap();
assert_eq!(sm_1_id, id);
let retrieved = manager.session_exists(id);
let retrieved = manager.state_machine_exists(id);
assert!(retrieved);
let not_found = manager.session_exists(123);
let not_found = manager.state_machine_exists(123);
assert!(!not_found);
}
@@ -143,13 +166,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.insert_session(local_session).unwrap();
let sm_1_id = manager.create_session_state_machine(local_session).unwrap();
let removed = manager.remove_session(sm_1_id);
let removed = manager.remove_state_machine(sm_1_id);
assert!(removed);
assert_eq!(manager.session_count(), 0);
let removed_again = manager.remove_session(sm_1_id);
let removed_again = manager.remove_state_machine(sm_1_id);
assert!(!removed_again);
}
@@ -161,15 +184,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.insert_session(session1).unwrap();
let sm_2 = manager.insert_session(session2).unwrap();
let sm_3 = manager.insert_session(session3).unwrap();
let sm_1 = manager.create_session_state_machine(session1).unwrap();
let sm_2 = manager.create_session_state_machine(session2).unwrap();
let sm_3 = manager.create_session_state_machine(session3).unwrap();
assert_eq!(manager.session_count(), 3);
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();
let retrieved1 = manager.get_state_machine_id(sm_1).unwrap();
let retrieved2 = manager.get_state_machine_id(sm_2).unwrap();
let retrieved3 = manager.get_state_machine_id(sm_3).unwrap();
assert_eq!(retrieved1, sm_1);
assert_eq!(retrieved2, sm_2);
@@ -183,10 +206,10 @@ mod tests {
let sesion = mock_session_for_test();
let sm = manager.insert_session(sesion).unwrap();
let sm = manager.create_session_state_machine(sesion).unwrap();
assert_eq!(manager.session_count(), 1);
let retrieved = manager.get_session_id(sm);
let retrieved = manager.get_state_machine_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