Compare commits

...

30 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacșu f25f76c1df Merge remote-tracking branch 'origin/incoming' into base 2023-06-29 17:02:11 +03:00
Jędrzej Stuczyński 096a599673 Socks5lib - FFI endpoint for getting 'ClientState' (#3464)
* method for getting current socks5 connection state

* prevent shutting down disconnected client (and starting connected client)

* fixed ios build

* setup additional logging
2023-05-30 10:38:44 +01:00
dependabot[bot] 41da67ad6f Bump yaml from 2.1.1 to 2.2.2 in /nym-api/tests (#3352)
Bumps [yaml](https://github.com/eemeli/yaml) from 2.1.1 to 2.2.2.
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v2.1.1...v2.2.2)

---
updated-dependencies:
- dependency-name: yaml
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 12:08:31 +01:00
Jędrzej Stuczyński 273a741cdf Fix typo in wasm-client x25519 keypair storage key (#3463) 2023-05-29 11:05:05 +01:00
Drazen Urch b5c8b69547 Outfox integration (#3331)
* Experiment with serde

* Framed encoding serde POC

* Outfox framing

* Outfox rest compat (#3333)

* Outfox forwarding compat

* Tidy up interface

* PacketSize compat

* Address PR comments

* Rebase on develop

commit 342883fcbe
Author: durch <durch@users.noreply.github.com>
Date:   Thu Apr 27 09:17:18 2023 +0200

    Put back PacketType 1

commit 61a0ee5a19
Author: Tommy Verrall <tommyvez@protonmail.com>
Date:   Wed Apr 26 16:37:29 2023 +0100

    change output for cpu-cycle management logs

commit 3956109c7e
Author: Tommy Verrall <tommy@nymtech.net>
Date:   Wed Apr 26 12:13:22 2023 +0100

    change the workflow file to build with cpucycles

commit 8d725b13c5
Author: durch <durch@users.noreply.github.com>
Date:   Mon Apr 24 13:14:58 2023 +0200

    Outfox client compat

commit 4d166c389b
Author: durch <durch@users.noreply.github.com>
Date:   Fri Apr 21 00:30:46 2023 +0200

    Address PR comments

commit 145c3c1223
Author: durch <durch@users.noreply.github.com>
Date:   Fri Apr 21 00:12:35 2023 +0200

    Rename PacketMode

commit cbd654d6fd
Author: Drazen Urch <drazen@urch.eu>
Date:   Thu Apr 20 23:59:40 2023 +0200

    Outfox rest compat (#3333)

    * Outfox forwarding compat

    * Tidy up interface

    * PacketSize compat

commit e7be91a94c
Author: durch <durch@users.noreply.github.com>
Date:   Wed Apr 19 16:36:48 2023 +0200

    Remove serde cruft

commit 582e7d566a
Author: durch <durch@users.noreply.github.com>
Date:   Wed Apr 19 16:24:09 2023 +0200

    Outfox framing

commit 6464da5f01
Author: durch <durch@users.noreply.github.com>
Date:   Tue Apr 18 22:23:02 2023 +0200

    Framing compat

commit d5e77e499b
Author: durch <durch@users.noreply.github.com>
Date:   Tue Apr 18 18:18:54 2023 +0200

    Framed encoding serde POC

commit f086f9c35a
Author: durch <durch@users.noreply.github.com>
Date:   Tue Apr 18 16:54:21 2023 +0200

    Experiment with serde

* Client tweaks

* Speed up from_plaintext

* SurbAcks

* More work on the reciever end, and outfox format

* Cleanup and fmt

* Wrap up rebase

* Happy clippy

* Fix lock files

* Final cleanup
2023-05-29 10:05:11 +02:00
pierre 5bd87bdaa8 feat(nc-native-android): add notification tap action
on notification tap, bring app to foreground
use a shield icon for the top bar app icon
clean code
add notes on a pending bug
2023-05-27 00:00:24 +02:00
omahs 7d64618701 Fix: typos (#3143)
* Fix: typos

* Fix: typos

* Fix: typo
2023-05-26 14:46:08 +01:00
Jon Häggblad 4151c65251 Add nym-socks5-listener to main workspace (#3455)
* Add nym-socks5-listener to main workspace

* add socks5-listener to CI build path trigger

* Using repr(u8) instead of repr(C) for ClientState enum

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2023-05-26 11:01:17 +02:00
Fouad ccb48f92cd fix sp drop down list (#3454) 2023-05-26 09:16:28 +01:00
pierre 8fce478a1f doc(nc-native-android): add readme 2023-05-26 00:34:40 +02:00
pierre 4f712ad4ba fix(android-native): crash on android 11 2023-05-25 23:14:11 +02:00
pierre 562fd44a30 fix(android-native): crash on android 11 2023-05-25 23:00:36 +02:00
pierre 603e897e2d build(android-native): add split abi config
refactor android build script
2023-05-25 18:03:36 +02:00
Pierre Dommerc 6c122aad10 add nym ic launcher (#3456) 2023-05-25 18:00:22 +02:00
Jon Häggblad b4ac601a82 Merge remote-tracking branch 'origin/release/v1.1.20' into develop 2023-05-25 15:58:14 +02:00
Jon Häggblad ce380a6b0d Native nym-connect clients for iOS and Android (initial version) (#3452)
* initial crate

* foomp

* Make it work for x86_64-linux-android

* remove unused stuff

* Add header

* another layer of hacks

* additional target os locking

* cleanup

* bootstrap android app

* android jni function

* instructions + xcode project

* update jni name

* add native socks5 class

* typo

* gitkeep android native lib path

* add native socks5 class

* add socks5 native lib in java

* add build script

* fix jni dependency declaration

* wip

* Update build.sh

* Move build.sh to new subdir

* rename to build-android.sh

* fix typo in FFI function name

* use a good SP

* wip not crashing state

* add android network permissions

* android_logging

* starting client on button in swift + safer ffi

* set tag for libnyms5 logs

* testing callbacks

* android: start socks5 process in a separated thread

* non-blocking client with callbacks

* Remove the old non-working logger

* Restore commented out functionality in socks5 client

* basic file write/load + possible android fix

* Fully working state (minus task manager)

* Remove unused function

* data persistence + cb with address

* Remove stray old MyClass file from the merge

* Make storage_dir and Option

* Fix char_p for android

* Android now works with the new branch

* Tidy up a little in the jni code

* Move android mod to seperate file

* jni wrap start/stop

* Add android build to Makefile

* android: add basic UI and start/stop actions

* typo

* add nym word

* dirty persistence restored

* dirty android fixes

* even dirtier workaround

* Move rust crate to sdk/lib

* Update cargo.toml

* Strip release binary

* Update lib name in android project

* Move ios project to nym-connect directory

* remove old gitignore file

* Move ios client one step deeper

* fixed xcode lib paths

* removed old tracked file

* move android app under new path

* a bit of cleanup

* hopefully fixing the CI issues (🤞)

* Update Makefile

* android: add better support for persistent state

* updating ios UI on ffi callbacks

* missing dead code

* Added toggle button (wip)

* swapped connect and disconnect  methods around

* icon

* fixed android build

* reset button + reuse service provider

* disabling reset button

* android: run proxy in a worker as foreground service

* todo user cancel action

* android build script: add aarch64

* add stop action from notification

* add simple callbacks to the socks5 bridge

* pick a sp randomly

* pass stop cb to lib call

* add loading state support

* refactor(android): base connection state on callback calls

* android: add optimistic ui

* android: unique instance of libnym

* removing deadcode

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: pierre <dommerc.pierre@gmail.com>
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2023-05-25 15:45:40 +02:00
Bogdan-Ștefan Neacşu 29f95febe9 Feature/ephemera compile (#3437)
* Include ephemera node code in repo

* Upgrade deps

* Bump minor version of cosmwasm-std

* Include ephemera in nym-api dep and downgrade rusqlite

* Fix clippy and ephemera docs code

* More clippy on ephemera

---------

Co-authored-by: Andrus Salumets <andrus@nymtech.net>
2023-05-25 11:24:49 +03:00
Jędrzej Stuczyński 525372d7ac Tmp hack/wasm client persist gateway cfg (#3443)
* wip

* semi-hacky way of persisting wasm-client gateway config

a better way shall be introduced after config refactoring

* cargo fmt

* wasm client clippy

* removed artifacts from other branches
2023-05-25 09:02:27 +01:00
Jon Häggblad e473a05250 Tweak fern logging format to be more similar to pretty_env_logger (#3451) 2023-05-25 09:29:59 +02:00
Simon Wicky a55d604bf5 increase connection buffer size to 2000 (#3439) 2023-05-23 16:08:55 +02:00
Jon Häggblad 5075894ff5 socks5: abort sending data if the connection is closed (#3365)
* socks5 inbound: stop reading when closing connection

* Wait for lane at select top-level

* Allow closing connection while waiting for lanes to clear

* Some tidy in inbound.rs

* Put chained future back inline in the select

* Remove commented out line

* Disable the read data branch on is_finished
2023-05-23 15:01:13 +02:00
fmtabbara c392266a4c Explorer: Fix - Load service provider data before trying to display 2023-05-17 12:44:24 +01:00
Tommy Verrall 9b540936db Merge pull request #3429 from nymtech/feature/explorer-show-all-gateway-versions
Add 'show all versions' options to gateways list
2023-05-17 10:50:06 +01:00
Tommy Verrall 7bf1036b68 Merge pull request #3430 from nymtech/service-providers-fix
small typo correction and service providers fix
2023-05-17 10:48:20 +01:00
Gala 1d82ec56d8 change where the change is applied 2023-05-17 11:41:45 +02:00
Gala 3ae5b59141 small typo correction and service providers fix 2023-05-17 11:27:49 +02:00
fmtabbara 929b401f95 default to 'all version' 2023-05-17 10:13:14 +01:00
fmtabbara d158deba77 add 'show all versions' options to gateways list 2023-05-17 10:07:25 +01:00
Tommy Verrall 74bedead20 Merge pull request #3418 from nymtech/jstuczyn-patch-1
Fix feature-locking for fs surb storage
2023-05-16 12:56:32 +01:00
Jędrzej Stuczyński 30137e285d Fix feature-locking for fs surb storage 2023-05-16 11:29:08 +01:00
339 changed files with 31282 additions and 2395 deletions
+4
View File
@@ -9,12 +9,14 @@ on:
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/lib/socks5-listener/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
- 'Cargo.toml'
pull_request:
paths:
- 'clients/**'
@@ -23,12 +25,14 @@ on:
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/lib/socks5-listener/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
- 'Cargo.toml'
jobs:
build:
+3 -1
View File
@@ -8,6 +8,7 @@
.idea
target
.env
.env.dev
/.vscode/settings.json
validator/.vscode
sample-configs/validator-config.toml
@@ -41,4 +42,5 @@ storybook-static
envs/qwerty.env
.parcel-cache
**/.DS_Store
cpu-cycles/libcpucycles/build
cpu-cycles/libcpucycles/build
foxyfox.env
Generated
+393 -234
View File
File diff suppressed because it is too large Load Diff
+14 -10
View File
@@ -78,6 +78,7 @@ members = [
"gateway/gateway-requests",
"integrations/bity",
"mixnode",
"sdk/lib/socks5-listener",
"sdk/rust/nym-sdk",
"service-providers/common",
"service-providers/network-requester",
@@ -115,18 +116,21 @@ async-trait = "0.1.64"
anyhow = "1.0.71"
bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0"
cosmwasm-derive = "=1.0.0"
cosmwasm-schema = "=1.0.0"
cosmwasm-std = "=1.0.0"
cosmwasm-storage = "=1.0.0"
cw-utils = "=0.13.4"
cw-storage-plus = "=0.13.4"
cw2 = { version = "=0.13.4" }
cw3 = { version = "=0.13.4" }
cw3-fixed-multisig = { version = "=0.13.4" }
cw4 = { version = "=0.13.4" }
cosmwasm-derive = "=1.2.5"
cosmwasm-schema = "=1.2.5"
cosmwasm-std = "=1.2.5"
cosmwasm-storage = "=1.2.5"
cosmrs = "=0.8.0"
cw-utils = "=1.0.1"
cw-storage-plus = "=1.0.1"
cw2 = { version = "=1.0.1" }
cw3 = { version = "=1.0.1" }
cw3-fixed-multisig = { version = "=1.0.1" }
cw4 = { version = "=1.0.1" }
cw-controllers = { version = "=1.0.1" }
dotenvy = "0.15.6"
generic-array = "0.14.7"
k256 = "0.11"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
+3 -3
View File
@@ -32,7 +32,7 @@ For Typescript components, please see [ts-packages](./ts-packages).
### Developer chat
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
You can chat with us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes place in the **#dev** channel. Node operators should be in the **#node-operators** channel.
### Rewards
@@ -46,7 +46,7 @@ Node, node operator and delegator rewards are determined according to the princi
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda_{i}#gh-dark-mode-only">|ratio of stake operator has pledged to their node to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\omega_{i}#gh-dark-mode-only">|fraction of total effort undertaken by node `i`, set to `1/k`.
|<img src="https://render.githubusercontent.com/render/math?math=k#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}k#gh-dark-mode-only">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitiveness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10% in.
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PF_{i}#gh-dark-mode-only">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMT.
@@ -70,7 +70,7 @@ Operator of node `i` is credited with the following amount:
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
Delegate with stake `s` recieves:
Delegate with stake `s` receives:
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
+20 -5
View File
@@ -20,6 +20,7 @@ use nym_client_core::client::received_buffer::{
use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_validator_client::nyxd::QueryNyxdClient;
@@ -63,6 +64,7 @@ impl SocketClient {
client_state: ClientState,
self_address: &Recipient,
shutdown: nym_task::TaskClient,
packet_type: PacketType,
) {
info!("Starting websocket listener...");
@@ -88,6 +90,7 @@ impl SocketClient {
self_address,
shared_lane_queue_lengths,
reply_controller_sender,
Some(packet_type),
);
websocket::Listener::new(config.get_listening_ip(), config.get_listening_port())
@@ -137,7 +140,8 @@ impl SocketClient {
}
let base_builder = self.create_base_client_builder().await?;
let mut started_client = base_builder.start_base().await?;
let packet_type = self.config.get_base().get_packet_type();
let mut started_client = base_builder.start_base(packet_type).await?;
let self_address = started_client.address;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
@@ -150,6 +154,7 @@ impl SocketClient {
client_state,
&self_address,
started_client.task_manager.subscribe(),
packet_type,
);
info!("Client startup finished!");
@@ -164,7 +169,8 @@ impl SocketClient {
}
let base_builder = self.create_base_client_builder().await?;
let mut started_client = base_builder.start_base().await?;
let packet_type = self.config.get_base().get_packet_type();
let mut started_client = base_builder.start_base(packet_type).await?;
let address = started_client.address;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
@@ -186,6 +192,7 @@ impl SocketClient {
reconstructed_receiver,
address,
shutdown_notifier: started_client.task_manager,
packet_type,
})
}
}
@@ -199,6 +206,7 @@ pub struct DirectClient {
// we need to keep reference to this guy otherwise things will start dropping
shutdown_notifier: TaskManager,
packet_type: PacketType,
}
impl DirectClient {
@@ -219,7 +227,7 @@ impl DirectClient {
/// well enough in local tests)
pub async fn send_regular_message(&mut self, recipient: Recipient, message: Vec<u8>) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane);
let input_msg = InputMessage::new_regular(recipient, message, lane, Some(self.packet_type));
self.client_input
.input_sender
@@ -238,7 +246,13 @@ impl DirectClient {
reply_surbs: u32,
) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
let input_msg = InputMessage::new_anonymous(
recipient,
message,
reply_surbs,
lane,
Some(self.packet_type),
);
self.client_input
.input_sender
@@ -252,7 +266,8 @@ impl DirectClient {
/// well enough in local tests)
pub async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: Vec<u8>) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
let input_msg =
InputMessage::new_reply(recipient_tag, message, lane, Some(self.packet_type));
self.client_input
.input_sender
+6 -3
View File
@@ -9,8 +9,8 @@ use crate::{
};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_config::NymConfig;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use serde::Serialize;
@@ -152,7 +152,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, PersistentStorage>(
let key_store = OnDiskKeys::from_config(config.get_base());
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, _>(
&key_store,
register_gateway,
user_chosen_gateway_id,
config.get_base(),
@@ -169,7 +171,8 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
print_saved_config(&config);
let address = nym_client_core::init::get_client_address_from_stored_keys(config.get_base())?;
let address =
nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?;
let init_results = InitResults::new(&config, &address);
println!("{}", args.output.format(&init_results));
+10 -3
View File
@@ -14,6 +14,7 @@ use nym_client_core::client::{
use nym_client_websocket_requests::{requests::ClientRequest, responses::ServerResponse};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::connections::{
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
@@ -41,6 +42,7 @@ pub(crate) struct HandlerBuilder {
self_full_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
}
impl HandlerBuilder {
@@ -51,6 +53,7 @@ impl HandlerBuilder {
self_full_address: &Recipient,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
) -> Self {
Self {
msg_input,
@@ -59,6 +62,7 @@ impl HandlerBuilder {
self_full_address: *self_full_address,
lane_queue_lengths,
reply_controller_sender,
packet_type,
}
}
@@ -73,6 +77,7 @@ impl HandlerBuilder {
received_response_type: Default::default(),
lane_queue_lengths: self.lane_queue_lengths.clone(),
reply_controller_sender: self.reply_controller_sender.clone(),
packet_type: self.packet_type,
}
}
}
@@ -86,6 +91,7 @@ pub(crate) struct Handler {
received_response_type: ReceivedResponseType,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
}
impl Drop for Handler {
@@ -160,7 +166,7 @@ impl Handler {
});
// the ack control is now responsible for chunking, etc.
let input_msg = InputMessage::new_regular(recipient, message, lane);
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
self.msg_input
.send(input_msg)
.await
@@ -191,7 +197,8 @@ impl Handler {
TransmissionLane::ConnectionId(id)
});
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
let input_msg =
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
self.msg_input
.send(input_msg)
.await
@@ -218,7 +225,7 @@ impl Handler {
TransmissionLane::ConnectionId(id)
});
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
self.msg_input
.send(input_msg)
.await
+7 -3
View File
@@ -8,8 +8,8 @@ use crate::{
};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_config::NymConfig;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::config::Config;
use nym_sphinx::addressing::clients::Recipient;
@@ -91,6 +91,7 @@ impl From<Init> for OverrideConfig {
no_cover: init_config.no_cover,
nyxd_urls: init_config.nyxd_urls,
enabled_credentials_mode: init_config.enabled_credentials_mode,
outfox: false,
}
}
}
@@ -158,7 +159,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, PersistentStorage>(
let key_store = OnDiskKeys::from_config(config.get_base());
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, _>(
&key_store,
register_gateway,
user_chosen_gateway_id,
config.get_base(),
@@ -177,7 +180,8 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
print_saved_config(&config);
let address = nym_client_core::init::get_client_address_from_stored_keys(config.get_base())?;
let address =
nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?;
let init_results = InitResults::new(&config, &address);
println!("{}", args.output.format(&init_results));
+8
View File
@@ -10,6 +10,7 @@ use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_config::{NymConfig, OptionalSet};
use nym_socks5_client_core::config::old_config_v1_1_13::OldConfigV1_1_13;
use nym_socks5_client_core::config::{BaseConfig, Config};
use nym_sphinx::params::PacketType;
use std::error::Error;
pub mod init;
@@ -64,6 +65,7 @@ pub(crate) struct OverrideConfig {
no_cover: bool,
nyxd_urls: Option<Vec<url::Url>>,
enabled_credentials_mode: Option<bool>,
outfox: bool,
}
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
@@ -80,9 +82,15 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Syn
}
pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
let packet_type = if args.outfox {
PacketType::Outfox
} else {
PacketType::Mix
};
config
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
.with_base(BaseConfig::with_packet_type, packet_type)
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
.with_optional(Config::with_port, args.port)
.with_optional_custom_env_ext(
+7 -1
View File
@@ -9,6 +9,7 @@ use crate::{
use clap::Args;
use log::*;
use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_client_core::client::base_client::storage::OnDiskPersistent;
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::{config::Config, NymClient};
@@ -67,6 +68,9 @@ pub(crate) struct Run {
/// with bandwidth credential requirement.
#[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>,
#[clap(long, hide = true, action)]
outfox: bool,
}
impl From<Run> for OverrideConfig {
@@ -79,6 +83,7 @@ impl From<Run> for OverrideConfig {
no_cover: run_config.no_cover,
nyxd_urls: run_config.nyxd_urls,
enabled_credentials_mode: run_config.enabled_credentials_mode,
outfox: run_config.outfox,
}
}
}
@@ -138,5 +143,6 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error
return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck));
}
NymClient::new(config).run_forever().await
let storage = OnDiskPersistent::from_config(config.get_base()).await?;
NymClient::new(config, storage).run_forever().await
}
-1
View File
@@ -1,6 +1,5 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
+5175
View File
File diff suppressed because it is too large Load Diff
+12 -16
View File
@@ -106,29 +106,25 @@ function printAndDisplayTestResult(result) {
});
}
function dummyGatewayConfig() {
return new GatewayEndpointConfig(
'336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9',
'n1rqqw8km7a0rvf8lr6k8dsdqvvkyn2mglj7xxfm',
'ws://85.159.212.96:9000',
)
}
async function testWithTester() {
const gatewayConfig = dummyGatewayConfig();
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
// A) construct with hardcoded topology
const topology = dummyTopology()
const nodeTester = await new NymNodeTester(gatewayConfig, topology);
const nodeTester = await new NymNodeTester(topology, dummyGateway);
// B) first get topology directly from nym-api
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const topology = await current_network_topology(validator)
// const nodeTester = await new NymNodeTester(gatewayConfig, topology);
// const nodeTester = await new NymNodeTester(topology, dummyGateway);
//
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const nodeTester = await NymNodeTester.new_with_api(gatewayConfig, validator)
// const nodeTester = await NymNodeTester.new_with_api(validator, dummyGateway)
// D, E, F) you also don't have to specify the gateway. if you don't, a random one (from your topology) will be used
// const topology = dummyTopology()
// const nodeTester = await new NymNodeTester(topology);
self.onmessage = async event => {
if (event.data && event.data.kind) {
@@ -146,7 +142,7 @@ async function testWithTester() {
}
async function testWithNymClient() {
const gatewayConfig = dummyGatewayConfig();
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
const topology = dummyTopology()
let received = 0
@@ -168,7 +164,7 @@ async function testWithNymClient() {
console.log('Instantiating WASM client...');
let clientBuilder = NymClientBuilder.new_tester(gatewayConfig, topology, onMessageHandler)
let clientBuilder = NymClientBuilder.new_tester(topology, onMessageHandler, dummyGateway)
console.log('Web worker creating WASM client...');
let local_client = await clientBuilder.start_client();
console.log('WASM client running!');
@@ -226,10 +222,10 @@ async function normalNymClientUsage() {
debug.topology_refresh_rate_ms = BigInt(60000)
const gatewayConfig = dummyGatewayConfig();
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
const config = new Config('my-awesome-wasm-client', validator, gatewayConfig, debug);
const config = new Config('my-awesome-wasm-client', validator, dummyGateway, debug);
const onMessageHandler = (message) => {
console.log(message);
+22 -8
View File
@@ -1,4 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
@@ -9,10 +9,10 @@
use nym_client_core::config::{
Acknowledgements as ConfigAcknowledgements, CoverTraffic as ConfigCoverTraffic,
DebugConfig as ConfigDebug, GatewayConnection as ConfigGatewayConnection,
GatewayEndpointConfig, ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology,
Traffic as ConfigTraffic,
ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology, Traffic as ConfigTraffic,
};
use nym_sphinx::params::PacketSize;
use nym_sphinx::params::{PacketSize, PacketType};
use nym_validator_client::client::IdentityKey;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
@@ -29,10 +29,13 @@ pub struct Config {
pub(crate) disabled_credentials_mode: bool,
/// Information regarding how the client should send data to gateway.
pub(crate) gateway_endpoint: GatewayEndpointConfig,
/// Information regarding how the client should choose gateway.
/// If unspecified, the client will attempt to load the config from the storage.
pub(crate) gateway: Option<IdentityKey>,
pub(crate) debug: ConfigDebug,
pub(crate) packet_type: PacketType,
}
#[wasm_bindgen]
@@ -41,9 +44,18 @@ impl Config {
pub fn new(
id: String,
validator_server: String,
gateway_endpoint: GatewayEndpointConfig,
packet_type: Option<String>,
gateway: Option<IdentityKey>,
debug: Option<Debug>,
) -> Self {
let packet_type = if let Some(packet_type) = packet_type {
match packet_type.as_str() {
"outfox" => PacketType::Outfox,
_ => PacketType::Mix,
}
} else {
PacketType::Mix
};
Config {
id,
nym_api_url: Some(
@@ -52,8 +64,9 @@ impl Config {
.expect("provided url was malformed"),
),
disabled_credentials_mode: true,
gateway_endpoint,
gateway,
debug: debug.map(Into::into).unwrap_or_default(),
packet_type,
}
}
}
@@ -96,6 +109,7 @@ impl From<Traffic> for ConfigTraffic {
.disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: use_extended_packet_size,
packet_type: None,
}
}
}
+53 -22
View File
@@ -6,7 +6,10 @@ use crate::client::helpers::{InputSender, NymClientTestRequest, WasmTopologyExt}
use crate::client::response_pusher::ResponsePusher;
use crate::constants::NODE_TESTER_CLIENT_ID;
use crate::error::WasmClientError;
use crate::helpers::{parse_recipient, parse_sender_tag, setup_reply_surb_storage_backend};
use crate::helpers::{
choose_gateway, gateway_from_topology, parse_recipient, parse_sender_tag,
setup_reply_surb_storage_backend,
};
use crate::storage::traits::FullWasmClientStorage;
use crate::storage::ClientStorage;
use crate::topology::WasmNymTopology;
@@ -18,16 +21,16 @@ use nym_client_core::client::base_client::{
};
use nym_client_core::client::inbound_messages::InputMessage;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config::{
CoverTraffic, DebugConfig, GatewayEndpointConfig, Topology, Traffic,
};
use nym_client_core::config::{CoverTraffic, DebugConfig, Topology, Traffic};
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider};
use nym_topology::NymTopology;
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use rand::RngCore;
use rand::{thread_rng, RngCore};
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
@@ -50,6 +53,7 @@ pub struct NymClient {
// even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline
_task_manager: TaskManager,
packet_type: Option<PacketType>,
}
#[wasm_bindgen]
@@ -66,6 +70,7 @@ pub struct NymClientBuilder {
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
disabled_credentials: bool,
packet_type: Option<PacketType>,
}
#[wasm_bindgen]
@@ -84,6 +89,7 @@ impl NymClientBuilder {
on_message,
bandwidth_controller: None,
disabled_credentials: true,
packet_type: None,
}
}
@@ -92,19 +98,21 @@ impl NymClientBuilder {
// hardcoded topology
// NOTE: you most likely want to use `[NymNodeTester]` instead.
pub fn new_tester(
gateway_config: GatewayEndpointConfig,
topology: WasmNymTopology,
on_message: js_sys::Function,
gateway: Option<IdentityKey>,
) -> Self {
if !topology.ensure_contains(&gateway_config) {
panic!("the specified topology does not contain the gateway used by the client")
if let Some(gateway_id) = &gateway {
if !topology.ensure_contains_gateway_id(gateway_id) {
panic!("the specified topology does not contain the gateway used by the client")
}
}
let full_config = Config {
id: NODE_TESTER_CLIENT_ID.to_string(),
nym_api_url: None,
disabled_credentials_mode: true,
gateway_endpoint: gateway_config,
gateway,
debug: DebugConfig {
traffic: Traffic {
disable_main_poisson_packet_distribution: true,
@@ -120,6 +128,7 @@ impl NymClientBuilder {
},
..Default::default()
},
packet_type: PacketType::Mix,
};
NymClientBuilder {
@@ -132,6 +141,7 @@ impl NymClientBuilder {
bandwidth_controller: None,
disabled_credentials: true,
storage_passphrase: None,
packet_type: None,
}
}
@@ -139,7 +149,7 @@ impl NymClientBuilder {
ResponsePusher::new(client_output, on_message).start()
}
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider>> {
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider + Send + Sync>> {
if let Some(hardcoded_topology) = self.custom_topology.take() {
Some(Box::new(HardcodedTopologyProvider::new(hardcoded_topology)))
} else {
@@ -150,27 +160,45 @@ impl NymClientBuilder {
async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> {
console_log!("Starting the wasm client");
let maybe_topology_provider = self.topology_provider();
let disabled_credentials = if self.disabled_credentials {
CredentialsToggle::Disabled
} else {
CredentialsToggle::Enabled
};
let nym_api_endpoints = match self.config.nym_api_url {
Some(endpoint) => vec![endpoint],
let nym_api_endpoints = match &self.config.nym_api_url {
Some(endpoint) => vec![endpoint.clone()],
None => Vec::new(),
};
// TODO: this will have to be re-used for surbs. but this is a problem for another PR.
let key_store =
let client_store =
ClientStorage::new_async(&self.config.id, self.storage_passphrase.take()).await?;
// if we provided hardcoded topology, get gateway from it, otherwise get it the 'standard' way
let gateway_endpoint = if let Some(topology) = &self.custom_topology {
gateway_from_topology(
&mut thread_rng(),
self.config.gateway.as_deref(),
topology,
&client_store,
)
.await?
} else {
choose_gateway(
&client_store,
self.config.gateway.clone(),
&nym_api_endpoints,
)
.await?
};
let maybe_topology_provider = self.topology_provider();
let mut base_builder: BaseClientBuilder<_, FullWasmClientStorage> = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&gateway_endpoint,
&self.config.debug,
key_store,
client_store,
self.bandwidth_controller,
self.reply_surb_storage_backend,
disabled_credentials,
@@ -180,7 +208,8 @@ impl NymClientBuilder {
base_builder = base_builder.with_topology_provider(topology_provider);
}
let mut started_client = base_builder.start_base().await?;
let packet_type = self.config.packet_type;
let mut started_client = base_builder.start_base(packet_type).await?;
let self_address = started_client.address.to_string();
let client_input = started_client.client_input.register_producer();
@@ -194,6 +223,7 @@ impl NymClientBuilder {
client_state: Arc::new(started_client.client_state),
_full_topology: None,
_task_manager: started_client.task_manager,
packet_type: self.packet_type,
})
}
@@ -269,7 +299,7 @@ impl NymClient {
let input_msgs = request
.test_msgs
.into_iter()
.map(|p| InputMessage::new_regular(recipient, p, lane))
.map(|p| InputMessage::new_regular(recipient, p, lane, None))
.collect();
self.client_input.send_messages(input_msgs)
@@ -289,7 +319,7 @@ impl NymClient {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane);
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
self.client_input.send_message(input_msg)
}
@@ -316,7 +346,8 @@ impl NymClient {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
let input_msg =
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
self.client_input.send_message(input_msg)
}
@@ -334,7 +365,7 @@ impl NymClient {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(sender_tag, message, lane);
let input_msg = InputMessage::new_reply(sender_tag, message, lane, self.packet_type);
self.client_input.send_message(input_msg)
}
}
+17 -1
View File
@@ -4,12 +4,14 @@
use crate::storage::errors::ClientStorageError;
use crate::topology::WasmTopologyError;
use js_sys::Promise;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_gateway_client::error::GatewayClientError;
use nym_node_tester_utils::error::NetworkTestingError;
use nym_sphinx::addressing::clients::RecipientFormattingError;
use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation;
use nym_topology::NymTopologyError;
use nym_validator_client::ValidatorClientError;
use thiserror::Error;
use wasm_bindgen::JsValue;
@@ -44,12 +46,18 @@ pub enum WasmClientError {
source: ValidatorClientError,
},
#[error("The provided topology was invalid: {source}")]
#[error("The provided wasm topology was invalid: {source}")]
WasmTopologyError {
#[from]
source: WasmTopologyError,
},
#[error("The provided nym topology was invalid: {source}")]
TopologyError {
#[from]
source: NymTopologyError,
},
#[error("failed to test the node: {source}")]
NodeTestingFailure {
#[from]
@@ -68,6 +76,9 @@ pub enum WasmClientError {
#[error("Mixnode {mixnode_identity} is not present in the current network topology")]
NonExistentMixnode { mixnode_identity: String },
#[error("Gateway {gateway_identity} is not present in the current network topology")]
NonExistentGateway { gateway_identity: String },
#[error("{raw} is not a valid Nym network recipient: {source}")]
MalformedRecipient {
raw: String,
@@ -85,6 +96,11 @@ pub enum WasmClientError {
#[from]
source: ClientStorageError,
},
#[error("this client has already registered with a gateway: {gateway_config:?}")]
AlreadyRegistered {
gateway_config: GatewayEndpointConfig,
},
}
impl WasmClientError {
+92 -1
View File
@@ -2,18 +2,24 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::storage::ClientStorage;
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::NymTopology;
use nym_validator_client::client::{IdentityKey, IdentityKeyRef};
use nym_validator_client::NymApiClient;
use rand::{CryptoRng, Rng};
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::PromisableResult;
use wasm_utils::{console_log, PromisableResult};
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no persistence
@@ -72,3 +78,88 @@ pub fn current_network_topology(nym_api_url: String) -> Promise {
.into_promise_result()
})
}
pub(crate) async fn choose_gateway(
client_store: &ClientStorage,
chosen_gateway: Option<IdentityKey>,
nym_apis: &[Url],
) -> Result<GatewayEndpointConfig, WasmClientError> {
let existing_gateway_config = client_store.read_gateway_config().await?;
console_log!("loaded: {:?}", existing_gateway_config);
if let Some(existing) = existing_gateway_config {
if let Some(provided) = &chosen_gateway {
if provided != &existing.gateway_id {
return Err(WasmClientError::AlreadyRegistered {
gateway_config: existing,
});
}
}
return Ok(existing);
};
// if NOTHING is specified nor available, choose gateway randomly.
let setup = GatewaySetup::new(None, chosen_gateway, None);
let config = setup.try_get_gateway_details(nym_apis).await?;
// perform registration + persist the new gateway info
// TODO: this is actually quite bad. we shouldn't be persisting gateway info here since we did not have persisted
// the shared key yet. this will only happen when we start the base client itself.
// but unfortunately, we can't do much more until we do a bit more refactoring.
client_store.store_gateway_config(&config).await?;
console_log!("stored: {:?}", config);
Ok(config)
}
pub(crate) async fn gateway_from_topology<R: Rng + CryptoRng>(
rng: &mut R,
explicit_gateway: Option<IdentityKeyRef<'_>>,
topology: &NymTopology,
client_store: &ClientStorage,
) -> Result<GatewayEndpointConfig, WasmClientError> {
let existing_gateway_config = client_store.read_gateway_config().await?;
console_log!("loaded: {:?}", existing_gateway_config);
let new_gateway: GatewayEndpointConfig = if let Some(provided) = explicit_gateway {
if let Some(existing) = existing_gateway_config {
// we have stored gateway info and explicitly provided identity key
//
// check if they match, otherwise return an error
return if provided != existing.gateway_id {
Err(WasmClientError::AlreadyRegistered {
gateway_config: existing,
})
} else {
Ok(existing)
};
} else {
// we have explicitly provided identity key and didn't have any prior stored data
//
// try to grab details from the topology
let gateway_identity = identity::PublicKey::from_base58_string(provided)
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
if let Some(gateway) = topology.get_gateway(&gateway_identity) {
gateway.clone().into()
} else {
return Err(WasmClientError::NonExistentGateway {
gateway_identity: gateway_identity.to_base58_string(),
});
}
}
} else if let Some(existing) = existing_gateway_config {
// we have stored data and didn't provide anything separately - use what's stored!
return Ok(existing);
} else {
// we don't have anything stored nor we have provided anything
//
// just grab random gateway from our topology
topology.random_gateway(rng)?.clone().into()
};
console_log!("storing: {:?}", new_gateway);
client_store.store_gateway_config(&new_gateway).await?;
Ok(new_gateway)
}
+30 -1
View File
@@ -3,6 +3,7 @@
use crate::storage::errors::ClientStorageError;
use js_sys::Promise;
use nym_client_core::config::GatewayEndpointConfig;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_client::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
@@ -23,10 +24,14 @@ const STORAGE_VERSION: u32 = 1;
mod v1 {
// stores
pub const KEYS_STORE: &str = "keys";
pub const CORE_STORE: &str = "core";
// keys
// TODO: to replace with FULL config
pub const GATEWAY_CONFIG: &str = "gateway_config";
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_key";
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
// TODO: for those we could actually use the subtle crypto storage
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
@@ -67,6 +72,7 @@ impl ClientStorage {
let db = evt.db();
db.create_object_store(v1::KEYS_STORE)?;
db.create_object_store(v1::CORE_STORE)?;
}
Ok(())
@@ -104,6 +110,15 @@ impl ClientStorage {
})
}
pub(crate) async fn read_gateway_config(
&self,
) -> Result<Option<GatewayEndpointConfig>, ClientStorageError> {
self.inner
.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_CONFIG))
.await
.map_err(Into::into)
}
async fn may_read_identity_keypair(
&self,
) -> Result<Option<identity::KeyPair>, ClientStorageError> {
@@ -228,4 +243,18 @@ impl ClientStorage {
.await
.map_err(Into::into)
}
pub(crate) async fn store_gateway_config(
&self,
gateway_endpoint: &GatewayEndpointConfig,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::CORE_STORE,
JsValue::from_str(v1::GATEWAY_CONFIG),
gateway_endpoint,
)
.await
.map_err(Into::into)
}
}
+36 -22
View File
@@ -3,7 +3,7 @@
use crate::constants::NODE_TESTER_ID;
use crate::error::WasmClientError;
use crate::helpers::current_network_topology_async;
use crate::helpers::{current_network_topology_async, gateway_from_topology};
use crate::storage::ClientStorage;
use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
use crate::tester::helpers::{
@@ -17,7 +17,6 @@ use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::key_manager::ManagedKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::GatewayClient;
use nym_node_tester_utils::receiver::SimpleMessageReceiver;
use nym_node_tester_utils::{NodeTester, TestMessage};
@@ -27,7 +26,9 @@ use nym_sphinx::params::PacketSize;
use nym_sphinx::preparer::PreparedFragment;
use nym_task::TaskManager;
use nym_topology::NymTopology;
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use rand::{CryptoRng, Rng};
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex as SyncMutex};
@@ -72,7 +73,7 @@ pub struct NymNodeTester {
#[wasm_bindgen]
pub struct NymNodeTesterBuilder {
gateway_config: GatewayEndpointConfig,
gateway: Option<IdentityKey>,
base_topology: NymTopology,
@@ -93,48 +94,61 @@ fn address(keys: &ManagedKeys, gateway_identity: NodeIdentity) -> Recipient {
impl NymNodeTesterBuilder {
#[wasm_bindgen(constructor)]
pub fn new(
gateway_config: GatewayEndpointConfig,
base_topology: WasmNymTopology,
gateway: Option<IdentityKey>,
) -> NymNodeTesterBuilder {
NymNodeTesterBuilder {
gateway_config,
gateway,
base_topology: base_topology.into(),
bandwidth_controller: None,
}
}
async fn _new_with_api(
gateway_config: GatewayEndpointConfig,
api_url: String,
gateway: Option<IdentityKey>,
) -> Result<Self, WasmClientError> {
let topology = current_network_topology_async(api_url).await?;
Ok(NymNodeTesterBuilder::new(gateway_config, topology))
Ok(NymNodeTesterBuilder::new(topology, gateway))
}
pub fn new_with_api(gateway_config: GatewayEndpointConfig, api_url: String) -> Promise {
pub fn new_with_api(gateway: Option<IdentityKey>, api_url: String) -> Promise {
future_to_promise(async move {
Self::_new_with_api(gateway_config, api_url)
Self::_new_with_api(api_url, gateway)
.await
.into_promise_result()
})
}
async fn gateway_info<R: Rng + CryptoRng>(
&self,
rng: &mut R,
client_store: &ClientStorage,
) -> Result<GatewayEndpointConfig, WasmClientError> {
gateway_from_topology(
rng,
self.gateway.as_deref(),
&self.base_topology,
client_store,
)
.await
}
async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> {
let mut rng = OsRng;
let task_manager = TaskManager::default();
let gateway_identity =
identity::PublicKey::from_base58_string(self.gateway_config.gateway_id)
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
let client_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?;
let key_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?;
let mut managed_keys = ManagedKeys::load_or_generate(&mut rng, &key_store).await;
let gateway_endpoint = self.gateway_info(&mut rng, &client_store).await?;
let gateway_identity = gateway_endpoint.try_get_gateway_identity_key()?;
let mut managed_keys = ManagedKeys::load_or_generate(&mut rng, &client_store).await;
let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded();
let mut gateway_client = GatewayClient::new(
self.gateway_config.gateway_listener,
gateway_endpoint.gateway_listener,
managed_keys.identity_keypair(),
gateway_identity,
managed_keys.gateway_shared_key(),
@@ -148,7 +162,7 @@ impl NymNodeTesterBuilder {
gateway_client.set_disabled_credentials_mode(true);
let shared_keys = gateway_client.authenticate_and_start().await?;
managed_keys
.deal_with_gateway_key(shared_keys, &key_store)
.deal_with_gateway_key(shared_keys, &client_store)
.await?;
// TODO: make those values configurable later
@@ -227,24 +241,24 @@ async fn test_mixnode(
impl NymNodeTester {
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(gateway_config: GatewayEndpointConfig, topology: WasmNymTopology) -> Promise {
pub fn new(topology: WasmNymTopology, gateway: Option<IdentityKey>) -> Promise {
console_log!("constructing node tester!");
NymNodeTesterBuilder::new(gateway_config, topology).setup_client()
NymNodeTesterBuilder::new(topology, gateway).setup_client()
}
async fn _new_with_api(
gateway_config: GatewayEndpointConfig,
api_url: String,
gateway: Option<IdentityKey>,
) -> Result<Self, WasmClientError> {
NymNodeTesterBuilder::_new_with_api(gateway_config, api_url)
NymNodeTesterBuilder::_new_with_api(api_url, gateway)
.await?
._setup_client()
.await
}
pub fn new_with_api(gateway_config: GatewayEndpointConfig, api_url: String) -> Promise {
pub fn new_with_api(api_url: String, gateway: Option<IdentityKey>) -> Promise {
future_to_promise(async move {
Self::_new_with_api(gateway_config, api_url)
Self::_new_with_api(api_url, gateway)
.await
.into_promise_result()
})
+7 -2
View File
@@ -6,7 +6,7 @@ use nym_crypto::asymmetric::{encryption, identity};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::{Layer, MixnodeConversionError};
use nym_topology::{gateway, mix, MixLayer, NymTopology};
use nym_validator_client::client::MixId;
use nym_validator_client::client::{IdentityKeyRef, MixId};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use thiserror::Error;
@@ -82,11 +82,16 @@ impl WasmNymTopology {
})
}
#[allow(dead_code)]
pub(crate) fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
}
pub(crate) fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
self.inner
.gateways()
.iter()
.any(|g| g.identity_key.to_base58_string() == gateway_config.gateway_id)
.any(|g| g.identity_key.to_base58_string() == gateway_id)
}
pub fn print(&self) {
+1 -1
View File
@@ -130,7 +130,7 @@ impl AsyncFileWatcher {
Ok(event) => {
let now = Instant::now();
if self.should_propagate(&event, now) {
self.last_received.insert(event.kind.clone(), now);
self.last_received.insert(event.kind, now);
if let Err(_err) = self.event_sender.unbounded_send(event) {
log::error!("the file watcher receiver has been dropped!");
}
+1 -1
View File
@@ -26,7 +26,7 @@ pub mod error;
#[cfg(target_arch = "wasm32")]
pub mod wasm_mockups;
pub struct BandwidthController<C, St: Storage> {
pub struct BandwidthController<C, St> {
storage: St,
client: C,
}
@@ -37,17 +37,19 @@ use nym_gateway_client::{
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_sphinx::params::PacketType;
use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use rand::thread_rng;
use rand::rngs::OsRng;
use std::sync::Arc;
use tap::TapFallible;
use url::Url;
#[cfg(target_arch = "wasm32")]
use nym_bandwidth_controller::wasm_mockups::DkgQueryClient;
#[cfg(not(target_arch = "wasm32"))]
use nym_validator_client::nyxd::traits::DkgQueryClient;
@@ -163,7 +165,7 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
reply_storage_backend: S::ReplyStore,
key_store: S::KeyStore,
custom_topology_provider: Option<Box<dyn TopologyProvider>>,
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
managed_keys: ManagedKeys,
}
@@ -216,7 +218,10 @@ where
}
}
pub fn with_topology_provider(mut self, provider: Box<dyn TopologyProvider>) -> Self {
pub fn with_topology_provider(
mut self,
provider: Box<dyn TopologyProvider + Send + Sync>,
) -> Self {
self.custom_topology_provider = Some(provider);
self
}
@@ -271,6 +276,7 @@ where
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
shutdown: TaskClient,
packet_type: PacketType,
) {
info!("Starting real traffic stream...");
@@ -286,7 +292,7 @@ where
lane_queue_lengths,
client_connection_rx,
)
.start_with_shutdown(shutdown);
.start_with_shutdown(shutdown, packet_type);
}
// buffer controlling all messages fetched from provider
@@ -367,9 +373,9 @@ where
}
fn setup_topology_provider(
custom_provider: Option<Box<dyn TopologyProvider>>,
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
nym_api_urls: Vec<Url>,
) -> Box<dyn TopologyProvider> {
) -> Box<dyn TopologyProvider + Send + Sync> {
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| {
Box::new(NymApiTopologyProvider::new(
@@ -382,7 +388,7 @@ where
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
topology_provider: Box<dyn TopologyProvider>,
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
topology_config: config::Topology,
topology_accessor: TopologyAccessor,
mut shutdown: TaskClient,
@@ -422,7 +428,7 @@ where
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// controller for sending packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
@@ -469,11 +475,14 @@ where
async fn initial_key_setup(&mut self) {
assert!(!self.managed_keys.is_valid());
let mut rng = thread_rng();
let mut rng = OsRng;
self.managed_keys = ManagedKeys::load_or_generate(&mut rng, &self.key_store).await;
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
pub async fn start_base(
mut self,
packet_type: PacketType,
) -> Result<BaseClient, ClientCoreError>
where
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
S::ReplyStore: Send + Sync,
@@ -544,11 +553,11 @@ where
task_manager.subscribe(),
);
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// The message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender =
let message_sender =
Self::start_mix_traffic_controller(gateway_client, task_manager.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic
@@ -570,13 +579,14 @@ where
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
message_sender.clone(),
reply_storage,
reply_controller_sender.clone(),
reply_controller_receiver,
shared_lane_queue_lengths.clone(),
client_connection_rx,
task_manager.subscribe(),
packet_type,
);
if !self
@@ -589,7 +599,7 @@ where
self.managed_keys.ack_key(),
self_address,
shared_topology_accessor.clone(),
sphinx_message_sender,
message_sender,
task_manager.subscribe(),
);
}
@@ -12,13 +12,18 @@ use nym_credential_storage::ephemeral_storage::{
};
use nym_credential_storage::storage::Storage as CredentialStorage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::base_client::non_wasm_helpers;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::key_manager::persistence::OnDiskKeys;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::config::{persistence::key_pathfinder::ClientKeyPathfinder, Config};
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::error::ClientCoreError;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::replies::reply_storage::fs_backend;
pub trait MixnetClientStorage {
@@ -69,14 +74,14 @@ impl MixnetClientStorage for Ephemeral {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub struct OnDiskPersistent {
pub(crate) key_store: OnDiskKeys,
pub(crate) reply_store: fs_backend::Backend,
pub(crate) credential_store: PersistentCredentialStorage,
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
impl OnDiskPersistent {
pub fn new(
key_store: OnDiskKeys,
@@ -89,9 +94,29 @@ impl OnDiskPersistent {
credential_store,
}
}
pub async fn from_config<T>(config: &Config<T>) -> Result<Self, ClientCoreError> {
let pathfinder = ClientKeyPathfinder::new_from_config(config);
let key_store = OnDiskKeys::new(pathfinder);
let reply_store = non_wasm_helpers::setup_fs_reply_surb_backend(
config.get_reply_surb_database_path(),
&config.get_debug_config().reply_surbs,
)
.await?;
let credential_store =
nym_credential_storage::initialise_persistent_storage(config.get_database_path()).await;
Ok(OnDiskPersistent {
key_store,
reply_store,
credential_store,
})
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
impl MixnetClientStorage for OnDiskPersistent {
type KeyStore = OnDiskKeys;
type ReplyStore = fs_backend::Backend;
@@ -45,7 +45,7 @@ where
#[cfg(target_arch = "wasm32")]
next_delay: Pin<Box<wasm_timer::Delay>>,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// Channel used for sending prepared nym packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: BatchMixMessageSender,
@@ -194,6 +194,7 @@ impl LoopCoverTrafficStream<OsRng> {
self.average_ack_delay,
self.cover_traffic.loop_cover_traffic_average_delay,
cover_traffic_packet_size,
nym_sphinx::params::PacketType::Mix,
)
.expect("Somehow failed to generate a loop cover message with a valid topology");
@@ -4,6 +4,7 @@
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
@@ -53,18 +54,49 @@ pub enum InputMessage {
data: Vec<u8>,
lane: TransmissionLane,
},
MessageWrapper {
message: Box<InputMessage>,
packet_type: PacketType,
},
}
impl InputMessage {
pub fn new_premade(msgs: Vec<MixPacket>, lane: TransmissionLane) -> Self {
InputMessage::Premade { msgs, lane }
pub fn new_premade(
msgs: Vec<MixPacket>,
lane: TransmissionLane,
packet_type: PacketType,
) -> Self {
let message = InputMessage::Premade { msgs, lane };
if packet_type == PacketType::Mix {
message
} else {
InputMessage::new_wrapper(message, packet_type)
}
}
pub fn new_regular(recipient: Recipient, data: Vec<u8>, lane: TransmissionLane) -> Self {
InputMessage::Regular {
pub fn new_wrapper(message: InputMessage, packet_type: PacketType) -> Self {
InputMessage::MessageWrapper {
message: Box::new(message),
packet_type,
}
}
pub fn new_regular(
recipient: Recipient,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
let message = InputMessage::Regular {
recipient,
data,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -73,12 +105,18 @@ impl InputMessage {
data: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
InputMessage::Anonymous {
let message = InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -86,11 +124,17 @@ impl InputMessage {
recipient_tag: AnonymousSenderTag,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
InputMessage::Reply {
let message = InputMessage::Reply {
recipient_tag,
data,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -100,6 +144,7 @@ impl InputMessage {
| InputMessage::Anonymous { lane, .. }
| InputMessage::Reply { lane, .. }
| InputMessage::Premade { lane, .. } => lane,
InputMessage::MessageWrapper { message, .. } => message.lane(),
}
}
}
@@ -37,22 +37,27 @@ impl ManagedKeys {
!matches!(self, ManagedKeys::Invalidated)
}
pub async fn must_load<S: KeyStore>(key_store: &S) -> Result<Self, S::StorageError> {
pub async fn try_load<S: KeyStore>(key_store: &S) -> Result<Self, S::StorageError> {
Ok(ManagedKeys::FullyDerived(
KeyManager::load_keys(key_store).await?,
))
}
pub fn generate_new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
ManagedKeys::Initial(KeyManagerBuilder::new(rng))
}
pub async fn load_or_generate<R, S>(rng: &mut R, key_store: &S) -> Self
where
R: RngCore + CryptoRng,
S: KeyStore,
{
if let Ok(loaded) = KeyManager::load_keys(key_store).await {
ManagedKeys::FullyDerived(loaded)
} else {
ManagedKeys::Initial(KeyManagerBuilder::new(rng))
}
Self::try_load(key_store)
.await
.unwrap_or_else(|_| Self::generate_new(rng))
}
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
@@ -8,6 +8,8 @@ use std::error::Error;
#[cfg(not(target_arch = "wasm32"))]
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
#[cfg(not(target_arch = "wasm32"))]
use crate::config::Config;
#[cfg(not(target_arch = "wasm32"))]
use nym_crypto::asymmetric::{encryption, identity};
#[cfg(not(target_arch = "wasm32"))]
use nym_gateway_requests::registration::handshake::SharedKeys;
@@ -79,6 +81,10 @@ impl OnDiskKeys {
OnDiskKeys { pathfinder }
}
pub fn from_config<T>(config: &Config<T>) -> Self {
OnDiskKeys::new(ClientKeyPathfinder::new_from_config(config))
}
fn load_key<T: PemStorableKey>(
&self,
path: &std::path::Path,
+3 -3
View File
@@ -40,15 +40,15 @@ where
pub fn new(
gateway_client: GatewayClient<C, St>,
) -> (MixTrafficController<C, St>, BatchMixMessageSender) {
let (sphinx_message_sender, sphinx_message_receiver) =
let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
(
MixTrafficController {
gateway_client,
mix_rx: sphinx_message_receiver,
mix_rx: message_receiver,
consecutive_gateway_failure_count: 0,
},
sphinx_message_sender,
message_sender,
)
}
@@ -71,7 +71,7 @@ impl AcknowledgementListener {
while !shutdown.is_shutdown() {
tokio::select! {
acks = self.ack_receiver.next() => match acks {
Some(acks) => self.handle_ack_receiver_item(acks).await,
Some(acks) => {self.handle_ack_receiver_item(acks).await}
None => {
log::trace!("AcknowledgementListener: Stopping since channel closed");
break;
@@ -9,6 +9,7 @@ use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng};
@@ -71,10 +72,11 @@ where
recipient: Recipient,
content: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
) {
if let Err(err) = self
.message_handler
.try_send_plain_message(recipient, content, lane)
.try_send_plain_message(recipient, content, lane, packet_type)
.await
{
warn!("failed to send a plain message - {err}")
@@ -87,10 +89,11 @@ where
content: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
) {
if let Err(err) = self
.message_handler
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane)
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type)
.await
{
warn!("failed to send a repliable message - {err}")
@@ -103,14 +106,17 @@ where
recipient,
data,
lane,
} => self.handle_plain_message(recipient, data, lane).await,
} => {
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
.await
}
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane)
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
.await
}
InputMessage::Reply {
@@ -121,6 +127,40 @@ where
self.handle_reply(recipient_tag, data, lane).await;
}
InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
InputMessage::MessageWrapper {
message,
packet_type,
} => match *message {
InputMessage::Regular {
recipient,
data,
lane,
} => {
self.handle_plain_message(recipient, data, lane, packet_type)
.await
}
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
.await
}
InputMessage::Reply {
recipient_tag,
data,
lane,
} => {
self.handle_reply(recipient_tag, data, lane).await;
}
InputMessage::Premade { msgs, lane } => {
self.handle_premade_packets(msgs, lane).await
}
// MessageWrappers can't be nested
InputMessage::MessageWrapper { .. } => unimplemented!(),
},
};
}
@@ -16,7 +16,7 @@ use futures::channel::mpsc;
use log::*;
use nym_gateway_client::AcknowledgementReceiver;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketSize;
use nym_sphinx::params::{PacketSize, PacketType};
use nym_sphinx::{
acknowledgements::AckKey,
addressing::clients::Recipient,
@@ -249,7 +249,11 @@ where
}
}
pub(super) fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
pub(super) fn start_with_shutdown(
self,
shutdown: nym_task::TaskClient,
packet_type: PacketType,
) {
let mut acknowledgement_listener = self.acknowledgement_listener;
let mut input_message_listener = self.input_message_listener;
let mut retransmission_request_listener = self.retransmission_request_listener;
@@ -275,7 +279,7 @@ where
let shutdown_handle = shutdown.clone();
spawn_future(async move {
retransmission_request_listener
.run_with_shutdown(shutdown_handle)
.run_with_shutdown(shutdown_handle, packet_type)
.await;
debug!("The retransmission request listener has finished execution!");
});
@@ -11,9 +11,9 @@ use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use crate::client::replies::reply_controller::ReplyControllerSender;
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::chunking::fragment::Fragment;
use nym_sphinx::preparer::PreparedFragment;
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak};
@@ -48,17 +48,20 @@ where
&mut self,
packet_recipient: Recipient,
chunk_data: Fragment,
packet_type: PacketType,
) -> Result<PreparedFragment, PreparationError> {
debug!("retransmitting normal packet...");
// TODO: Figure out retransmission packet type signaling
self.message_handler
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data)
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type)
.await
}
async fn on_retransmission_request(
&mut self,
weak_timed_out_ack: Weak<PendingAcknowledgement>,
packet_type: PacketType,
) {
let timed_out_ack = match weak_timed_out_ack.upgrade() {
Some(timed_out_ack) => timed_out_ack,
@@ -85,6 +88,7 @@ where
self.prepare_normal_retransmission_chunk(
**recipient,
timed_out_ack.message_chunk.clone(),
packet_type,
)
.await
}
@@ -140,13 +144,17 @@ where
.await
}
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
pub(super) async fn run_with_shutdown(
&mut self,
mut shutdown: nym_task::TaskClient,
packet_type: PacketType,
) {
debug!("Started RetransmissionRequestListener with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack).await,
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack, packet_type).await,
None => {
log::trace!("RetransmissionRequestListener: Stopping since channel closed");
break;
@@ -15,7 +15,7 @@ use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessa
use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey};
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx::message::NymMessage;
use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::params::{PacketSize, PacketType, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
use nym_sphinx::Delay;
use nym_task::connections::TransmissionLane;
@@ -27,7 +27,7 @@ use std::time::Duration;
use thiserror::Error;
// TODO: move that error elsewhere since it seems to be contaminating different files
#[derive(Debug, Clone, Error)]
#[derive(Debug, Error)]
pub enum PreparationError {
#[error(transparent)]
NymTopologyError(#[from] NymTopologyError),
@@ -417,9 +417,10 @@ where
recipient: Recipient,
message: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), PreparationError> {
let message = NymMessage::new_plain(message);
self.try_split_and_send_non_reply_message(message, recipient, lane)
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
.await
}
@@ -428,7 +429,9 @@ where
message: NymMessage,
recipient: Recipient,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), PreparationError> {
debug!("Sending non-reply message with packet type {packet_type}");
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
debug_assert!(!matches!(message, NymMessage::Reply(_)));
@@ -436,7 +439,11 @@ where
let topology_permit = self.topology_access.get_read_permit().await;
let topology = self.get_topology(&topology_permit)?;
let packet_size = self.optimal_packet_size(&message);
let packet_size = if packet_type == PacketType::Outfox {
PacketSize::OutfoxRegularPacket
} else {
self.optimal_packet_size(&message)
};
debug!("Using {packet_size} packets for {message}");
let fragments = self
.message_preparer
@@ -453,6 +460,7 @@ where
topology,
&self.config.ack_key,
&recipient,
packet_type,
)?;
let real_message = RealMessage::new(
@@ -476,7 +484,9 @@ where
&mut self,
recipient: Recipient,
amount: u32,
packet_type: PacketType,
) -> Result<(), PreparationError> {
debug!("Sending additional reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
let (reply_surbs, reply_keys) =
self.generate_reply_surbs_with_keys(amount as usize).await?;
@@ -490,6 +500,7 @@ where
message,
recipient,
TransmissionLane::AdditionalReplySurbs,
packet_type,
)
.await?;
@@ -505,7 +516,9 @@ where
message: Vec<u8>,
num_reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), SurbWrappedPreparationError> {
debug!("Sending message with reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
let (reply_surbs, reply_keys) = self
.generate_reply_surbs_with_keys(num_reply_surbs as usize)
@@ -514,7 +527,7 @@ where
let message =
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
self.try_split_and_send_non_reply_message(message, recipient, lane)
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
.await?;
log::trace!("storing {} reply keys", reply_keys.len());
@@ -527,13 +540,21 @@ where
&mut self,
recipient: Recipient,
chunk: Fragment,
packet_type: PacketType,
) -> Result<PreparedFragment, PreparationError> {
debug!("Sending single chunk with packet type {packet_type}");
let topology_permit = self.topology_access.get_read_permit().await;
let topology = self.get_topology(&topology_permit)?;
let prepared_fragment = self
.message_preparer
.prepare_chunk_for_sending(chunk, topology, &self.config.ack_key, &recipient)
.prepare_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
&recipient,
packet_type,
)
.unwrap();
Ok(prepared_fragment)
@@ -569,6 +590,7 @@ where
topology,
&self.config.ack_key,
reply_surb,
PacketType::Mix,
)
.unwrap()
})
@@ -588,7 +610,13 @@ where
let prepared_fragment = self
.message_preparer
.prepare_reply_chunk_for_sending(chunk, topology, &self.config.ack_key, reply_surb)
.prepare_reply_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
reply_surb,
PacketType::Mix,
)
.unwrap();
Ok(prepared_fragment)
@@ -26,6 +26,7 @@ use log::*;
use nym_gateway_client::AcknowledgementReceiver;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType;
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
@@ -207,7 +208,7 @@ impl RealMessagesController<OsRng> {
}
}
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient, packet_type: PacketType) {
let mut out_queue_control = self.out_queue_control;
let ack_control = self.ack_control;
let mut reply_control = self.reply_control;
@@ -223,6 +224,6 @@ impl RealMessagesController<OsRng> {
debug!("The reply controller has finished execution!");
});
ack_control.start_with_shutdown(shutdown);
ack_control.start_with_shutdown(shutdown, packet_type);
}
}
@@ -92,7 +92,7 @@ where
// messages.
sending_delay_controller: SendingDelayController,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// Channel used for sending prepared packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: BatchMixMessageSender,
@@ -136,7 +136,7 @@ impl From<PreparedFragment> for RealMessage {
impl RealMessage {
pub(crate) fn packet_size(&self) -> usize {
self.mix_packet.sphinx_packet().len()
self.mix_packet.packet().len()
}
pub(crate) fn new(mix_packet: MixPacket, fragment_id: Option<FragmentIdentifier>) -> Self {
@@ -247,6 +247,7 @@ where
self.config.average_ack_delay,
self.config.traffic.average_packet_delay,
cover_traffic_packet_size,
self.config.traffic.packet_type.unwrap_or_default(),
)
.expect(
"Somehow failed to generate a loop cover message with a valid topology",
@@ -386,7 +387,7 @@ where
// On every iteration we get new messages from upstream. Given that these come bunched
// in `Vec`, this ensures that on average we will fetch messages faster than we can
// send, which is a condition for being able to multiplex sphinx packets from multiple
// send, which is a condition for being able to multiplex packets from multiple
// data streams.
match Pin::new(&mut self.real_receiver).poll_recv(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
@@ -512,7 +512,11 @@ where
let to_send = min(remaining, 100);
if let Err(err) = self
.message_handler
.try_send_additional_reply_surbs(recipient, to_send)
.try_send_additional_reply_surbs(
recipient,
to_send,
nym_sphinx::params::PacketType::Mix,
)
.await
{
warn!("failed to send additional surbs to {recipient} - {err}");
@@ -26,7 +26,7 @@ impl TopologyRefresherConfig {
}
pub struct TopologyRefresher {
topology_provider: Box<dyn TopologyProvider>,
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
topology_accessor: TopologyAccessor,
refresh_rate: Duration,
@@ -37,7 +37,7 @@ impl TopologyRefresher {
pub fn new(
cfg: TopologyRefresherConfig,
topology_accessor: TopologyAccessor,
topology_provider: Box<dyn TopologyProvider>,
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
) -> Self {
TopologyRefresher {
topology_provider,
@@ -47,7 +47,7 @@ impl TopologyRefresher {
}
}
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider>) {
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider + Send + Sync>) {
self.topology_provider = provider;
}
@@ -28,7 +28,7 @@ impl SizedData for RealMessage {
impl SizedData for Fragment {
fn data_size(&self) -> usize {
// note that raw `Fragment` is smaller than sphinx packet payload
// note that raw `Fragment` is smaller than packet payload
// as it doesn't include surb-ack or the [shared] key materials
self.payload_size()
}
+67 -2
View File
@@ -3,19 +3,30 @@
use nym_config::defaults::NymNetworkDetails;
use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME};
use nym_sphinx::params::PacketSize;
use nym_crypto::asymmetric::identity;
use nym_sphinx::params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::time::Duration;
use url::Url;
use crate::error::ClientCoreError;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
pub mod old_config_v1_1_13;
pub mod persistence;
pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem";
pub const DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem";
pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem";
pub const DEFAULT_GATEWAY_KEYS_FILENAME: &str = "gateway_shared.pem";
pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem";
pub const DEFAULT_REPLY_STORE_FILENAME: &str = "persistent_reply_store.sqlite";
pub const DEFAULT_CREDENTIAL_STORE_FILENAME: &str = CRED_DB_FILE_NAME;
pub const MISSING_VALUE: &str = "MISSING VALUE";
// 'DEBUG'
@@ -107,6 +118,37 @@ impl<T> Config<T> {
self
}
#[must_use]
#[doc(hidden)]
// TODO: this totally contradicts our trait... we REALLY have to refactor it...
pub fn reset_data_directory<P: AsRef<Path>>(mut self, dir: P) -> Self {
self.client.private_identity_key_file =
dir.as_ref().join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME);
self.client.public_identity_key_file =
dir.as_ref().join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME);
self.client.private_encryption_key_file =
dir.as_ref().join(DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME);
self.client.public_encryption_key_file =
dir.as_ref().join(DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME);
self.client.gateway_shared_key_file = dir.as_ref().join(DEFAULT_GATEWAY_KEYS_FILENAME);
self.client.ack_key_file = dir.as_ref().join(DEFAULT_ACK_KEY_FILENAME);
self.client.reply_surb_database_path = dir.as_ref().join(DEFAULT_REPLY_STORE_FILENAME);
self.client.database_path = dir.as_ref().join(DEFAULT_CREDENTIAL_STORE_FILENAME);
self
}
#[must_use]
#[doc(hidden)]
// TODO: this totally contradicts our trait... we REALLY have to refactor it...
pub fn reset_nym_root_directory<P: AsRef<Path>>(mut self, dir: P) -> Self
where
T: NymConfig,
{
self.client.nym_root_directory = dir.as_ref().to_owned();
self
}
pub fn set_empty_fields_to_defaults(&mut self) -> bool
where
T: NymConfig,
@@ -217,6 +259,11 @@ impl<T> Config<T> {
self
}
pub fn with_packet_type(mut self, packet_type: PacketType) -> Self {
self.client.packet_type = Some(packet_type);
self
}
pub fn set_high_default_traffic_volume(&mut self) {
self.debug.traffic.average_packet_delay = Duration::from_millis(10);
// basically don't really send cover messages
@@ -404,6 +451,10 @@ impl<T> Config<T> {
pub fn get_maximum_reply_key_age(&self) -> Duration {
self.debug.reply_surbs.maximum_reply_key_age
}
pub fn get_packet_type(&self) -> PacketType {
self.client.packet_type.unwrap_or(PacketType::Mix)
}
}
impl<T: NymConfig> Default for Config<T> {
@@ -446,6 +497,14 @@ impl GatewayEndpointConfig {
}
}
// separate block so it wouldn't be exported via wasm bindgen
impl GatewayEndpointConfig {
pub fn try_get_gateway_identity_key(&self) -> Result<identity::PublicKey, ClientCoreError> {
identity::PublicKey::from_base58_string(&self.gateway_id)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)
}
}
impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
fn from(node: nym_topology::gateway::Node) -> GatewayEndpointConfig {
let gateway_listener = node.clients_address();
@@ -518,6 +577,8 @@ pub struct Client<T> {
#[serde(skip)]
pub super_struct: PhantomData<T>,
pub packet_type: Option<PacketType>,
}
impl<T: NymConfig> Default for Client<T> {
@@ -556,6 +617,7 @@ impl<T: NymConfig> Default for Client<T> {
reply_surb_database_path: Default::default(),
nym_root_directory: T::default_root_directory(),
super_struct: Default::default(),
packet_type: Default::default(),
}
}
}
@@ -627,6 +689,8 @@ pub struct Traffic {
/// Note that its use decreases overall anonymity.
/// Do not set it it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>,
pub packet_type: Option<PacketType>,
}
impl Traffic {
@@ -650,6 +714,7 @@ impl Default for Traffic {
disable_main_poisson_packet_distribution: false,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: None,
}
}
}
@@ -125,6 +125,7 @@ impl From<OldDebugConfigV1_1_13> for DebugConfig {
.disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: value.use_extended_packet_size.map(Into::into),
packet_type: None,
},
cover_traffic: CoverTraffic {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
@@ -210,8 +211,8 @@ impl<T, U> From<OldConfigV1_1_13<T>> for Config<U> {
database_path: value.client.database_path,
reply_surb_database_path: value.client.reply_surb_database_path,
nym_root_directory: value.client.nym_root_directory,
super_struct: PhantomData,
packet_type: Some(nym_sphinx::params::PacketType::Mix),
},
logging: value.logging,
debug: value.debug.into(),
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::config::Config;
use nym_config::NymConfig;
use std::path::{Path, PathBuf};
#[derive(Debug)]
@@ -29,7 +28,7 @@ impl ClientKeyPathfinder {
}
}
pub fn new_from_config<T: NymConfig>(config: &Config<T>) -> Self {
pub fn new_from_config<T>(config: &Config<T>) -> Self {
ClientKeyPathfinder {
identity_private_key: config.get_private_identity_key_file(),
identity_public_key: config.get_public_identity_key_file(),
@@ -41,11 +40,17 @@ impl ClientKeyPathfinder {
}
pub fn identity_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(self.private_identity_key(), self.public_identity_key())
nym_pemstore::KeyPairPath::new(
self.private_identity_key().to_path_buf(),
self.public_identity_key().to_path_buf(),
)
}
pub fn encryption_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(self.private_encryption_key(), self.public_encryption_key())
nym_pemstore::KeyPairPath::new(
self.private_encryption_key().to_path_buf(),
self.public_encryption_key().to_path_buf(),
)
}
pub fn any_file_exists(&self) -> bool {
+5
View File
@@ -92,6 +92,11 @@ pub enum ClientCoreError {
#[error("Unexpected exit")]
UnexpectedExit,
#[error(
"This operation would have resulted in clients keys being overwritten without permission"
)]
ForbiddenKeyOverwrite,
}
/// Set of messages that the client can send to listeners via the task manager
+12 -53
View File
@@ -1,15 +1,15 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::GatewayEndpointConfig;
use crate::error::ClientCoreError;
use futures::{SinkExt, StreamExt};
use log::{debug, info, trace, warn};
use nym_credential_storage::storage::Storage;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::GatewayClient;
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_topology::{filter::VersionFilterable, gateway};
use rand::{seq::SliceRandom, thread_rng, Rng};
use rand::{seq::SliceRandom, Rng};
use std::{sync::Arc, time::Duration};
use tap::TapFallible;
use tungstenite::Message;
@@ -27,12 +27,6 @@ use tokio_tungstenite::connect_async;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(not(target_arch = "wasm32"))]
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
#[cfg(not(target_arch = "wasm32"))]
use crate::client::key_manager::persistence::OnDiskKeys;
#[cfg(not(target_arch = "wasm32"))]
use crate::config::{persistence::key_pathfinder::ClientKeyPathfinder, Config};
#[cfg(not(target_arch = "wasm32"))]
use nym_config::NymConfig;
#[cfg(target_arch = "wasm32")]
use nym_bandwidth_controller::wasm_mockups::DirectSigningNyxdClient;
@@ -61,9 +55,9 @@ impl GatewayWithLatency {
}
}
async fn current_gateways<R: Rng>(
pub(super) async fn current_gateways<R: Rng>(
rng: &mut R,
nym_apis: Vec<Url>,
nym_apis: &[Url],
) -> Result<Vec<gateway::Node>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
@@ -160,7 +154,7 @@ async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, C
Ok(GatewayWithLatency::new(gateway, avg))
}
async fn choose_gateway_by_latency<R: Rng>(
pub(super) async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
@@ -193,7 +187,7 @@ async fn choose_gateway_by_latency<R: Rng>(
Ok(chosen.gateway.clone())
}
fn uniformly_random_gateway<R: Rng>(
pub(super) fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
@@ -203,39 +197,14 @@ fn uniformly_random_gateway<R: Rng>(
.cloned()
}
pub(super) async fn query_gateway_details(
validator_servers: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<gateway::Node, ClientCoreError> {
let mut rng = thread_rng();
let gateways = current_gateways(&mut rng, validator_servers).await?;
// if we set an explicit gateway, use that one and nothing else
if let Some(explicitly_chosen) = chosen_gateway_id {
gateways
.into_iter()
.find(|gateway| gateway.identity_key == explicitly_chosen)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(explicitly_chosen.to_string()))
} else if by_latency {
choose_gateway_by_latency(&mut rng, gateways).await
} else {
uniformly_random_gateway(&mut rng, gateways)
}
}
pub(super) async fn register_with_gateway<St>(
gateway: &gateway::Node,
pub(super) async fn register_with_gateway(
gateway: &GatewayEndpointConfig,
our_identity: Arc<identity::KeyPair>,
) -> Result<Arc<SharedKeys>, ClientCoreError>
where
St: Storage,
<St as Storage>::StorageError: Send + Sync + 'static,
{
) -> Result<Arc<SharedKeys>, ClientCoreError> {
let timeout = Duration::from_millis(1500);
let mut gateway_client: GatewayClient<DirectSigningNyxdClient, St> = GatewayClient::new_init(
gateway.clients_address(),
gateway.identity_key,
let mut gateway_client: GatewayClient<DirectSigningNyxdClient, _> = GatewayClient::new_init(
gateway.gateway_listener.clone(),
gateway.try_get_gateway_identity_key()?,
our_identity.clone(),
timeout,
);
@@ -249,13 +218,3 @@ where
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
Ok(shared_keys)
}
// TODO: make it generic
#[cfg(not(target_arch = "wasm32"))]
pub(super) fn on_disk_key_store<T>(config: &Config<T>) -> OnDiskKeys
where
T: NymConfig,
{
let pathfinder = ClientKeyPathfinder::new_from_config(config);
OnDiskKeys::new(pathfinder)
}
+160 -52
View File
@@ -3,7 +3,10 @@
//! Collection of initialization steps used by client implementations
use crate::client::key_manager::KeyManager;
use crate::client::base_client::storage::MixnetClientStorage;
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::{KeyManager, ManagedKeys};
use crate::init::helpers::{choose_gateway_by_latency, current_gateways, uniformly_random_gateway};
use crate::{
config::{
persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config,
@@ -12,18 +15,101 @@ use crate::{
error::ClientCoreError,
};
use nym_config::NymConfig;
use nym_credential_storage::storage::Storage;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use serde::Serialize;
use std::fmt::Display;
use std::sync::Arc;
use std::fmt::{Debug, Display};
use tap::TapFallible;
use url::Url;
mod helpers;
#[derive(Clone)]
pub enum GatewaySetup {
/// Specifies usage of a new, random, gateway.
New {
/// Should the new gateway be selected based on latency.
by_latency: bool,
},
Specified {
/// Identity key of the gateway we want to try to use.
gateway_identity: IdentityKey,
},
Predefined {
/// Full gateway configuration
config: GatewayEndpointConfig,
},
}
impl From<GatewayEndpointConfig> for GatewaySetup {
fn from(config: GatewayEndpointConfig) -> Self {
GatewaySetup::Predefined { config }
}
}
impl From<IdentityKey> for GatewaySetup {
fn from(gateway_identity: IdentityKey) -> Self {
GatewaySetup::Specified { gateway_identity }
}
}
impl Default for GatewaySetup {
fn default() -> Self {
GatewaySetup::New { by_latency: false }
}
}
impl GatewaySetup {
pub fn new(
full_config: Option<GatewayEndpointConfig>,
gateway_identity: Option<IdentityKey>,
latency_based_selection: Option<bool>,
) -> Self {
if let Some(config) = full_config {
GatewaySetup::Predefined { config }
} else if let Some(gateway_identity) = gateway_identity {
GatewaySetup::Specified { gateway_identity }
} else {
GatewaySetup::New {
by_latency: latency_based_selection.unwrap_or_default(),
}
}
}
pub async fn try_get_gateway_details(
self,
validator_servers: &[Url],
) -> Result<GatewayEndpointConfig, ClientCoreError> {
match self {
GatewaySetup::New { by_latency } => {
let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers).await?;
if by_latency {
choose_gateway_by_latency(&mut rng, gateways).await
} else {
uniformly_random_gateway(&mut rng, gateways)
}
}
.map(Into::into),
GatewaySetup::Specified { gateway_identity } => {
let user_gateway = identity::PublicKey::from_base58_string(&gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers).await?;
gateways
.into_iter()
.find(|gateway| gateway.identity_key == user_gateway)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))
}
.map(Into::into),
GatewaySetup::Predefined { config } => Ok(config),
}
}
}
/// Struct describing the results of the client initialization procedure.
#[derive(Debug, Serialize)]
pub struct InitResults {
@@ -62,29 +148,55 @@ impl Display for InitResults {
}
}
/// Authenticate and register with a gateway.
/// Either pick one at random by querying the available gateways from the nym-api, or use the
/// chosen one if it's among the available ones.
/// The shared key is added to the supplied `KeyManager` and the endpoint details are returned.
pub async fn register_with_gateway<St>(
identity_keys: Arc<identity::KeyPair>,
nym_api_endpoints: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<(GatewayEndpointConfig, Arc<SharedKeys>), ClientCoreError>
/// Recovers the already present gateway information or attempts to register with new gateway
/// and stores the newly obtained key
pub async fn get_registered_gateway<S>(
validator_servers: Vec<Url>,
key_store: &S::KeyStore,
setup: GatewaySetup,
overwrite_keys: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError>
where
St: Storage,
<St as Storage>::StorageError: Send + Sync + 'static,
S: MixnetClientStorage,
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
{
// Get the gateway details of the gateway we will use
let gateway =
helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id, by_latency).await?;
log::debug!("Querying gateway gives: {gateway}");
let mut rng = OsRng;
// try load keys
let mut managed_keys = match ManagedKeys::try_load(key_store).await {
Ok(_) => {
// if we loaded something and we don't have full gateway details, check if we can overwrite the data
if let GatewaySetup::Predefined { config } = setup {
// we already have defined gateway details AND a shared key, so nothing more for us to do
return Ok(config);
} else if overwrite_keys {
ManagedKeys::generate_new(&mut rng)
} else {
return Err(ClientCoreError::ForbiddenKeyOverwrite);
}
}
Err(_) => ManagedKeys::generate_new(&mut rng),
};
// choose gateway
let gateway_details = setup.try_get_gateway_details(&validator_servers).await?;
// get our identity key
let our_identity = managed_keys.identity_keypair();
// Establish connection, authenticate and generate keys for talking with the gateway
let shared_keys = helpers::register_with_gateway::<St>(&gateway, identity_keys).await?;
let shared_keys = helpers::register_with_gateway(&gateway_details, our_identity).await?;
Ok((gateway.into(), shared_keys))
managed_keys
.deal_with_gateway_key(shared_keys, key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
// TODO: here we should be probably persisting gateway details as opposed to returning them
Ok(gateway_details)
}
/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the
@@ -94,8 +206,8 @@ where
/// b. Create a new gateway configuration but keep existing keys. This assumes that the caller
/// knows what they are doing and that the keys match the requested gateway.
/// c. Create a new gateway configuration with a newly registered gateway and keys.
#[cfg(not(target_arch = "wasm32"))]
pub async fn setup_gateway_from_config<C, T, St>(
pub async fn setup_gateway_from_config<C, T, KSt>(
key_store: &KSt,
register_gateway: bool,
user_chosen_gateway_id: Option<identity::PublicKey>,
config: &Config<T>,
@@ -104,8 +216,8 @@ pub async fn setup_gateway_from_config<C, T, St>(
where
C: NymConfig + ClientCoreConfigTrait,
T: NymConfig,
St: Storage,
<St as Storage>::StorageError: Send + Sync + 'static,
KSt: KeyStore,
<KSt as KeyStore>::StorageError: Send + Sync + 'static,
{
let id = config.get_id();
@@ -116,41 +228,42 @@ where
return load_existing_gateway_config::<C>(&id);
}
let gateway_setup = GatewaySetup::new(
None,
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(by_latency),
);
// Else, we proceed by querying the nym-api
let gateway = helpers::query_gateway_details(
config.get_nym_api_endpoints(),
user_chosen_gateway_id,
by_latency,
)
.await?;
log::debug!("Querying gateway gives: {}", gateway);
let gateway = gateway_setup
.try_get_gateway_details(&config.get_nym_api_endpoints())
.await?;
log::debug!("Querying gateway gives: {:?}", gateway);
// If we are not registering, just return this and assume the caller has the keys already and
// wants to keep the,
if !register_gateway && user_chosen_gateway_id.is_some() {
eprintln!("Using gateway provided by user, keeping existing keys");
return Ok(gateway.into());
return Ok(gateway);
}
let key_store = helpers::on_disk_key_store(config);
let mut rng = rand::thread_rng();
let mut rng = OsRng;
let mut managed_keys =
crate::client::key_manager::ManagedKeys::load_or_generate(&mut rng, &key_store).await;
crate::client::key_manager::ManagedKeys::load_or_generate(&mut rng, key_store).await;
// Create new keys and derive our identity
let our_identity = managed_keys.identity_keypair();
// Establish connection, authenticate and generate keys for talking with the gateway
eprintln!("Registering with new gateway");
let shared_keys = helpers::register_with_gateway::<St>(&gateway, our_identity).await?;
let shared_keys = helpers::register_with_gateway(&gateway, our_identity).await?;
managed_keys
.deal_with_gateway_key(shared_keys, &key_store)
.deal_with_gateway_key(shared_keys, key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
Ok(gateway.into())
Ok(gateway)
}
/// Read and reuse the existing gateway configuration from a file that was generate earlier.
@@ -187,7 +300,8 @@ pub fn get_client_address(
}
/// Get the client address by loading the keys from stored files.
pub fn get_client_address_from_stored_keys<T>(
// TODO: rethink that sucker
pub fn get_client_address_from_stored_ondisk_keys<T>(
config: &Config<T>,
) -> Result<Recipient, ClientCoreError>
where
@@ -197,11 +311,8 @@ where
pathfinder: &ClientKeyPathfinder,
) -> Result<identity::KeyPair, ClientCoreError> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
pathfinder.private_identity_key().to_owned(),
pathfinder.public_identity_key().to_owned(),
))
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
nym_pemstore::load_keypair(&pathfinder.identity_key_pair_path())
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
Ok(identity_keypair)
}
@@ -209,11 +320,8 @@ where
pathfinder: &ClientKeyPathfinder,
) -> Result<encryption::KeyPair, ClientCoreError> {
let sphinx_keypair: encryption::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
pathfinder.private_encryption_key().to_owned(),
pathfinder.public_encryption_key().to_owned(),
))
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
nym_pemstore::load_keypair(&pathfinder.encryption_key_pair_path())
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
Ok(sphinx_keypair)
}
+54 -48
View File
@@ -12,6 +12,8 @@ use futures::{SinkExt, StreamExt};
use log::*;
use nym_bandwidth_controller::BandwidthController;
use nym_coconut_interface::Credential;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::identity;
use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use nym_gateway_requests::iv::IV;
@@ -26,7 +28,6 @@ use std::sync::Arc;
use std::time::Duration;
use tungstenite::protocol::Message;
use nym_credential_storage::storage::Storage;
#[cfg(not(target_arch = "wasm32"))]
use nym_validator_client::nyxd::traits::DkgQueryClient;
#[cfg(not(target_arch = "wasm32"))]
@@ -42,7 +43,7 @@ use wasm_utils::websocket::JSWebsocket;
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
pub struct GatewayClient<C, St: Storage> {
pub struct GatewayClient<C, St> {
authenticated: bool,
disabled_credentials_mode: bool,
bandwidth_remaining: i64,
@@ -68,12 +69,7 @@ pub struct GatewayClient<C, St: Storage> {
shutdown: TaskClient,
}
impl<C, St> GatewayClient<C, St>
where
C: Sync + Send,
St: Storage,
<St as Storage>::StorageError: Send + Sync + 'static,
{
impl<C, St> GatewayClient<C, St> {
// TODO: put it all in a Config struct
#[allow(clippy::too_many_arguments)]
pub fn new(
@@ -124,40 +120,6 @@ where
self.reconnection_backoff = backoff
}
pub fn new_init(
gateway_address: String,
gateway_identity: identity::PublicKey,
local_identity: Arc<identity::KeyPair>,
response_timeout_duration: Duration,
) -> Self {
use futures::channel::mpsc;
// note: this packet_router is completely invalid in normal circumstances, but "works"
// perfectly fine here, because it's not meant to be used
let (ack_tx, _) = mpsc::unbounded();
let (mix_tx, _) = mpsc::unbounded();
let shutdown = TaskClient::dummy();
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
GatewayClient::<C, St> {
authenticated: false,
disabled_credentials_mode: true,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
local_identity,
shared_key: None,
connection: SocketState::NotConnected,
packet_router,
response_timeout_duration,
bandwidth_controller: None,
should_reconnect_on_failure: false,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
shutdown,
}
}
pub fn gateway_identity(&self) -> identity::PublicKey {
self.gateway_identity
}
@@ -569,7 +531,9 @@ where
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError>
where
C: DkgQueryClient,
C: DkgQueryClient + Send + Sync,
St: CredentialStorage,
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
{
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
@@ -607,7 +571,7 @@ where
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
packets
.iter()
.map(|packet| packet.sphinx_packet().len())
.map(|packet| packet.packet().len())
.sum::<usize>() as i64
}
@@ -615,6 +579,8 @@ where
&mut self,
packets: Vec<MixPacket>,
) -> Result<(), GatewayClientError> {
debug!("Sending {} mix packets", packets.len());
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
@@ -659,9 +625,10 @@ where
) -> Result<(), GatewayClientError> {
if let Err(err) = self.send_websocket_message_without_response(msg).await {
if err.is_closed_connection() && self.should_reconnect_on_failure {
info!("Going to attempt a reconnection");
debug!("Going to attempt a reconnection");
self.attempt_reconnection().await
} else {
warn!("{err}");
Err(err)
}
} else {
@@ -688,9 +655,9 @@ where
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
if (mix_packet.packet().len() as i64) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth(
mix_packet.sphinx_packet().len() as i64,
mix_packet.packet().len() as i64,
self.bandwidth_remaining,
));
}
@@ -773,7 +740,9 @@ where
pub async fn authenticate_and_start(&mut self) -> Result<Arc<SharedKeys>, GatewayClientError>
where
C: DkgQueryClient,
C: DkgQueryClient + Send + Sync,
St: CredentialStorage,
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
{
if !self.connection.is_established() {
self.establish_connection().await?;
@@ -792,3 +761,40 @@ where
Ok(shared_key)
}
}
impl<C> GatewayClient<C, EphemeralCredentialStorage> {
// for initialisation we do not need credential storage. Though it's still a bit weird we have to set the generic...
pub fn new_init(
gateway_address: String,
gateway_identity: identity::PublicKey,
local_identity: Arc<identity::KeyPair>,
response_timeout_duration: Duration,
) -> Self {
use futures::channel::mpsc;
// note: this packet_router is completely invalid in normal circumstances, but "works"
// perfectly fine here, because it's not meant to be used
let (ack_tx, _) = mpsc::unbounded();
let (mix_tx, _) = mpsc::unbounded();
let shutdown = TaskClient::dummy();
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
GatewayClient::<C, EphemeralCredentialStorage> {
authenticated: false,
disabled_credentials_mode: true,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
local_identity,
shared_key: None,
connection: SocketState::NotConnected,
packet_router,
response_timeout_duration,
bandwidth_controller: None,
should_reconnect_on_failure: false,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
shutdown,
}
}
}
@@ -50,10 +50,15 @@ impl PacketRouter {
let ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
for received_packet in unwrapped_packets {
if received_packet.len() == PacketSize::AckPacket.plaintext_size() {
if received_packet.len() == PacketSize::AckPacket.plaintext_size()
|| received_packet.len() == PacketSize::OutfoxAckPacket.plaintext_size()
{
received_acks.push(received_packet);
} else if received_packet.len()
== PacketSize::RegularPacket.plaintext_size() - ack_overhead
|| received_packet.len()
== PacketSize::OutfoxRegularPacket.plaintext_size() - ack_overhead
|| received_packet.len() == PacketSize::OutfoxRegularPacket.size() - 6
{
trace!("routing regular packet");
received_messages.push(received_packet);
+15 -18
View File
@@ -4,10 +4,11 @@
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_sphinx::framing::codec::SphinxCodec;
use nym_sphinx::framing::packet::FramedSphinxPacket;
use nym_sphinx::params::PacketMode;
use nym_sphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket};
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx::framing::codec::NymCodec;
use nym_sphinx::framing::packet::FramedNymPacket;
use nym_sphinx::params::PacketType;
use nym_sphinx::NymPacket;
use std::collections::HashMap;
use std::io;
use std::net::SocketAddr;
@@ -50,8 +51,8 @@ pub trait SendWithoutResponse {
fn send_without_response(
&mut self,
address: NymNodeRoutingAddress,
packet: SphinxPacket,
packet_mode: PacketMode,
packet: NymPacket,
packet_type: PacketType,
) -> io::Result<()>;
}
@@ -61,12 +62,12 @@ pub struct Client {
}
struct ConnectionSender {
channel: mpsc::Sender<FramedSphinxPacket>,
channel: mpsc::Sender<FramedNymPacket>,
current_reconnection_attempt: Arc<AtomicU32>,
}
impl ConnectionSender {
fn new(channel: mpsc::Sender<FramedSphinxPacket>) -> Self {
fn new(channel: mpsc::Sender<FramedNymPacket>) -> Self {
ConnectionSender {
channel,
current_reconnection_attempt: Arc::new(AtomicU32::new(0)),
@@ -84,7 +85,7 @@ impl Client {
async fn manage_connection(
address: SocketAddr,
receiver: mpsc::Receiver<FramedSphinxPacket>,
receiver: mpsc::Receiver<FramedNymPacket>,
connection_timeout: Duration,
current_reconnection: &AtomicU32,
) {
@@ -96,7 +97,7 @@ impl Client {
debug!("Managed to establish connection to {}", address);
// if we managed to connect, reset the reconnection count (whatever it might have been)
current_reconnection.store(0, Ordering::Release);
Framed::new(stream, SphinxCodec)
Framed::new(stream, NymCodec)
}
Err(err) => {
debug!(
@@ -148,11 +149,7 @@ impl Client {
}
}
fn make_connection(
&mut self,
address: NymNodeRoutingAddress,
pending_packet: FramedSphinxPacket,
) {
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
// this CAN'T fail because we just created the channel which has a non-zero capacity
@@ -200,12 +197,12 @@ impl SendWithoutResponse for Client {
fn send_without_response(
&mut self,
address: NymNodeRoutingAddress,
packet: SphinxPacket,
packet_mode: PacketMode,
packet: NymPacket,
packet_type: PacketType,
) -> io::Result<()> {
trace!("Sending packet to {:?}", address);
let framed_packet =
FramedSphinxPacket::new(packet, packet_mode, self.config.use_legacy_version);
FramedNymPacket::new(packet, packet_type, self.config.use_legacy_version);
if let Some(sender) = self.conn_new.get_mut(&address) {
if let Err(err) = sender.channel.try_send(framed_packet) {
@@ -59,14 +59,14 @@ impl PacketForwarder {
trace!("Going to forward packet to {:?}", mix_packet.next_hop());
let next_hop = mix_packet.next_hop();
let packet_mode = mix_packet.packet_mode();
let sphinx_packet = mix_packet.into_sphinx_packet();
let packet_type = mix_packet.packet_type();
let packet = mix_packet.into_packet();
// we don't care about responses, we just want to fire packets
// as quickly as possible
if let Err(err) =
self.mixnet_client
.send_without_response(next_hop, sphinx_packet, packet_mode)
.send_without_response(next_hop, packet, packet_type)
{
debug!("failed to forward the packet - {err}")
}
@@ -40,7 +40,7 @@ nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
async-trait = { workspace = true, optional = true }
bip39 = { workspace = true, features = ["rand"], optional = true }
nym-config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true }
cosmrs = { workspace = true, features = ["rpc", "bip32", "cosmwasm"], optional = true }
# note that this has the same version as used by cosmrs
eyre = { version = "0.6", optional = true }
cw3 = { workspace = true, optional = true }
@@ -54,7 +54,7 @@ cosmwasm-std = { workspace = true, optional = true }
[dev-dependencies]
bip39 = { workspace = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32"] }
cosmrs = { workspace = true, features = ["rpc", "bip32"] }
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
ts-rs = "6.1.2"
@@ -11,7 +11,9 @@ use nym_api_requests::models::{
};
use nym_coconut_dkg_common::types::NodeIndex;
use nym_coconut_interface::VerificationKey;
pub use nym_mixnet_contract_common::{mixnode::MixNodeDetails, GatewayBond, IdentityKeyRef, MixId};
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, MixId,
};
use url::Url;
#[cfg(feature = "nyxd-client")]
@@ -31,7 +33,7 @@ use nym_mixnet_contract_common::{
families::{Family, FamilyHead},
mixnode::MixNodeBond,
pending_events::{PendingEpochEvent, PendingIntervalEvent},
Delegation, IdentityKey, RewardedSetNodeStatus, UnbondedMixnode,
Delegation, RewardedSetNodeStatus, UnbondedMixnode,
};
#[cfg(feature = "nyxd-client")]
use nym_network_defaults::NymNetworkDetails;
@@ -127,7 +127,7 @@ impl GasAdjustable for Gas {
mod sealed {
use cosmrs::tx::{self, Gas};
use cosmrs::Coin as CosmosCoin;
use cosmrs::{AccountId, Decimal as CosmosDecimal, Denom as CosmosDenom};
use cosmrs::{AccountId, Denom as CosmosDenom};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
fn cosmos_denom_inner_getter(val: &CosmosDenom) -> String {
@@ -144,29 +144,11 @@ mod sealed {
}
}
fn cosmos_decimal_inner_getter(val: &CosmosDecimal) -> u64 {
// haha, this code is so disgusting. I'll make a PR on cosmrs to slightly alleviate those issues...
// note: unwrap here is fine as the to_string is just returning a stringified u64 which, well, is a valid u64
val.to_string().parse().unwrap()
}
// at the time of writing it the current cosmrs' Decimal is extremely limited...
#[derive(Serialize, Deserialize)]
#[serde(remote = "CosmosDecimal")]
struct Decimal(#[serde(getter = "cosmos_decimal_inner_getter")] u64);
impl From<Decimal> for CosmosDecimal {
fn from(val: Decimal) -> Self {
val.0.into()
}
}
#[derive(Serialize, Deserialize, Clone)]
struct Coin {
#[serde(with = "Denom")]
denom: CosmosDenom,
#[serde(with = "Decimal")]
amount: CosmosDecimal,
amount: u128,
}
impl From<Coin> for CosmosCoin {
@@ -39,7 +39,7 @@ pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::{self, Gas};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
pub use cosmrs::{bip32, AccountId, Denom};
use cosmwasm_std::Addr;
pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
+2 -2
View File
@@ -14,7 +14,7 @@ clap = { version = "4.0", features = ["derive"] }
cw-utils = { workspace = true }
handlebars = "3.0.1"
humantime-serde = "1.0"
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
k256 = { workspace = true, features = ["ecdsa", "sha256"] }
log = { workspace = true }
rand = {version = "0.6", features = ["std"] }
serde = { version = "1.0", features = ["derive"] }
@@ -25,7 +25,7 @@ toml = "0.5.6"
url = "2.2"
tap = "1"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmrs = { workspace = true }
cosmwasm-std = { workspace = true }
nym-validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
@@ -56,6 +56,8 @@ pub async fn generate(args: Args) {
.expect("threshold can't be converted to Decimal"),
},
max_voting_period: Duration::Time(args.max_voting_period),
executor: None,
proposal_deposit: None,
coconut_bandwidth_contract_address: coconut_bandwidth_contract_address.to_string(),
coconut_dkg_contract_address: coconut_dkg_contract_address.to_string(),
};
+21 -5
View File
@@ -10,7 +10,7 @@ use std::any::type_name;
use std::fmt::Debug;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{fs, io};
@@ -31,17 +31,29 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
// default, most probable, implementations; can be easily overridden where required
fn default_config_directory(id: &str) -> PathBuf {
Self::default_root_directory().join(id).join(CONFIG_DIR)
Self::default_config_directory_with_root(Self::default_root_directory(), id)
}
fn default_config_directory_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf {
root.as_ref().join(id).join(CONFIG_DIR)
}
fn default_data_directory(id: &str) -> PathBuf {
Self::default_root_directory().join(id).join(DATA_DIR)
Self::default_data_directory_with_root(Self::default_root_directory(), id)
}
fn default_data_directory_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf {
root.as_ref().join(id).join(DATA_DIR)
}
fn default_config_file_path(id: &str) -> PathBuf {
Self::default_config_directory(id).join(Self::config_file_name())
}
fn default_config_file_path_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf {
Self::default_config_directory_with_root(root, id).join(Self::config_file_name())
}
// We provide a second set of functions that tries to not panic.
fn try_default_root_directory() -> Option<PathBuf>;
@@ -99,8 +111,12 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
fn load_from_file(id: &str) -> io::Result<Self> {
let file = Self::default_config_file_path(id);
log::trace!("Loading from file: {:#?}", file);
let config_contents = fs::read_to_string(file)?;
Self::load_from_filepath(file)
}
fn load_from_filepath<P: AsRef<Path>>(filepath: P) -> io::Result<Self> {
log::trace!("Loading from file: {:#?}", filepath.as_ref().to_owned());
let config_contents = fs::read_to_string(filepath)?;
toml::from_str(&config_contents)
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
@@ -14,7 +14,7 @@ pub struct InstantiateMsg {
pub mix_denom: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
DepositFunds { data: DepositData },
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use crate::msg::ExecuteMsg;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct SpendCredentialData {
funds: Coin,
blinded_serial_number: String,
@@ -43,7 +43,7 @@ pub enum SpendCredentialStatus {
Spent,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct SpendCredential {
funds: Coin,
blinded_serial_number: String,
@@ -74,7 +74,7 @@ impl SpendCredential {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedSpendCredentialResponse {
pub spend_credentials: Vec<SpendCredential>,
pub per_page: usize,
@@ -95,7 +95,7 @@ impl PagedSpendCredentialResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct SpendCredentialResponse {
pub spend_credential: Option<SpendCredential>,
}
@@ -15,7 +15,7 @@ pub type Nonce = u32;
// define this type explicitly for [hopefully] better usability
// (so you wouldn't need to worry about whether you should use bytes, bs58, etc.)
#[derive(Clone, Debug, PartialEq, JsonSchema)]
#[derive(Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct MessageSignature(Vec<u8>);
impl MessageSignature {
@@ -6,6 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-schema = { workspace = true }
cw4 = { workspace = true }
cw-controllers = { workspace = true }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
@@ -1,13 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_schema::{cw_serde, QueryResponses};
use cw4::Member;
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
pub struct InstantiateMsg {
/// The admin is the only account that can update the group state.
/// Omit it to make the group immutable.
@@ -15,8 +9,7 @@ pub struct InstantiateMsg {
pub members: Vec<Member>,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
pub enum ExecuteMsg {
/// Change the admin
UpdateAdmin { admin: Option<String> },
@@ -32,23 +25,24 @@ pub enum ExecuteMsg {
RemoveHook { addr: String },
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
/// Return AdminResponse
#[returns(cw_controllers::AdminResponse)]
Admin {},
/// Return TotalWeightResponse
TotalWeight {},
/// Returns MembersListResponse
#[returns(cw4::TotalWeightResponse)]
TotalWeight { at_height: Option<u64> },
#[returns(cw4::MemberListResponse)]
ListMembers {
start_after: Option<String>,
limit: Option<u32>,
},
/// Returns MemberResponse
#[returns(cw4::MemberResponse)]
Member {
addr: String,
at_height: Option<u64>,
},
/// Shows all registered hooks. Returns HooksResponse.
/// Shows all registered hooks.
#[returns(cw_controllers::HooksResponse)]
Hooks {},
}
@@ -37,7 +37,7 @@ pub fn generate_owner_storage_subkey(
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct Delegation {
/// Address of the owner of this delegation.
pub owner: Addr,
@@ -114,7 +114,7 @@ impl Delegation {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedMixNodeDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<OwnerProxySubKey>,
@@ -129,7 +129,7 @@ impl PagedMixNodeDelegationsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedDelegatorDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(MixId, OwnerProxySubKey)>,
@@ -147,7 +147,7 @@ impl PagedDelegatorDelegationsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeDelegationResponse {
pub delegation: Option<Delegation>,
pub mixnode_still_bonded: bool,
@@ -162,7 +162,7 @@ impl MixNodeDelegationResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<StorageKey>,
@@ -23,7 +23,7 @@ pub struct Gateway {
pub version: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct GatewayBond {
pub pledge_amount: Coin,
pub owner: Addr,
@@ -132,7 +132,7 @@ impl GatewayConfigUpdate {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedGatewayResponse {
pub nodes: Vec<GatewayBond>,
pub per_page: usize,
@@ -153,13 +153,13 @@ impl PagedGatewayResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct GatewayOwnershipResponse {
pub address: Addr,
pub gateway: Option<GatewayBond>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct GatewayBondResponse {
pub identity: IdentityKey,
pub gateway: Option<GatewayBond>,
@@ -489,7 +489,7 @@ impl CurrentIntervalResponse {
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct PendingEpochEventsResponse {
pub seconds_until_executable: i64,
pub events: Vec<PendingEpochEvent>,
@@ -510,7 +510,7 @@ impl PendingEpochEventsResponse {
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct PendingIntervalEventsResponse {
pub seconds_until_executable: i64,
pub events: Vec<PendingIntervalEvent>,
@@ -33,7 +33,7 @@ impl RewardedSetNodeStatus {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeDetails {
pub bond_information: MixNodeBond,
pub rewarding_details: MixNodeRewarding,
@@ -86,7 +86,7 @@ impl MixNodeDetails {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeRewarding {
/// Information provided by the operator that influence the cost function.
pub cost_params: MixNodeCostParams,
@@ -465,7 +465,7 @@ impl MixNodeRewarding {
}
// operator information + data assigned by the contract(s)
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeBond {
/// Unique id assigned to the bonded mixnode.
pub mix_id: MixId,
@@ -559,7 +559,7 @@ pub struct MixNode {
pub version: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeCostParams {
pub profit_margin_percent: Percent,
@@ -686,7 +686,7 @@ impl MixNodeConfigUpdate {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedMixnodeBondsResponse {
pub nodes: Vec<MixNodeBond>,
pub per_page: usize,
@@ -703,7 +703,7 @@ impl PagedMixnodeBondsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedMixnodesDetailsResponse {
pub nodes: Vec<MixNodeDetails>,
pub per_page: usize,
@@ -745,19 +745,19 @@ impl PagedUnbondedMixnodesResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixOwnershipResponse {
pub address: Addr,
pub mixnode_details: Option<MixNodeDetails>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixnodeDetailsResponse {
pub mix_id: MixId,
pub mixnode_details: Option<MixNodeDetails>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixnodeRewardingDetailsResponse {
pub mix_id: MixId,
pub rewarding_details: Option<MixNodeRewarding>,
@@ -7,19 +7,19 @@ use crate::{BlockHeight, EpochEventId, IntervalEventId, MixId};
use cosmwasm_std::{Addr, Coin};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PendingEpochEvent {
pub id: EpochEventId,
pub event: PendingEpochEventData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PendingEpochEventData {
pub created_at: BlockHeight,
pub kind: PendingEpochEventKind,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum PendingEpochEventKind {
// can't just pass the `Delegation` struct here as it's impossible to determine
// `cumulative_reward_ratio` ahead of time
@@ -68,19 +68,19 @@ impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PendingIntervalEvent {
pub id: IntervalEventId,
pub event: PendingIntervalEventData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PendingIntervalEventData {
pub created_at: BlockHeight,
pub kind: PendingIntervalEventKind,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum PendingIntervalEventKind {
ChangeMixCostParams {
mix_id: MixId,
@@ -35,7 +35,7 @@ pub struct RewardDistribution {
pub delegates: Decimal,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct PendingRewardResponse {
pub amount_staked: Option<Coin>,
pub amount_earned: Option<Coin>,
@@ -46,7 +46,7 @@ pub struct PendingRewardResponse {
pub mixnode_still_fully_bonded: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct EstimatedCurrentEpochRewardResponse {
pub original_stake: Option<Coin>,
@@ -23,7 +23,7 @@ pub type EpochEventId = u32;
pub type IntervalEventId = u32;
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq, Eq)]
pub struct LayerAssignment {
mix_id: MixId,
layer: Layer,
@@ -119,7 +119,7 @@ impl Index<Layer> for LayerDistribution {
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ContractState {
pub owner: Addr, // only the owner account can update state
pub rewarding_validator_address: Addr,
@@ -131,7 +131,7 @@ pub struct ContractState {
pub params: ContractStateParams,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ContractStateParams {
/// Minimum amount a delegator must stake in orders for his delegation to get accepted.
pub minimum_mixnode_delegation: Option<Coin>,
@@ -8,8 +8,9 @@ edition = "2021"
[dependencies]
cw-utils = { workspace = true }
cw3 = { workspace = true }
cw3-fixed-multisig = { workspace = true, features = ["library"] }
cw4 = { workspace= true }
cw-storage-plus = { workspace = true }
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::StdError;
use cw_utils::ThresholdError;
use cw3::DepositError;
use cw_utils::{PaymentError, ThresholdError};
use thiserror::Error;
@@ -17,9 +18,6 @@ pub enum ContractError {
#[error("Group contract invalid address '{addr}'")]
InvalidGroup { addr: String },
#[error("Coconut bandwidth contract address not found")]
InvalidCoconutBandwidth {},
#[error("Unauthorized")]
Unauthorized {},
@@ -43,4 +41,10 @@ pub enum ContractError {
#[error("Cannot close completed or passed proposals")]
WrongCloseStatus {},
#[error("{0}")]
Payment(#[from] PaymentError),
#[error("{0}")]
Deposit(#[from] DepositError),
}
@@ -1,2 +1,3 @@
pub mod error;
pub mod msg;
pub mod state;
@@ -1,15 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{CosmosMsg, Empty};
use cw3::Vote;
use cw3::{UncheckedDepositInfo, Vote};
use cw4::MemberChangedHookMsg;
use cw_utils::{Duration, Expiration, Threshold};
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
use crate::state::Executor;
#[cw_serde]
pub struct InstantiateMsg {
// this is the group contract that contains the member list
pub group_addr: String,
@@ -17,11 +17,15 @@ pub struct InstantiateMsg {
pub coconut_dkg_contract_address: String,
pub threshold: Threshold,
pub max_voting_period: Duration,
// who is able to execute passed proposals
// None means that anyone can execute
pub executor: Option<Executor>,
/// The cost of creating a proposal (if any).
pub proposal_deposit: Option<UncheckedDepositInfo>,
}
// TODO: add some T variants? Maybe good enough as fixed Empty for now
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
pub enum ExecuteMsg {
Propose {
title: String,
@@ -45,41 +49,44 @@ pub enum ExecuteMsg {
}
// We can also add this as a cw3 extension
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
/// Return ThresholdResponse
#[returns(cw_utils::ThresholdResponse)]
Threshold {},
/// Returns ProposalResponse
#[returns(cw3::ProposalResponse)]
Proposal { proposal_id: u64 },
/// Returns ProposalListResponse
#[returns(cw3::ProposalListResponse)]
ListProposals {
start_after: Option<u64>,
limit: Option<u32>,
},
/// Returns ProposalListResponse
#[returns(cw3::ProposalListResponse)]
ReverseProposals {
start_before: Option<u64>,
limit: Option<u32>,
},
/// Returns VoteResponse
#[returns(cw3::VoteResponse)]
Vote { proposal_id: u64, voter: String },
/// Returns VoteListResponse
#[returns(cw3::VoteListResponse)]
ListVotes {
proposal_id: u64,
start_after: Option<String>,
limit: Option<u32>,
},
/// Returns VoterInfo
#[returns(cw3::VoterResponse)]
Voter { address: String },
/// Returns VoterListResponse
#[returns(cw3::VoterListResponse)]
ListVoters {
start_after: Option<String>,
limit: Option<u32>,
},
/// Gets the current configuration.
#[returns(crate::state::Config)]
Config {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
pub struct MigrateMsg {
pub coconut_bandwidth_address: String,
pub coconut_dkg_address: String,
@@ -0,0 +1,59 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, QuerierWrapper};
use cw3::DepositInfo;
use cw4::Cw4Contract;
use cw_storage_plus::Item;
use cw_utils::{Duration, Threshold};
use crate::error::ContractError;
/// Defines who is able to execute proposals once passed
#[cw_serde]
pub enum Executor {
/// Any member of the voting group, even with 0 points
Member,
/// Only the given address
Only(Addr),
}
#[cw_serde]
pub struct Config {
pub threshold: Threshold,
pub max_voting_period: Duration,
// Total weight and voters are queried from this contract
pub group_addr: Cw4Contract,
pub coconut_bandwidth_addr: Addr,
pub coconut_dkg_addr: Addr,
// who is able to execute passed proposals
// None means that anyone can execute
pub executor: Option<Executor>,
/// The price, if any, of creating a new proposal.
pub proposal_deposit: Option<DepositInfo>,
}
impl Config {
// Executor can be set in 3 ways:
// - Member: any member of the voting group is authorized
// - Only: only passed address is authorized
// - None: Everyone are authorized
pub fn authorize(&self, querier: &QuerierWrapper, sender: &Addr) -> Result<(), ContractError> {
if let Some(executor) = &self.executor {
match executor {
Executor::Member => {
self.group_addr
.is_member(querier, sender, None)?
.ok_or(ContractError::Unauthorized {})?;
}
Executor::Only(addr) => {
if addr != sender {
return Err(ContractError::Unauthorized {});
}
}
}
}
Ok(())
}
}
// unique items
pub const CONFIG: Item<Config> = Item::new("config");
@@ -28,7 +28,7 @@ pub enum Period {
After,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct PledgeData {
pub amount: Coin,
pub block_time: Timestamp,
@@ -49,7 +49,7 @@ impl PledgeData {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub enum PledgeCap {
Percent(Percent),
Absolute(Uint128), // This has to be in unym
@@ -77,7 +77,7 @@ impl Default for PledgeCap {
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct OriginalVestingResponse {
pub amount: Coin,
pub number_of_periods: usize,
@@ -55,7 +55,7 @@ impl VestingSpecification {
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
// Families
+1 -1
View File
@@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmrs = { workspace = true }
thiserror = "1.0"
# I guess temporarily until we get serde support in coconut up and running
+2 -2
View File
@@ -6,8 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bip32 = "0.3.0"
k256 = "0.10.4"
bip32 = "0.4.0"
k256 = { workspace = true }
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
thiserror = "1"
@@ -3,12 +3,15 @@
use nym_sphinx_acknowledgements::surb_ack::SurbAckRecoveryError;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddressError;
use nym_sphinx_types::Error as SphinxError;
use nym_sphinx_types::{NymPacketError, SphinxError};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MixProcessingError {
#[error("failed to process received packet: {0}")]
NymPacketProcessingError(#[from] NymPacketError),
#[error("failed to process received sphinx packet: {0}")]
SphinxProcessingError(#[from] SphinxError),
#[error("the forward hop address was malformed: {0}")]
@@ -7,11 +7,11 @@ use log::*;
use nym_sphinx_acknowledgements::surb_ack::SurbAck;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_framing::packet::FramedSphinxPacket;
use nym_sphinx_params::{PacketMode, PacketSize};
use nym_sphinx_framing::packet::FramedNymPacket;
use nym_sphinx_params::{PacketSize, PacketType};
use nym_sphinx_types::{
Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, Payload, PrivateKey,
ProcessedPacket, SphinxPacket,
Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, NymPacket, NymProcessedPacket,
PrivateKey, ProcessedPacket,
};
use std::convert::TryFrom;
use std::sync::Arc;
@@ -53,14 +53,14 @@ impl SphinxPacketProcessor {
feature = "cpucycles",
instrument(skip(self, packet), fields(cpucycles))
)]
fn perform_initial_sphinx_packet_processing(
fn perform_initial_packet_processing(
&self,
packet: SphinxPacket,
) -> Result<ProcessedPacket, MixProcessingError> {
packet: NymPacket,
) -> Result<NymProcessedPacket, MixProcessingError> {
measure!({
packet.process(&self.sphinx_key).map_err(|err| {
debug!("Failed to unwrap Sphinx packet: {err}");
MixProcessingError::SphinxProcessingError(err)
debug!("Failed to unwrap NymPacket packet: {err}");
MixProcessingError::NymPacketProcessingError(err)
})
})
}
@@ -72,17 +72,12 @@ impl SphinxPacketProcessor {
)]
fn perform_initial_unwrapping(
&self,
received: FramedSphinxPacket,
) -> Result<ProcessedPacket, MixProcessingError> {
received: FramedNymPacket,
) -> Result<NymProcessedPacket, MixProcessingError> {
measure!({
let packet_mode = received.packet_mode();
let sphinx_packet = received.into_inner();
let packet = received.into_inner();
if packet_mode.is_old_vpn() {
return Err(MixProcessingError::ReceivedOldTypeVpnPacket);
}
self.perform_initial_sphinx_packet_processing(sphinx_packet)
self.perform_initial_packet_processing(packet)
})
}
@@ -90,14 +85,14 @@ impl SphinxPacketProcessor {
/// and packs all the data in a way that can be easily sent to the next hop.
fn process_forward_hop(
&self,
packet: SphinxPacket,
packet: NymPacket,
forward_address: NodeAddressBytes,
delay: SphinxDelay,
packet_mode: PacketMode,
packet_type: PacketType,
) -> Result<MixProcessingResult, MixProcessingError> {
let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?;
let mix_packet = MixPacket::new(next_hop_address, packet, packet_mode);
let mix_packet = MixPacket::new(next_hop_address, packet, packet_type);
Ok(MixProcessingResult::ForwardHop(mix_packet, Some(delay)))
}
@@ -106,14 +101,17 @@ impl SphinxPacketProcessor {
fn split_hop_data_into_ack_and_message(
&self,
mut extracted_data: Vec<u8>,
packet_type: PacketType,
) -> Result<(Vec<u8>, Vec<u8>), MixProcessingError> {
let ack_len = SurbAck::len(Some(packet_type));
// in theory it's impossible for this to fail since it managed to go into correct `match`
// branch at the caller
if extracted_data.len() < SurbAck::len() {
if extracted_data.len() < ack_len {
return Err(MixProcessingError::NoSurbAckInFinalHop);
}
let message = extracted_data.split_off(SurbAck::len());
let message = extracted_data.split_off(ack_len);
let ack_data = extracted_data;
Ok((ack_data, message))
}
@@ -124,21 +122,30 @@ impl SphinxPacketProcessor {
&self,
data: Vec<u8>,
packet_size: PacketSize,
packet_mode: PacketMode,
packet_type: PacketType,
) -> Result<(Option<MixPacket>, Vec<u8>), MixProcessingError> {
match packet_size {
PacketSize::AckPacket => {
PacketSize::AckPacket | PacketSize::OutfoxAckPacket => {
trace!("received an ack packet!");
Ok((None, data))
}
PacketSize::RegularPacket
| PacketSize::ExtendedPacket8
| PacketSize::ExtendedPacket16
| PacketSize::ExtendedPacket32 => {
| PacketSize::ExtendedPacket32
| PacketSize::OutfoxRegularPacket => {
trace!("received a normal packet!");
let (ack_data, message) = self.split_hop_data_into_ack_and_message(data)?;
let (ack_first_hop, ack_packet) = SurbAck::try_recover_first_hop_packet(&ack_data)?;
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_mode);
let (ack_data, message) =
self.split_hop_data_into_ack_and_message(data, packet_type)?;
let (ack_first_hop, ack_packet) =
match SurbAck::try_recover_first_hop_packet(&ack_data, packet_type) {
Ok((first_hop, packet)) => (first_hop, packet),
Err(err) => {
debug!("Failed to recover first hop from ack data: {err}");
return Err(err.into());
}
};
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type);
Ok((Some(forward_ack), message))
}
}
@@ -150,14 +157,12 @@ impl SphinxPacketProcessor {
fn process_final_hop(
&self,
destination: DestinationAddressBytes,
payload: Payload,
payload: Vec<u8>,
packet_size: PacketSize,
packet_mode: PacketMode,
packet_type: PacketType,
) -> Result<MixProcessingResult, MixProcessingError> {
let packet_message = payload.recover_plaintext()?;
let (forward_ack, message) =
self.split_into_ack_and_message(packet_message, packet_size, packet_mode)?;
self.split_into_ack_and_message(payload, packet_size, packet_type)?;
Ok(MixProcessingResult::FinalHop(ProcessedFinalHop {
destination,
@@ -170,18 +175,48 @@ impl SphinxPacketProcessor {
/// or a final hop.
fn perform_final_processing(
&self,
packet: ProcessedPacket,
packet: NymProcessedPacket,
packet_size: PacketSize,
packet_mode: PacketMode,
packet_type: PacketType,
) -> Result<MixProcessingResult, MixProcessingError> {
match packet {
ProcessedPacket::ForwardHop(packet, address, delay) => {
self.process_forward_hop(*packet, address, delay, packet_mode)
NymProcessedPacket::Sphinx(packet) => {
match packet {
ProcessedPacket::ForwardHop(packet, address, delay) => self
.process_forward_hop(
NymPacket::Sphinx(*packet),
address,
delay,
packet_type,
),
// right now there's no use for the surb_id included in the header - probably it should get removed from the
// sphinx all together?
ProcessedPacket::FinalHop(destination, _, payload) => self.process_final_hop(
destination,
payload.recover_plaintext()?,
packet_size,
packet_type,
),
}
}
// right now there's no use for the surb_id included in the header - probably it should get removed from the
// sphinx all together?
ProcessedPacket::FinalHop(destination, _, payload) => {
self.process_final_hop(destination, payload, packet_size, packet_mode)
NymProcessedPacket::Outfox(packet) => {
let next_address = *packet.next_address();
let packet = packet.into_packet();
if packet.is_final_hop() {
self.process_final_hop(
DestinationAddressBytes::from_bytes(next_address),
packet.recover_plaintext().to_vec(),
packet_size,
packet_type,
)
} else {
let mix_packet = MixPacket::new(
NymNodeRoutingAddress::try_from_bytes(&next_address)?,
NymPacket::Outfox(packet),
PacketType::Outfox,
);
Ok(MixProcessingResult::ForwardHop(mix_packet, None))
}
}
}
}
@@ -192,19 +227,19 @@ impl SphinxPacketProcessor {
)]
pub fn process_received(
&self,
received: FramedSphinxPacket,
received: FramedNymPacket,
) -> Result<MixProcessingResult, MixProcessingError> {
// explicit packet size will help to correctly parse final hop
measure!({
let packet_size = received.packet_size();
let packet_mode = received.packet_mode();
let packet_type = received.packet_type();
// unwrap the sphinx packet and if possible and appropriate, cache keys
let processed_packet = self.perform_initial_unwrapping(received)?;
// for forward packets, extract next hop and set delay (but do NOT delay here)
// for final packets, extract SURBAck
self.perform_final_processing(processed_packet, packet_size, packet_mode)
self.perform_final_processing(processed_packet, packet_size, packet_type)
})
}
}
@@ -226,31 +261,71 @@ mod tests {
let short_data = vec![42u8];
assert!(processor
.split_hop_data_into_ack_and_message(short_data)
.split_hop_data_into_ack_and_message(short_data, PacketType::Mix)
.is_err());
let sufficient_data = vec![42u8; SurbAck::len()];
let sufficient_data = vec![42u8; SurbAck::len(Some(PacketType::Mix))];
let (ack, data) = processor
.split_hop_data_into_ack_and_message(sufficient_data.clone())
.split_hop_data_into_ack_and_message(sufficient_data.clone(), PacketType::Mix)
.unwrap();
assert_eq!(sufficient_data, ack);
assert!(data.is_empty());
let long_data = vec![42u8; SurbAck::len() * 5];
let long_data = vec![42u8; SurbAck::len(Some(PacketType::Mix)) * 5];
let (ack, data) = processor
.split_hop_data_into_ack_and_message(long_data)
.split_hop_data_into_ack_and_message(long_data, PacketType::Mix)
.unwrap();
assert_eq!(ack.len(), SurbAck::len());
assert_eq!(data.len(), SurbAck::len() * 4)
assert_eq!(ack.len(), SurbAck::len(Some(PacketType::Mix)));
assert_eq!(data.len(), SurbAck::len(Some(PacketType::Mix)) * 4)
}
#[tokio::test]
async fn splitting_hop_data_works_for_sufficiently_long_payload_outfox() {
let processor = fixture();
let short_data = vec![42u8];
assert!(processor
.split_hop_data_into_ack_and_message(short_data, PacketType::Outfox)
.is_err());
let sufficient_data = vec![42u8; SurbAck::len(Some(PacketType::Outfox))];
let (ack, data) = processor
.split_hop_data_into_ack_and_message(sufficient_data.clone(), PacketType::Outfox)
.unwrap();
assert_eq!(sufficient_data, ack);
assert!(data.is_empty());
let long_data = vec![42u8; SurbAck::len(Some(PacketType::Outfox)) * 5];
let (ack, data) = processor
.split_hop_data_into_ack_and_message(long_data, PacketType::Outfox)
.unwrap();
assert_eq!(ack.len(), SurbAck::len(Some(PacketType::Outfox)));
assert_eq!(data.len(), SurbAck::len(Some(PacketType::Outfox)) * 4)
}
#[tokio::test]
async fn splitting_into_ack_and_message_returns_whole_data_for_ack() {
let processor = fixture();
let data = vec![42u8; SurbAck::len() + 10];
let data = vec![42u8; SurbAck::len(Some(PacketType::Mix)) + 10];
let (ack, message) = processor
.split_into_ack_and_message(data.clone(), PacketSize::AckPacket, Default::default())
.split_into_ack_and_message(data.clone(), PacketSize::AckPacket, PacketType::Mix)
.unwrap();
assert!(ack.is_none());
assert_eq!(data, message)
}
#[tokio::test]
async fn splitting_into_ack_and_message_returns_whole_data_for_ack_outfox() {
let processor = fixture();
let data = vec![42u8; SurbAck::len(Some(PacketType::Outfox)) + 10];
let (ack, message) = processor
.split_into_ack_and_message(
data.clone(),
PacketSize::OutfoxAckPacket,
PacketType::Outfox,
)
.unwrap();
assert!(ack.is_none());
assert_eq!(data, message)
+1
View File
@@ -17,6 +17,7 @@ tokio = { workspace = true, features = ["macros"]}
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
nym-task = { path = "../task" }
nym-topology = { path = "../topology" }
nym-sphinx-params = { path = "../nymsphinx/params" }
# TODO: do we need the whole nymsphinx?
nym-sphinx = { path = "../nymsphinx" }
+9 -1
View File
@@ -10,6 +10,7 @@ use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::message::NymMessage;
use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::preparer::{FragmentPreparer, PreparedFragment};
use nym_sphinx_params::PacketType;
use nym_topology::{gateway, mix, NymTopology};
use rand::{CryptoRng, Rng};
use serde::Serialize;
@@ -243,7 +244,14 @@ where
// TODO: can we avoid this arc clone?
let ack_key = Arc::clone(&self.ack_key);
Ok(self.prepare_chunk_for_sending(fragment, topology, &ack_key, &address, &address)?)
Ok(self.prepare_chunk_for_sending(
fragment,
topology,
&ack_key,
&address,
&address,
PacketType::Mix,
)?)
}
pub fn create_test_packet<T>(
-3
View File
@@ -28,9 +28,6 @@ nym-sphinx-types = { path = "types" }
nym-crypto = { path = "../crypto", version = "0.3.0" }
nym-topology = { path = "../topology" }
# outfox
nym-outfox = { path = "../../nym-outfox" }
[dev-dependencies]
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
nym-crypto = { path = "../crypto", version = "0.3.0", features = ["asymmetric"] }
@@ -3,9 +3,7 @@
use crate::AckKey;
use nym_crypto::symmetric::stream_cipher::{self, encrypt, iv_from_slice, random_iv, IvSizeUser};
use nym_sphinx_params::{
packet_sizes::PacketSize, AckEncryptionAlgorithm, SerializedFragmentIdentifier, FRAG_ID_LEN,
};
use nym_sphinx_params::{AckEncryptionAlgorithm, SerializedFragmentIdentifier, FRAG_ID_LEN};
use rand::{CryptoRng, RngCore};
// TODO: should those functions even exist in this file?
@@ -26,12 +24,6 @@ pub fn recover_identifier(
key: &AckKey,
iv_id_ciphertext: &[u8],
) -> Option<SerializedFragmentIdentifier> {
// The content of an 'ACK' packet consists of AckEncryptionAlgorithm::IV followed by
// serialized FragmentIdentifier
if iv_id_ciphertext.len() != PacketSize::AckPacket.plaintext_size() {
return None;
}
let iv_size = AckEncryptionAlgorithm::iv_size();
let iv = iv_from_slice::<AckEncryptionAlgorithm>(&iv_id_ciphertext[..iv_size]);
@@ -8,21 +8,18 @@ use nym_sphinx_addressing::nodes::{
NymNodeRoutingAddress, NymNodeRoutingAddressError, MAX_NODE_ADDRESS_UNPADDED_LEN,
};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::DEFAULT_NUM_MIX_HOPS;
use nym_sphinx_types::builder::SphinxPacketBuilder;
use nym_sphinx_types::Error as SphinxError;
use nym_sphinx_types::{
delays::{self, Delay},
SphinxPacket,
};
use nym_sphinx_params::{PacketType, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::delays::{self, Delay};
use nym_sphinx_types::{NymPacket, NymPacketError, MIN_PACKET_SIZE};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
use std::time;
use thiserror::Error;
#[derive(Debug)]
pub struct SurbAck {
surb_ack_packet: SphinxPacket,
surb_ack_packet: NymPacket,
first_hop_address: NymNodeRoutingAddress,
expected_total_delay: Delay,
}
@@ -35,8 +32,8 @@ pub enum SurbAckRecoveryError {
#[error("could not extract first hop address information - {0}")]
InvalidAddress(#[from] NymNodeRoutingAddressError),
#[error("the contained sphinx packet was not correctly formed - {0}")]
InvalidSphinxPacket(#[from] SphinxError),
#[error("packet: {0}")]
NymPacket(#[from] NymPacketError),
}
impl SurbAck {
@@ -47,6 +44,7 @@ impl SurbAck {
marshaled_fragment_id: [u8; 5],
average_delay: time::Duration,
topology: &NymTopology,
packet_type: PacketType,
) -> Result<Self, NymTopologyError>
where
R: RngCore + CryptoRng,
@@ -57,11 +55,34 @@ impl SurbAck {
let destination = recipient.as_sphinx_destination();
let surb_ack_payload = prepare_identifier(rng, ack_key, marshaled_fragment_id);
let packet_size = match packet_type {
PacketType::Outfox => surb_ack_payload.len().max(MIN_PACKET_SIZE),
PacketType::Mix => PacketSize::AckPacket.payload_size(),
PacketType::Vpn => PacketSize::AckPacket.payload_size(),
};
let surb_ack_packet = SphinxPacketBuilder::new()
.with_payload_size(PacketSize::AckPacket.payload_size())
.build_packet(surb_ack_payload, &route, &destination, &delays)
.unwrap();
let surb_ack_packet = match packet_type {
PacketType::Outfox => NymPacket::outfox_build(
surb_ack_payload,
route.as_slice(),
&destination,
Some(packet_size),
)?,
PacketType::Mix => NymPacket::sphinx_build(
packet_size,
surb_ack_payload,
&route,
&destination,
&delays,
)?,
PacketType::Vpn => NymPacket::sphinx_build(
packet_size,
surb_ack_payload,
&route,
&destination,
&delays,
)?,
};
// in our case, the last hop is a gateway that does NOT do any delays
let expected_total_delay = delays.iter().take(delays.len() - 1).sum();
@@ -75,45 +96,50 @@ impl SurbAck {
})
}
pub fn len() -> usize {
pub fn len(packet_type: Option<PacketType>) -> usize {
// TODO: this will be variable once/if we decide to introduce optimization described
// in common/nymsphinx/chunking/src/lib.rs:available_plaintext_size()
PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN
let packet_type = packet_type.unwrap_or(PacketType::Mix);
match packet_type {
PacketType::Outfox => {
PacketSize::OutfoxAckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN
}
PacketType::Mix => PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN,
PacketType::Vpn => PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN,
}
}
pub fn expected_total_delay(&self) -> Delay {
self.expected_total_delay
}
pub fn prepare_for_sending(self) -> (Delay, Vec<u8>) {
pub fn prepare_for_sending(self) -> Result<(Delay, Vec<u8>), SurbAckRecoveryError> {
// SURB_FIRST_HOP || SURB_ACK
let surb_bytes: Vec<_> = self
.first_hop_address
.as_zero_padded_bytes(MAX_NODE_ADDRESS_UNPADDED_LEN)
.into_iter()
.chain(self.surb_ack_packet.to_bytes().into_iter())
.chain(self.surb_ack_packet.to_bytes()?.into_iter())
.collect();
(self.expected_total_delay, surb_bytes)
Ok((self.expected_total_delay, surb_bytes))
}
// partial reciprocal of `prepare_for_sending` performed by the gateway
pub fn try_recover_first_hop_packet(
b: &[u8],
) -> Result<(NymNodeRoutingAddress, SphinxPacket), SurbAckRecoveryError> {
if b.len() != Self::len() {
Err(SurbAckRecoveryError::InvalidPacketSize {
received: b.len(),
expected: Self::len(),
})
} else {
let address = NymNodeRoutingAddress::try_from_bytes(b)?;
packet_type: PacketType,
) -> Result<(NymNodeRoutingAddress, NymPacket), SurbAckRecoveryError> {
let address = NymNodeRoutingAddress::try_from_bytes(b)?;
// TODO: this will be variable once/if we decide to introduce optimization described
// in common/nymsphinx/chunking/src/lib.rs:available_plaintext_size()
let address_offset = MAX_NODE_ADDRESS_UNPADDED_LEN;
let packet = SphinxPacket::from_bytes(&b[address_offset..])?;
// TODO: this will be variable once/if we decide to introduce optimization described
// in common/nymsphinx/chunking/src/lib.rs:available_plaintext_size()
let address_offset = MAX_NODE_ADDRESS_UNPADDED_LEN;
let packet = match packet_type {
PacketType::Outfox => NymPacket::outfox_from_bytes(&b[address_offset..])?,
PacketType::Mix => NymPacket::sphinx_from_bytes(&b[address_offset..])?,
PacketType::Vpn => NymPacket::sphinx_from_bytes(&b[address_offset..])?,
};
Ok((address, packet))
}
Ok((address, packet))
}
}
@@ -6,8 +6,8 @@ use nym_crypto::{generic_array::typenum::Unsigned, Digest};
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::{delays, Error as SphinxError, SURBMaterial, SphinxPacket, SURB};
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::{delays, NymPacket, SURBMaterial, SphinxError, SURB};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore};
use serde::de::{Error as SerdeError, Visitor};
@@ -173,7 +173,8 @@ impl ReplySurb {
self,
message: M,
packet_size: PacketSize,
) -> Result<(SphinxPacket, NymNodeRoutingAddress), ReplySurbError> {
_packet_type: PacketType,
) -> Result<(NymPacket, NymNodeRoutingAddress), ReplySurbError> {
let message_bytes = message.as_ref();
if message_bytes.len() != packet_size.plaintext_size() {
return Err(ReplySurbError::UnpaddedMessageError);
@@ -187,6 +188,6 @@ impl ReplySurb {
let first_hop_address = NymNodeRoutingAddress::try_from(first_hop).unwrap();
Ok((packet, first_hop_address))
Ok((NymPacket::Sphinx(packet), first_hop_address))
}
}
+29 -14
View File
@@ -3,7 +3,7 @@
use nym_crypto::shared_key::new_ephemeral_shared_key;
use nym_crypto::symmetric::stream_cipher;
use nym_sphinx_acknowledgements::surb_ack::SurbAck;
use nym_sphinx_acknowledgements::surb_ack::{SurbAck, SurbAckRecoveryError};
use nym_sphinx_acknowledgements::AckKey;
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
@@ -11,10 +11,9 @@ use nym_sphinx_chunking::fragment::COVER_FRAG_ID;
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketMode, DEFAULT_NUM_MIX_HOPS,
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketType, DEFAULT_NUM_MIX_HOPS,
};
use nym_sphinx_types::builder::SphinxPacketBuilder;
use nym_sphinx_types::{delays, Error as SphinxError};
use nym_sphinx_types::{delays, NymPacket};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
@@ -28,8 +27,11 @@ pub enum CoverMessageError {
#[error("Could not construct cover message due to invalid topology - {0}")]
InvalidTopologyError(#[from] NymTopologyError),
#[error("Could not construct a valid sphinx packet - {0}")]
SphinxError(#[from] SphinxError),
#[error("SurbAck: {0}")]
SurbAck(#[from] SurbAckRecoveryError),
#[error("NymPacket: {0}")]
NymPacket(#[from] nym_sphinx_types::NymPacketError),
}
pub fn generate_loop_cover_surb_ack<R>(
@@ -38,6 +40,7 @@ pub fn generate_loop_cover_surb_ack<R>(
ack_key: &AckKey,
full_address: &Recipient,
average_ack_delay: time::Duration,
packet_type: PacketType,
) -> Result<SurbAck, CoverMessageError>
where
R: RngCore + CryptoRng,
@@ -49,9 +52,11 @@ where
COVER_FRAG_ID.to_bytes(),
average_ack_delay,
topology,
packet_type,
)?)
}
#[allow(clippy::too_many_arguments)]
pub fn generate_loop_cover_packet<R>(
rng: &mut R,
topology: &NymTopology,
@@ -60,14 +65,21 @@ pub fn generate_loop_cover_packet<R>(
average_ack_delay: time::Duration,
average_packet_delay: time::Duration,
packet_size: PacketSize,
packet_type: PacketType,
) -> Result<MixPacket, CoverMessageError>
where
R: RngCore + CryptoRng,
{
// we don't care about total ack delay - we will not be retransmitting it anyway
let (_, ack_bytes) =
generate_loop_cover_surb_ack(rng, topology, ack_key, full_address, average_ack_delay)?
.prepare_for_sending();
let (_, ack_bytes) = generate_loop_cover_surb_ack(
rng,
topology,
ack_key,
full_address,
average_ack_delay,
packet_type,
)?
.prepare_for_sending()?;
// cover message can't be distinguishable from a normal traffic so we have to go through
// all the effort of key generation, encryption, etc. Note here we are generating shared key
@@ -111,15 +123,18 @@ where
let destination = full_address.as_sphinx_destination();
// once merged, that's an easy rng injection point for sphinx packets : )
let packet = SphinxPacketBuilder::new()
.with_payload_size(packet_size.payload_size())
.build_packet(packet_payload, &route, &destination, &delays)
.unwrap();
let packet = NymPacket::sphinx_build(
packet_size.payload_size(),
packet_payload,
&route,
&destination,
&delays,
)?;
let first_hop_address =
NymNodeRoutingAddress::try_from(route.first().unwrap().address).unwrap();
Ok(MixPacket::new(first_hop_address, packet, PacketMode::Mix))
Ok(MixPacket::new(first_hop_address, packet, PacketType::Mix))
}
/// Helper function used to determine if given message represents a loop cover message.
+1
View File
@@ -12,3 +12,4 @@ nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-params = { path = "../params" }
nym-sphinx-types = { path = "../types" }
nym-outfox = { path = "../../../nym-outfox" }
thiserror = "1"
+41 -58
View File
@@ -2,42 +2,28 @@
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
use nym_sphinx_params::{PacketMode, PacketSize};
use nym_sphinx_types::SphinxPacket;
use nym_sphinx_params::{PacketSize, PacketType};
use nym_sphinx_types::{NymPacket, NymPacketError};
use std::convert::TryFrom;
use std::fmt::{self, Debug, Display, Formatter};
use std::fmt::{self, Debug, Formatter};
use thiserror::Error;
#[derive(Debug)]
#[derive(Debug, Error)]
pub enum MixPacketFormattingError {
#[error("too few bytes provided to recover from bytes")]
TooFewBytesProvided,
InvalidPacketMode,
#[error("provided packet mode is invalid")]
InvalidPacketType,
#[error("received request had invalid size - received {0}")]
InvalidPacketSize(usize),
#[error("address field was incorrectly encoded")]
InvalidAddress,
#[error("received sphinx packet was malformed")]
MalformedSphinxPacket,
#[error("Packet: {0}")]
Packet(#[from] NymPacketError),
}
impl Display for MixPacketFormattingError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use MixPacketFormattingError::*;
match self {
TooFewBytesProvided => write!(f, "Too few bytes provided to recover from bytes"),
InvalidAddress => write!(f, "address field was incorrectly encoded"),
InvalidPacketSize(actual) =>
write!(
f,
"received request had invalid size. (actual: {}, but expected one of: {} (ACK), {} (REGULAR), {}, {}, {} (EXTENDED))",
actual, PacketSize::AckPacket.size(), PacketSize::RegularPacket.size(),
PacketSize::ExtendedPacket8.size(), PacketSize::ExtendedPacket16.size(),
PacketSize::ExtendedPacket32.size()
),
MalformedSphinxPacket => write!(f, "received sphinx packet was malformed"),
InvalidPacketMode => write!(f, "provided packet mode is invalid")
}
}
}
impl std::error::Error for MixPacketFormattingError {}
impl From<NymNodeRoutingAddressError> for MixPacketFormattingError {
fn from(_: NymNodeRoutingAddressError) -> Self {
MixPacketFormattingError::InvalidAddress
@@ -46,19 +32,16 @@ impl From<NymNodeRoutingAddressError> for MixPacketFormattingError {
pub struct MixPacket {
next_hop: NymNodeRoutingAddress,
sphinx_packet: SphinxPacket,
packet_mode: PacketMode,
packet: NymPacket,
packet_type: PacketType,
}
impl Debug for MixPacket {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"MixPacket to {:?} with packet_mode {:?}. Sphinx header: {:?}, payload length: {}",
self.next_hop,
self.packet_mode,
self.sphinx_packet.header,
self.sphinx_packet.payload.len()
"MixPacket to {:?} with packet_type {:?}. Packet {:?}",
self.next_hop, self.packet_type, self.packet
)
}
}
@@ -66,13 +49,13 @@ impl Debug for MixPacket {
impl MixPacket {
pub fn new(
next_hop: NymNodeRoutingAddress,
sphinx_packet: SphinxPacket,
packet_mode: PacketMode,
packet: NymPacket,
packet_type: PacketType,
) -> Self {
MixPacket {
next_hop,
sphinx_packet,
packet_mode,
packet,
packet_type,
}
}
@@ -80,52 +63,52 @@ impl MixPacket {
self.next_hop
}
pub fn sphinx_packet(&self) -> &SphinxPacket {
&self.sphinx_packet
pub fn packet(&self) -> &NymPacket {
&self.packet
}
pub fn into_sphinx_packet(self) -> SphinxPacket {
self.sphinx_packet
pub fn into_packet(self) -> NymPacket {
self.packet
}
pub fn packet_mode(&self) -> PacketMode {
self.packet_mode
pub fn packet_type(&self) -> PacketType {
self.packet_type
}
// the message is formatted as follows:
// PACKET_MODE || FIRST_HOP || SPHINX_PACKET
// packet_type || FIRST_HOP || packet
pub fn try_from_bytes(b: &[u8]) -> Result<Self, MixPacketFormattingError> {
let packet_mode = match PacketMode::try_from(b[0]) {
let packet_type = match PacketType::try_from(b[0]) {
Ok(mode) => mode,
Err(_) => return Err(MixPacketFormattingError::InvalidPacketMode),
Err(_) => return Err(MixPacketFormattingError::InvalidPacketType),
};
let next_hop = NymNodeRoutingAddress::try_from_bytes(&b[1..])?;
let addr_offset = next_hop.bytes_min_len();
let sphinx_packet_data = &b[addr_offset + 1..];
let packet_size = sphinx_packet_data.len();
let packet_data = &b[addr_offset + 1..];
let packet_size = packet_data.len();
if PacketSize::get_type(packet_size).is_err() {
Err(MixPacketFormattingError::InvalidPacketSize(packet_size))
} else {
let sphinx_packet = match SphinxPacket::from_bytes(sphinx_packet_data) {
Ok(packet) => packet,
Err(_) => return Err(MixPacketFormattingError::MalformedSphinxPacket),
let packet = match packet_type {
PacketType::Outfox => NymPacket::outfox_from_bytes(packet_data)?,
_ => NymPacket::sphinx_from_bytes(packet_data)?,
};
Ok(MixPacket {
next_hop,
sphinx_packet,
packet_mode,
packet,
packet_type,
})
}
}
pub fn into_bytes(self) -> Vec<u8> {
std::iter::once(self.packet_mode as u8)
pub fn into_bytes(self) -> Result<Vec<u8>, MixPacketFormattingError> {
Ok(std::iter::once(self.packet_type as u8)
.chain(self.next_hop.as_bytes().into_iter())
.chain(self.sphinx_packet.to_bytes().into_iter())
.collect()
.chain(self.packet.to_bytes()?.into_iter())
.collect())
}
}
+140 -93
View File
@@ -1,65 +1,56 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::packet::{FramedSphinxPacket, Header};
use crate::packet::{FramedNymPacket, Header};
use bytes::{Buf, BufMut, BytesMut};
use nym_sphinx_params::packet_modes::InvalidPacketMode;
use nym_sphinx_params::packet_sizes::{InvalidPacketSize, PacketSize};
use nym_sphinx_types::Error as SphinxError;
use nym_sphinx_types::SphinxPacket;
use nym_sphinx_params::packet_types::InvalidPacketType;
use nym_sphinx_params::PacketType;
use nym_sphinx_types::{NymPacket, NymPacketError};
use std::io;
use thiserror::Error;
use tokio_util::codec::{Decoder, Encoder};
#[derive(Error, Debug)]
pub enum SphinxCodecError {
pub enum NymCodecError {
#[error("the packet size information was malformed - {0}")]
InvalidPacketSize(#[from] InvalidPacketSize),
#[error("the packet mode information was malformed - {0}")]
InvalidPacketMode(#[from] InvalidPacketMode),
#[error("the actual sphinx packet was malformed - {0}")]
MalformedSphinxPacket(#[from] SphinxError),
InvalidPacketType(#[from] InvalidPacketType),
#[error("encountered an IO error - {0}")]
IoError(#[from] io::Error),
}
impl From<SphinxCodecError> for io::Error {
fn from(err: SphinxCodecError) -> Self {
match err {
SphinxCodecError::InvalidPacketSize(source) => {
io::Error::new(io::ErrorKind::InvalidInput, source)
}
SphinxCodecError::InvalidPacketMode(source) => {
io::Error::new(io::ErrorKind::InvalidInput, source)
}
SphinxCodecError::MalformedSphinxPacket(source) => {
io::Error::new(io::ErrorKind::InvalidData, source)
}
SphinxCodecError::IoError(err) => err,
}
}
#[error("encountered a packet error - {0}")]
NymPacket(#[from] NymPacketError),
#[error("could not convert to bytes")]
ToBytes,
#[error("could not convert to bytes")]
FromBytes,
}
// TODO: in the future it could be extended to have state containing symmetric encryption key
// so that all data could be encrypted easily (alternatively we could just slap TLS)
pub struct SphinxCodec;
pub struct NymCodec;
impl Encoder<FramedSphinxPacket> for SphinxCodec {
type Error = SphinxCodecError;
impl Encoder<FramedNymPacket> for NymCodec {
type Error = NymCodecError;
fn encode(&mut self, item: FramedSphinxPacket, dst: &mut BytesMut) -> Result<(), Self::Error> {
fn encode(&mut self, item: FramedNymPacket, dst: &mut BytesMut) -> Result<(), Self::Error> {
item.header.encode(dst);
dst.put(item.packet.to_bytes().as_ref());
let packet_bytes = item.packet.to_bytes()?;
let encoded = packet_bytes.as_slice();
dst.put(encoded);
Ok(())
}
}
impl Decoder for SphinxCodec {
type Item = FramedSphinxPacket;
type Error = SphinxCodecError;
impl Decoder for NymCodec {
type Item = FramedNymPacket;
type Error = NymCodecError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if src.is_empty() {
@@ -76,23 +67,32 @@ impl Decoder for SphinxCodec {
None => return Ok(None), // we have some data but not enough to get header back
};
let sphinx_packet_size = header.packet_size.size();
let frame_len = header.size() + sphinx_packet_size;
let packet_size = header.packet_size.size();
let frame_len = header.size() + packet_size;
if src.len() < frame_len {
// we don't have enough bytes to read the rest of frame
src.reserve(sphinx_packet_size);
src.reserve(packet_size);
return Ok(None);
}
// advance buffer past the header - at this point we have enough bytes
src.advance(header.size());
let sphinx_packet_bytes = src.split_to(sphinx_packet_size);
let packet_bytes = src.split_to(packet_size);
let packet = if let Some(slice) = packet_bytes.get(..) {
// here it could be debatable whether stream is corrupt or not,
// but let's go with the safer approach and assume it is.
match header.packet_type {
PacketType::Outfox => NymPacket::outfox_from_bytes(slice)?,
PacketType::Mix => NymPacket::sphinx_from_bytes(slice)?,
PacketType::Vpn => NymPacket::sphinx_from_bytes(slice)?,
}
} else {
return Ok(None);
};
// here it could be debatable whether stream is corrupt or not,
// but let's go with the safer approach and assume it is.
let packet = SphinxPacket::from_bytes(&sphinx_packet_bytes)?;
let nymsphinx_packet = FramedSphinxPacket { header, packet };
// let packet = SphinxPacket::from_bytes(&sphinx_packet_bytes)?;
let nymsphinx_packet = FramedNymPacket { header, packet };
// As per docs:
// Before returning from the function, implementations should ensure that the buffer
@@ -104,6 +104,7 @@ impl Decoder for SphinxCodec {
// reserve for that.
// we also assume the next packet coming from the same client will use exactly the same versioning
// as the current packet
let mut allocate_for_next_packet = header.size() + PacketSize::AckPacket.size();
if !src.is_empty() {
match Header::decode(src) {
@@ -120,7 +121,6 @@ impl Decoder for SphinxCodec {
};
}
src.reserve(allocate_for_next_packet);
Ok(Some(nymsphinx_packet))
}
}
@@ -128,13 +128,47 @@ impl Decoder for SphinxCodec {
#[cfg(test)]
mod packet_encoding {
use super::*;
use nym_sphinx_types::builder::SphinxPacketBuilder;
use nym_sphinx_types::{
crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes,
DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH,
};
fn make_valid_sphinx_packet(size: PacketSize) -> SphinxPacket {
fn make_valid_outfox_packet(size: PacketSize) -> NymPacket {
let (_, node1_pk) = crypto::keygen();
let node1 = Node::new(
NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
node1_pk,
);
let (_, node2_pk) = crypto::keygen();
let node2 = Node::new(
NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]),
node2_pk,
);
let (_, node3_pk) = crypto::keygen();
let node3 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pk,
);
let (_, node4_pk) = crypto::keygen();
let node4 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node4_pk,
);
let destination = Destination::new(
DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]),
[4u8; IDENTIFIER_LENGTH],
);
let route = &[node1, node2, node3, node4];
let payload = vec![1; 48];
NymPacket::outfox_build(payload, route, &destination, Some(size.plaintext_size())).unwrap()
}
fn make_valid_sphinx_packet(size: PacketSize) -> NymPacket {
let (_, node1_pk) = crypto::keygen();
let node1 = Node::new(
NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
@@ -161,9 +195,7 @@ mod packet_encoding {
SphinxDelay::new_from_nanos(42),
SphinxDelay::new_from_nanos(42),
];
SphinxPacketBuilder::new()
.with_payload_size(size.payload_size())
.build_packet(b"foomp", &route, &destination, &delays)
NymPacket::sphinx_build(size.payload_size(), b"foomp", &route, &destination, &delays)
.unwrap()
}
@@ -171,32 +203,50 @@ mod packet_encoding {
fn whole_packet_can_be_decoded_from_a_valid_encoded_instance() {
let header = Default::default();
let sphinx_packet = make_valid_sphinx_packet(Default::default());
let sphinx_bytes = sphinx_packet.to_bytes();
let sphinx_bytes = sphinx_packet.to_bytes().unwrap();
let packet = FramedSphinxPacket {
let packet = FramedNymPacket {
header,
packet: sphinx_packet,
};
let mut bytes = BytesMut::new();
SphinxCodec.encode(packet, &mut bytes).unwrap();
let decoded = SphinxCodec.decode(&mut bytes).unwrap().unwrap();
NymCodec.encode(packet, &mut bytes).unwrap();
let decoded = NymCodec.decode(&mut bytes).unwrap().unwrap();
assert_eq!(decoded.header, header);
assert_eq!(decoded.packet.to_bytes(), sphinx_bytes)
assert_eq!(decoded.packet.to_bytes().unwrap(), sphinx_bytes)
}
#[test]
fn whole_outfox_can_be_decoded_from_a_valid_encoded_instance() {
let header = Header::outfox();
let packet = make_valid_outfox_packet(PacketSize::OutfoxRegularPacket);
let packet_bytes = packet.to_bytes().unwrap();
NymPacket::outfox_from_bytes(packet_bytes.as_slice()).unwrap();
let packet = FramedNymPacket { header, packet };
let mut bytes = BytesMut::new();
NymCodec.encode(packet, &mut bytes).unwrap();
let decoded = NymCodec.decode(&mut bytes).unwrap().unwrap();
assert_eq!(decoded.header, header);
assert_eq!(decoded.packet.to_bytes().unwrap(), packet_bytes)
}
#[cfg(test)]
mod decode_will_allocate_enough_bytes_for_next_call {
use super::*;
use nym_sphinx_params::packet_version::PacketVersion;
use nym_sphinx_params::PacketMode;
use nym_sphinx_params::PacketType;
#[test]
fn for_empty_bytes() {
// empty bytes should allocate for header + ack packet
let mut empty_bytes = BytesMut::new();
assert!(SphinxCodec.decode(&mut empty_bytes).unwrap().is_none());
assert!(NymCodec.decode(&mut empty_bytes).unwrap().is_none());
assert_eq!(
empty_bytes.capacity(),
Header::LEGACY_SIZE + PacketSize::AckPacket.size()
@@ -217,11 +267,11 @@ mod packet_encoding {
let header = Header {
packet_version: PacketVersion::Legacy,
packet_size,
packet_mode: Default::default(),
..Default::default()
};
let mut bytes = BytesMut::new();
header.encode(&mut bytes);
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
assert!(NymCodec.decode(&mut bytes).unwrap().is_none());
assert_eq!(bytes.capacity(), Header::LEGACY_SIZE + packet_size.size())
}
@@ -241,11 +291,11 @@ mod packet_encoding {
let header = Header {
packet_version: PacketVersion::Versioned(123),
packet_size,
packet_mode: Default::default(),
..Default::default()
};
let mut bytes = BytesMut::new();
header.encode(&mut bytes);
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
assert!(NymCodec.decode(&mut bytes).unwrap().is_none());
assert_eq!(
bytes.capacity(),
@@ -257,18 +307,17 @@ mod packet_encoding {
#[test]
fn for_full_frame_with_legacy_header() {
// if full frame is used exactly, there should be enough space for header + ack packet
let packet = FramedSphinxPacket {
let packet = FramedNymPacket {
header: Header {
packet_version: PacketVersion::Legacy,
packet_size: Default::default(),
packet_mode: Default::default(),
..Default::default()
},
packet: make_valid_sphinx_packet(Default::default()),
};
let mut bytes = BytesMut::new();
SphinxCodec.encode(packet, &mut bytes).unwrap();
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
NymCodec.encode(packet, &mut bytes).unwrap();
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert_eq!(
bytes.capacity(),
Header::LEGACY_SIZE + PacketSize::AckPacket.size()
@@ -278,14 +327,14 @@ mod packet_encoding {
#[test]
fn for_full_frame_with_versioned_header() {
// if full frame is used exactly, there should be enough space for header + ack packet
let packet = FramedSphinxPacket {
let packet = FramedNymPacket {
header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()),
};
let mut bytes = BytesMut::new();
SphinxCodec.encode(packet, &mut bytes).unwrap();
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
NymCodec.encode(packet, &mut bytes).unwrap();
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert_eq!(
bytes.capacity(),
Header::VERSIONED_SIZE + PacketSize::AckPacket.size()
@@ -304,20 +353,19 @@ mod packet_encoding {
];
for packet_size in packet_sizes {
let first_packet = FramedSphinxPacket {
let first_packet = FramedNymPacket {
header: Header {
packet_version: PacketVersion::Legacy,
packet_size: Default::default(),
packet_mode: Default::default(),
..Default::default()
},
packet: make_valid_sphinx_packet(Default::default()),
};
let mut bytes = BytesMut::new();
SphinxCodec.encode(first_packet, &mut bytes).unwrap();
NymCodec.encode(first_packet, &mut bytes).unwrap();
bytes.put_u8(packet_size as u8);
bytes.put_u8(PacketMode::default() as u8);
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
bytes.put_u8(PacketType::default() as u8);
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert!(bytes.capacity() >= Header::LEGACY_SIZE + packet_size.size())
}
@@ -335,53 +383,53 @@ mod packet_encoding {
];
for packet_size in packet_sizes {
let first_packet = FramedSphinxPacket {
let first_packet = FramedNymPacket {
header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()),
};
let mut bytes = BytesMut::new();
SphinxCodec.encode(first_packet, &mut bytes).unwrap();
NymCodec.encode(first_packet, &mut bytes).unwrap();
bytes.put_u8(PacketVersion::new_versioned(123).as_u8().unwrap());
bytes.put_u8(packet_size as u8);
bytes.put_u8(PacketMode::default() as u8);
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
bytes.put_u8(PacketType::default() as u8);
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert!(bytes.capacity() >= Header::VERSIONED_SIZE + packet_size.size())
// assert!(bytes.capacity() >= Header::VERSIONED_SIZE + packet_size.size())
}
}
}
#[test]
fn can_decode_two_packets_immediately() {
let packet1 = FramedSphinxPacket {
let packet1 = FramedNymPacket {
header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()),
};
let packet2 = FramedSphinxPacket {
let packet2 = FramedNymPacket {
header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()),
};
let mut bytes = BytesMut::new();
SphinxCodec.encode(packet1, &mut bytes).unwrap();
SphinxCodec.encode(packet2, &mut bytes).unwrap();
NymCodec.encode(packet1, &mut bytes).unwrap();
NymCodec.encode(packet2, &mut bytes).unwrap();
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert!(NymCodec.decode(&mut bytes).unwrap().is_none());
}
#[test]
fn can_decode_two_packets_in_separate_calls() {
let packet1 = FramedSphinxPacket {
let packet1 = FramedNymPacket {
header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()),
};
let packet2 = FramedSphinxPacket {
let packet2 = FramedNymPacket {
header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()),
};
@@ -389,18 +437,17 @@ mod packet_encoding {
let mut bytes = BytesMut::new();
let mut bytes_tmp = BytesMut::new();
SphinxCodec.encode(packet1, &mut bytes).unwrap();
SphinxCodec.encode(packet2, &mut bytes_tmp).unwrap();
NymCodec.encode(packet1, &mut bytes).unwrap();
NymCodec.encode(packet2, &mut bytes_tmp).unwrap();
let tmp = bytes_tmp.split_off(100);
bytes.put(bytes_tmp);
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert!(NymCodec.decode(&mut bytes).unwrap().is_none());
bytes.put(tmp);
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert!(NymCodec.decode(&mut bytes).unwrap().is_none());
}
}
+49 -31
View File
@@ -1,47 +1,57 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::codec::SphinxCodecError;
use crate::codec::NymCodecError;
use bytes::{BufMut, BytesMut};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::packet_version::PacketVersion;
use nym_sphinx_params::PacketMode;
use nym_sphinx_types::SphinxPacket;
use nym_sphinx_params::PacketType;
use nym_sphinx_types::NymPacket;
use std::convert::TryFrom;
pub struct FramedSphinxPacket {
#[derive(Debug)]
pub struct FramedNymPacket {
/// Contains any metadata helping receiver to handle the underlying packet.
pub(crate) header: Header,
/// The actual SphinxPacket being sent.
pub(crate) packet: SphinxPacket,
pub(crate) packet: NymPacket,
}
impl FramedSphinxPacket {
pub fn new(packet: SphinxPacket, packet_mode: PacketMode, use_legacy_version: bool) -> Self {
impl FramedNymPacket {
pub fn new(packet: NymPacket, packet_type: PacketType, use_legacy_version: bool) -> Self {
// If this fails somebody is using the library in a super incorrect way, because they
// already managed to somehow create a sphinx packet
let packet_size = PacketSize::get_type(packet.len()).unwrap();
FramedSphinxPacket {
header: Header {
packet_version: PacketVersion::new(use_legacy_version),
packet_size,
packet_mode,
},
packet,
}
let use_legacy = if packet_type == PacketType::Outfox {
false
} else {
use_legacy_version
};
let header = Header {
packet_version: PacketVersion::new(use_legacy),
packet_size,
packet_type,
};
FramedNymPacket { header, packet }
}
pub fn header(&self) -> Header {
self.header
}
pub fn packet_size(&self) -> PacketSize {
self.header.packet_size
}
pub fn packet_mode(&self) -> PacketMode {
self.header.packet_mode
pub fn packet_type(&self) -> PacketType {
self.header.packet_type
}
pub fn into_inner(self) -> SphinxPacket {
pub fn into_inner(self) -> NymPacket {
self.packet
}
}
@@ -64,15 +74,23 @@ pub struct Header {
///
/// TODO: ask @AP whether this can be sent like this - could it introduce some anonymity issues?
/// (note: this will be behind some encryption, either something implemented by us or some SSL action)
// Note: currently packet_mode is deprecated but is still left as a concept behind to not break
// Note: currently packet_type is deprecated but is still left as a concept behind to not break
// compatibility with existing network
pub(crate) packet_mode: PacketMode,
pub(crate) packet_type: PacketType,
}
impl Header {
pub(crate) const LEGACY_SIZE: usize = 2;
pub(crate) const VERSIONED_SIZE: usize = 3;
pub fn outfox() -> Header {
Header {
packet_version: PacketVersion::default(),
packet_size: PacketSize::OutfoxRegularPacket,
packet_type: PacketType::Outfox,
}
}
pub(crate) fn size(&self) -> usize {
if self.packet_version.is_legacy() {
Self::LEGACY_SIZE
@@ -90,12 +108,12 @@ impl Header {
}
dst.put_u8(self.packet_size as u8);
dst.put_u8(self.packet_mode as u8);
dst.put_u8(self.packet_type as u8);
// reserve bytes for the actual packet
dst.reserve(self.packet_size.size());
}
pub(crate) fn decode(src: &mut BytesMut) -> Result<Option<Self>, SphinxCodecError> {
pub(crate) fn decode(src: &mut BytesMut) -> Result<Option<Self>, NymCodecError> {
if src.len() < Self::LEGACY_SIZE {
// can't do anything if we don't have enough bytes - but reserve enough for the next call
src.reserve(Self::LEGACY_SIZE);
@@ -107,7 +125,7 @@ impl Header {
Ok(Some(Header {
packet_version,
packet_size: PacketSize::try_from(src[0])?,
packet_mode: PacketMode::try_from(src[1])?,
packet_type: PacketType::try_from(src[1])?,
}))
} else if src.len() < Self::VERSIONED_SIZE {
// we're missing that 1 byte to read the full header...
@@ -117,7 +135,7 @@ impl Header {
Ok(Some(Header {
packet_version,
packet_size: PacketSize::try_from(src[1])?,
packet_mode: PacketMode::try_from(src[2])?,
packet_type: PacketType::try_from(src[2])?,
}))
}
}
@@ -148,7 +166,7 @@ mod header_encoding {
[
PacketVersion::new_versioned(123).as_u8().unwrap(),
unknown_packet_size,
PacketMode::default() as u8,
PacketType::default() as u8,
]
.as_ref(),
);
@@ -156,12 +174,12 @@ mod header_encoding {
}
#[test]
fn decoding_will_fail_for_unknown_packet_mode() {
let unknown_packet_mode: u8 = 255;
fn decoding_will_fail_for_unknown_packet_type() {
let unknown_packet_type: u8 = 255;
// make sure this is still 'unknown' for if we make changes in the future
assert!(PacketMode::try_from(unknown_packet_mode).is_err());
assert!(PacketType::try_from(unknown_packet_type).is_err());
let mut bytes = BytesMut::from([PacketSize::default() as u8, unknown_packet_mode].as_ref());
let mut bytes = BytesMut::from([PacketSize::default() as u8, unknown_packet_type].as_ref());
assert!(Header::decode(&mut bytes).is_err())
}
@@ -191,7 +209,7 @@ mod header_encoding {
let header = Header {
packet_version: PacketVersion::Legacy,
packet_size,
packet_mode: Default::default(),
..Default::default()
};
let mut bytes = BytesMut::new();
header.encode(&mut bytes);
@@ -212,7 +230,7 @@ mod header_encoding {
let header = Header {
packet_version: PacketVersion::Versioned(123),
packet_size,
packet_mode: Default::default(),
..Default::default()
};
let mut bytes = BytesMut::new();
header.encode(&mut bytes);
+3 -3
View File
@@ -8,11 +8,11 @@ use nym_crypto::ctr;
type Aes128Ctr = ctr::Ctr64BE<Aes128>;
// Re-export for ease of use
pub use packet_modes::PacketMode;
pub use packet_sizes::PacketSize;
pub use packet_types::PacketType;
pub mod packet_modes;
pub mod packet_sizes;
pub mod packet_types;
pub mod packet_version;
// If somebody can provide an argument why it might be reasonable to have more than 255 mix hops,
@@ -29,7 +29,7 @@ pub type SerializedFragmentIdentifier = [u8; FRAG_ID_LEN];
// when packet header gets serialized, the following bytes (in that order) are put onto the wire:
// - packet_version (starting with v1.1.0)
// - packet_size indicator
// - packet_mode
// - packet_type
// it also just so happens that the only valid values for packet_size indicator include values 1-6
// therefore if we receive byte `7` (or larger than that) we'll know we received a versioned packet,
// otherwise we should treat it as legacy
@@ -1,46 +0,0 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::convert::TryFrom;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("{received} is not a valid packet mode tag")]
pub struct InvalidPacketMode {
received: u8,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum PacketMode {
/// Represents 'normal' packet sent through the network that should be delayed by an appropriate
/// value at each hop.
#[default]
Mix = 0,
/// Represents a VPN packet that should not be delayed and ideally cached pre-computed keys
/// should be used for unwrapping data. Note that it does not offer the same level of anonymity.
Vpn = 1,
}
impl PacketMode {
pub fn is_mix(self) -> bool {
self == PacketMode::Mix
}
pub fn is_old_vpn(self) -> bool {
self == PacketMode::Vpn
}
}
impl TryFrom<u8> for PacketMode {
type Error = InvalidPacketMode;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
_ if value == (PacketMode::Mix as u8) => Ok(Self::Mix),
_ if value == (PacketMode::Vpn as u8) => Ok(Self::Vpn),
v => Err(InvalidPacketMode { received: v }),
}
}
}
+75 -13
View File
@@ -1,9 +1,11 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::FRAG_ID_LEN;
use crate::{PacketType, FRAG_ID_LEN};
use nym_sphinx_types::header::HEADER_SIZE;
use nym_sphinx_types::PAYLOAD_OVERHEAD_SIZE;
use nym_sphinx_types::{
MIN_PACKET_SIZE, MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD, PAYLOAD_OVERHEAD_SIZE,
};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::convert::TryFrom;
@@ -12,20 +14,24 @@ use std::str::FromStr;
use thiserror::Error;
// each sphinx packet contains mandatory header and payload padding + markers
const PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE;
const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE;
// it's up to the smart people to figure those values out : )
const REGULAR_PACKET_SIZE: usize = 2 * 1024 + PACKET_OVERHEAD;
// TODO: even though we have 16B IV, is having just 5B (FRAG_ID_LEN) of the ID possibly insecure?
// TODO: I'm not entirely sure if we can easily extract `<AckEncryptionAlgorithm as NewStreamCipher>::NonceSize`
// into a const usize before relevant stuff is stabilised in rust...
const ACK_IV_SIZE: usize = 16;
const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + FRAG_ID_LEN + PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + PACKET_OVERHEAD;
const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD;
const REGULAR_PACKET_SIZE: usize = 2 * 1024 + SPHINX_PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + SPHINX_PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + SPHINX_PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + SPHINX_PACKET_OVERHEAD;
const OUTFOX_ACK_PACKET_SIZE: usize = MIN_PACKET_SIZE + OUTFOX_PACKET_OVERHEAD;
const OUTFOX_REGULAR_PACKET_SIZE: usize = 2 * 1024 + OUTFOX_PACKET_OVERHEAD;
#[derive(Debug, Error)]
pub enum InvalidPacketSize {
@@ -62,6 +68,13 @@ pub enum PacketSize {
// for example for streaming fast and furious in compressed XviD quality
#[serde(rename = "extended16")]
ExtendedPacket16 = 5,
#[serde(rename = "outfox_regular")]
OutfoxRegularPacket = 6,
// for sending SURB-ACKs
#[serde(rename = "outfox_ack")]
OutfoxAckPacket = 7,
}
impl PartialOrd for PacketSize {
@@ -88,6 +101,8 @@ impl FromStr for PacketSize {
"extended8" => Ok(Self::ExtendedPacket8),
"extended16" => Ok(Self::ExtendedPacket16),
"extended32" => Ok(Self::ExtendedPacket32),
"outfox_regular" => Ok(Self::OutfoxRegularPacket),
"outfox_ack" => Ok(Self::OutfoxAckPacket),
s => Err(InvalidPacketSize::UnknownExtendedPacketVariant {
received: s.to_string(),
}),
@@ -103,6 +118,8 @@ impl Display for PacketSize {
PacketSize::ExtendedPacket32 => write!(f, "extended32"),
PacketSize::ExtendedPacket8 => write!(f, "extended8"),
PacketSize::ExtendedPacket16 => write!(f, "extended16"),
PacketSize::OutfoxRegularPacket => write!(f, "outfox_regular"),
PacketSize::OutfoxAckPacket => write!(f, "outfox_ack"),
}
}
}
@@ -127,6 +144,8 @@ impl TryFrom<u8> for PacketSize {
_ if value == (PacketSize::ExtendedPacket8 as u8) => Ok(Self::ExtendedPacket8),
_ if value == (PacketSize::ExtendedPacket16 as u8) => Ok(Self::ExtendedPacket16),
_ if value == (PacketSize::ExtendedPacket32 as u8) => Ok(Self::ExtendedPacket32),
_ if value == (PacketSize::OutfoxRegularPacket as u8) => Ok(Self::OutfoxRegularPacket),
_ if value == (PacketSize::OutfoxAckPacket as u8) => Ok(Self::OutfoxAckPacket),
v => Err(InvalidPacketSize::UnknownPacketTag { received: v }),
}
}
@@ -140,15 +159,41 @@ impl PacketSize {
PacketSize::ExtendedPacket8 => EXTENDED_PACKET_SIZE_8,
PacketSize::ExtendedPacket16 => EXTENDED_PACKET_SIZE_16,
PacketSize::ExtendedPacket32 => EXTENDED_PACKET_SIZE_32,
PacketSize::OutfoxRegularPacket => OUTFOX_REGULAR_PACKET_SIZE,
PacketSize::OutfoxAckPacket => OUTFOX_ACK_PACKET_SIZE,
}
}
pub const fn header_size(&self) -> usize {
match self {
PacketSize::RegularPacket
| PacketSize::AckPacket
| PacketSize::ExtendedPacket8
| PacketSize::ExtendedPacket16
| PacketSize::ExtendedPacket32 => HEADER_SIZE,
PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => MIX_PARAMS_LEN,
}
}
pub const fn payload_overhead(&self) -> usize {
match self {
PacketSize::RegularPacket
| PacketSize::AckPacket
| PacketSize::ExtendedPacket8
| PacketSize::ExtendedPacket16
| PacketSize::ExtendedPacket32 => PAYLOAD_OVERHEAD_SIZE,
PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => {
OUTFOX_PACKET_OVERHEAD - MIX_PARAMS_LEN // Mix params are calculated into the total overhead so we take them out here
}
}
}
pub const fn plaintext_size(self) -> usize {
self.size() - HEADER_SIZE - PAYLOAD_OVERHEAD_SIZE
self.size() - self.header_size() - self.payload_overhead()
}
pub const fn payload_size(self) -> usize {
self.size() - HEADER_SIZE
self.size() - self.header_size()
}
pub fn get_type(size: usize) -> Result<Self, InvalidPacketSize> {
@@ -162,6 +207,12 @@ impl PacketSize {
Ok(PacketSize::ExtendedPacket16)
} else if PacketSize::ExtendedPacket32.size() == size {
Ok(PacketSize::ExtendedPacket32)
} else if PacketSize::OutfoxRegularPacket.size() == size
|| PacketSize::OutfoxRegularPacket.size() == size + 6
{
Ok(PacketSize::OutfoxRegularPacket)
} else if PacketSize::OutfoxAckPacket.size() == size {
Ok(PacketSize::OutfoxAckPacket)
} else {
Err(InvalidPacketSize::UnknownPacketSize { received: size })
}
@@ -169,7 +220,10 @@ impl PacketSize {
pub fn is_extended_size(&self) -> bool {
match self {
PacketSize::RegularPacket | PacketSize::AckPacket => false,
PacketSize::RegularPacket
| PacketSize::AckPacket
| PacketSize::OutfoxAckPacket
| PacketSize::OutfoxRegularPacket => false,
PacketSize::ExtendedPacket8
| PacketSize::ExtendedPacket16
| PacketSize::ExtendedPacket32 => true,
@@ -184,8 +238,16 @@ impl PacketSize {
}
}
pub fn get_type_from_plaintext(plaintext_size: usize) -> Result<Self, InvalidPacketSize> {
let packet_size = plaintext_size + PACKET_OVERHEAD;
pub fn get_type_from_plaintext(
plaintext_size: usize,
packet_type: PacketType,
) -> Result<Self, InvalidPacketSize> {
let overhead = match packet_type {
PacketType::Mix => SPHINX_PACKET_OVERHEAD,
PacketType::Vpn => SPHINX_PACKET_OVERHEAD,
PacketType::Outfox => OUTFOX_PACKET_OVERHEAD,
};
let packet_size = plaintext_size + overhead;
Self::get_type(packet_size)
}
}
@@ -0,0 +1,76 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt;
use thiserror::Error;
use crate::PacketSize;
#[derive(Error, Debug)]
#[error("{received} is not a valid packet mode tag")]
pub struct InvalidPacketType {
received: u8,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum PacketType {
/// Represents 'normal' packet sent through the network that should be delayed by an appropriate
/// value at each hop.
#[default]
Mix = 0,
/// Represents a packet that should be sent through the network as fast as possible.
Vpn = 1,
/// Abusing this to add Outfox support
Outfox = 2,
}
impl fmt::Display for PacketType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PacketType::Mix => write!(f, "Mix"),
PacketType::Vpn => write!(f, "Vpn"),
PacketType::Outfox => write!(f, "Outfox"),
}
}
}
impl PacketType {
pub fn is_mix(self) -> bool {
self == PacketType::Mix
}
pub fn is_outfox(self) -> bool {
self == PacketType::Outfox
}
}
impl TryFrom<u8> for PacketType {
type Error = InvalidPacketType;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
_ if value == (PacketType::Mix as u8) => Ok(Self::Mix),
_ if value == (PacketType::Outfox as u8) => Ok(Self::Outfox),
v => Err(InvalidPacketType { received: v }),
}
}
}
impl From<PacketSize> for PacketType {
fn from(s: PacketSize) -> Self {
match s {
PacketSize::RegularPacket => PacketType::Mix,
PacketSize::AckPacket => PacketType::Mix,
PacketSize::ExtendedPacket32 => PacketType::Mix,
PacketSize::ExtendedPacket8 => PacketType::Mix,
PacketSize::ExtendedPacket16 => PacketType::Mix,
PacketSize::OutfoxRegularPacket => PacketType::Outfox,
PacketSize::OutfoxAckPacket => PacketType::Outfox,
}
}
}
@@ -1,9 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use crate::{PacketSize, CURRENT_PACKET_VERSION_NUMBER};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PacketVersion {
// this will allow updated mixnodes to still understand packets from before the update
Legacy,
+1 -1
View File
@@ -21,4 +21,4 @@ pub use nym_sphinx_types::*;
pub use nym_sphinx_framing as framing;
// TEMP UNTIL FURTHER REFACTORING
pub use preparer::payload::NymsphinxPayloadBuilder;
pub use preparer::payload::NymPayloadBuilder;
+11 -2
View File
@@ -11,12 +11,14 @@ use nym_sphinx_anonymous_replies::requests::{
ReplyMessageContent,
};
use nym_sphinx_chunking::fragment::Fragment;
use nym_sphinx_params::{PacketSize, ReplySurbKeyDigestAlgorithm};
use nym_sphinx_params::{PacketSize, PacketType, ReplySurbKeyDigestAlgorithm};
use rand::Rng;
use std::fmt::{Display, Formatter};
use thiserror::Error;
pub(crate) const ACK_OVERHEAD: usize = MAX_NODE_ADDRESS_UNPADDED_LEN + PacketSize::AckPacket.size();
pub(crate) const OUTFOX_ACK_OVERHEAD: usize =
MAX_NODE_ADDRESS_UNPADDED_LEN + PacketSize::OutfoxAckPacket.size();
#[derive(Debug, Error)]
pub enum NymMessageError {
@@ -187,8 +189,15 @@ impl NymMessage {
NymMessage::Reply(_) => ReplySurbKeyDigestAlgorithm::output_size(),
};
let packet_type = PacketType::from(packet_size);
// each packet will contain an ack + variant specific data (as described above)
packet_size.plaintext_size() - ACK_OVERHEAD - variant_overhead
match packet_type {
PacketType::Outfox => {
packet_size.plaintext_size() - OUTFOX_ACK_OVERHEAD - variant_overhead
}
_ => packet_size.plaintext_size() - ACK_OVERHEAD - variant_overhead,
}
}
/// Length of the actual (from the **message** point of view) data that is available in each packet.
+93 -27
View File
@@ -1,8 +1,8 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::message::{NymMessage, ACK_OVERHEAD};
use crate::NymsphinxPayloadBuilder;
use crate::message::{NymMessage, ACK_OVERHEAD, OUTFOX_ACK_OVERHEAD};
use crate::NymPayloadBuilder;
use nym_crypto::asymmetric::encryption;
use nym_crypto::Digest;
use nym_sphinx_acknowledgements::surb_ack::SurbAck;
@@ -13,9 +13,8 @@ use nym_sphinx_anonymous_replies::reply_surb::ReplySurb;
use nym_sphinx_chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::builder::SphinxPacketBuilder;
use nym_sphinx_types::{delays, Delay};
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::{delays, Delay, NymPacket};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, Rng};
use std::convert::TryFrom;
@@ -77,6 +76,7 @@ pub trait FragmentPreparer {
fragment_id: FragmentIdentifier,
topology: &NymTopology,
ack_key: &AckKey,
packet_type: PacketType,
) -> Result<SurbAck, NymTopologyError> {
let ack_delay = self.average_ack_delay();
@@ -87,6 +87,7 @@ pub trait FragmentPreparer {
fragment_id.to_bytes(),
ack_delay,
topology,
packet_type,
)
}
@@ -107,15 +108,20 @@ pub trait FragmentPreparer {
ack_key: &AckKey,
reply_surb: ReplySurb,
packet_sender: &Recipient,
packet_type: PacketType,
) -> Result<PreparedFragment, NymTopologyError> {
// each reply attaches the digest of the encryption key so that the recipient could
// lookup correct key for decryption,
let reply_overhead = ReplySurbKeyDigestAlgorithm::output_size();
let expected_plaintext = fragment.serialized_size() + ACK_OVERHEAD + reply_overhead;
let expected_plaintext = match packet_type {
PacketType::Outfox => fragment.serialized_size() + OUTFOX_ACK_OVERHEAD + reply_overhead,
_ => fragment.serialized_size() + ACK_OVERHEAD + reply_overhead,
};
// the reason we're unwrapping (or rather 'expecting') here rather than handling the error
// more gracefully is that this error should never be reached as it implies incorrect chunking
let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext)
// reply packets are always Sphinx
let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext, PacketType::Mix)
.expect("the message has been incorrectly fragmented");
// this is not going to be accurate by any means. but that's the best estimation we can do
@@ -126,24 +132,34 @@ pub trait FragmentPreparer {
let fragment_identifier = fragment.fragment_identifier();
// create an ack
let surb_ack =
self.generate_surb_ack(packet_sender, fragment_identifier, topology, ack_key)?;
let surb_ack = self.generate_surb_ack(
packet_sender,
fragment_identifier,
topology,
ack_key,
packet_type,
)?;
let ack_delay = surb_ack.expected_total_delay();
let packet_payload = NymsphinxPayloadBuilder::new(fragment, surb_ack)
.build_reply(reply_surb.encryption_key());
let packet_payload = match NymPayloadBuilder::new(fragment, surb_ack)
.build_reply(reply_surb.encryption_key())
{
Ok(payload) => payload,
Err(_e) => return Err(NymTopologyError::PayloadBuilder),
};
// the unwrap here is fine as the failures can only originate from attempting to use invalid payload lengths
// and we just very carefully constructed a (presumably) valid one
let (sphinx_packet, first_hop_address) =
reply_surb.apply_surb(packet_payload, packet_size).unwrap();
let (sphinx_packet, first_hop_address) = reply_surb
.apply_surb(packet_payload, packet_size, packet_type)
.unwrap();
Ok(PreparedFragment {
// the round-trip delay is the sum of delays of all hops on the forward route as
// well as the total delay of the ack packet.
// we don't know the delays inside the reply surbs so we use best-effort estimation from our poisson distribution
total_delay: expected_forward_delay + ack_delay,
mix_packet: MixPacket::new(first_hop_address, sphinx_packet, Default::default()),
mix_packet: MixPacket::new(first_hop_address, sphinx_packet, packet_type),
fragment_identifier,
})
}
@@ -172,27 +188,42 @@ pub trait FragmentPreparer {
ack_key: &AckKey,
packet_sender: &Recipient,
packet_recipient: &Recipient,
packet_type: PacketType,
) -> Result<PreparedFragment, NymTopologyError> {
// each plain or repliable packet (i.e. not a reply) attaches an ephemeral public key so that the recipient
// could perform diffie-hellman with its own keys followed by a kdf to re-derive
// the packet encryption key
let non_reply_overhead = encryption::PUBLIC_KEY_SIZE;
let expected_plaintext = fragment.serialized_size() + ACK_OVERHEAD + non_reply_overhead;
let expected_plaintext = match packet_type {
PacketType::Outfox => {
fragment.serialized_size() + OUTFOX_ACK_OVERHEAD + non_reply_overhead
}
_ => fragment.serialized_size() + ACK_OVERHEAD + non_reply_overhead,
};
// the reason we're unwrapping (or rather 'expecting') here rather than handling the error
// more gracefully is that this error should never be reached as it implies incorrect chunking
let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext)
let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext, packet_type)
.expect("the message has been incorrectly fragmented");
let fragment_identifier = fragment.fragment_identifier();
// create an ack
let surb_ack =
self.generate_surb_ack(packet_sender, fragment_identifier, topology, ack_key)?;
let surb_ack = self.generate_surb_ack(
packet_sender,
fragment_identifier,
topology,
ack_key,
packet_type,
)?;
let ack_delay = surb_ack.expected_total_delay();
let packet_payload = NymsphinxPayloadBuilder::new(fragment, surb_ack)
.build_regular(self.rng(), packet_recipient.encryption_key());
let packet_payload = match NymPayloadBuilder::new(fragment, surb_ack)
.build_regular(self.rng(), packet_recipient.encryption_key())
{
Ok(payload) => payload,
Err(_e) => return Err(NymTopologyError::PayloadBuilder),
};
// generate pseudorandom route for the packet
let hops = self.num_mix_hops();
@@ -206,10 +237,28 @@ pub trait FragmentPreparer {
// create the actual sphinx packet here. With valid route and correct payload size,
// there's absolutely no reason for this call to fail.
let sphinx_packet = SphinxPacketBuilder::new()
.with_payload_size(packet_size.payload_size())
.build_packet(packet_payload, &route, &destination, &delays)
.unwrap();
let packet = match packet_type {
PacketType::Outfox => NymPacket::outfox_build(
packet_payload,
route.as_slice(),
&destination,
Some(packet_size.plaintext_size()),
)?,
PacketType::Mix => NymPacket::sphinx_build(
packet_size.payload_size(),
packet_payload,
&route,
&destination,
&delays,
)?,
PacketType::Vpn => NymPacket::sphinx_build(
packet_size.payload_size(),
packet_payload,
&route,
&destination,
&delays,
)?,
};
// from the previously constructed route extract the first hop
let first_hop_address =
@@ -220,7 +269,7 @@ pub trait FragmentPreparer {
// well as the total delay of the ack packet.
// note that the last hop of the packet is a gateway that does not do any delays
total_delay: delays.iter().take(delays.len() - 1).sum::<Delay>() + ack_delay,
mix_packet: MixPacket::new(first_hop_address, sphinx_packet, Default::default()),
mix_packet: MixPacket::new(first_hop_address, packet, packet_type),
fragment_identifier,
})
}
@@ -317,11 +366,18 @@ where
topology: &NymTopology,
ack_key: &AckKey,
reply_surb: ReplySurb,
packet_type: PacketType,
) -> Result<PreparedFragment, NymTopologyError> {
let sender = self.sender_address;
<Self as FragmentPreparer>::prepare_reply_chunk_for_sending(
self, fragment, topology, ack_key, reply_surb, &sender,
self,
fragment,
topology,
ack_key,
reply_surb,
&sender,
packet_type,
)
}
@@ -331,6 +387,7 @@ where
topology: &NymTopology,
ack_key: &AckKey,
packet_recipient: &Recipient,
packet_type: PacketType,
) -> Result<PreparedFragment, NymTopologyError> {
let sender = self.sender_address;
@@ -341,6 +398,7 @@ where
ack_key,
&sender,
packet_recipient,
packet_type,
)
}
@@ -350,9 +408,17 @@ where
fragment_id: FragmentIdentifier,
topology: &NymTopology,
ack_key: &AckKey,
packet_type: PacketType,
) -> Result<SurbAck, NymTopologyError> {
let sender = self.sender_address;
<Self as FragmentPreparer>::generate_surb_ack(self, &sender, fragment_id, topology, ack_key)
<Self as FragmentPreparer>::generate_surb_ack(
self,
&sender,
fragment_id,
topology,
ack_key,
packet_type,
)
}
pub fn pad_and_split_message(
+16 -12
View File
@@ -6,7 +6,7 @@ use nym_crypto::asymmetric::encryption;
use nym_crypto::shared_key::new_ephemeral_shared_key;
use nym_crypto::symmetric::stream_cipher;
use nym_crypto::symmetric::stream_cipher::CipherKey;
use nym_sphinx_acknowledgements::surb_ack::SurbAck;
use nym_sphinx_acknowledgements::surb_ack::{SurbAck, SurbAckRecoveryError};
use nym_sphinx_anonymous_replies::SurbEncryptionKey;
use nym_sphinx_chunking::fragment::Fragment;
use nym_sphinx_params::{
@@ -14,25 +14,25 @@ use nym_sphinx_params::{
};
use rand::{CryptoRng, RngCore};
pub struct NymsphinxPayloadBuilder {
pub struct NymPayloadBuilder {
fragment: Fragment,
surb_ack: SurbAck,
}
impl NymsphinxPayloadBuilder {
impl NymPayloadBuilder {
pub fn new(fragment: Fragment, surb_ack: SurbAck) -> Self {
NymsphinxPayloadBuilder { fragment, surb_ack }
NymPayloadBuilder { fragment, surb_ack }
}
fn build<C>(
self,
packet_encryption_key: &CipherKey<C>,
variant_data: impl IntoIterator<Item = u8>,
) -> NymsphinxPayload
) -> Result<NymPayload, SurbAckRecoveryError>
where
C: StreamCipher + KeyIvInit,
{
let (_, surb_ack_bytes) = self.surb_ack.prepare_for_sending();
let (_, surb_ack_bytes) = self.surb_ack.prepare_for_sending()?;
let mut fragment_data = self.fragment.into_bytes();
stream_cipher::encrypt_in_place::<C>(
@@ -46,16 +46,20 @@ impl NymsphinxPayloadBuilder {
// where variant-specific data is as follows:
// for replies it would be the digest of the encryption key used
// for 'regular' messages it would be the public component used in DH later used in the KDF
NymsphinxPayload(
Ok(NymPayload(
surb_ack_bytes
.into_iter()
.chain(variant_data.into_iter())
.chain(fragment_data.into_iter())
.collect(),
)
))
}
pub fn build_reply(self, packet_encryption_key: &SurbEncryptionKey) -> NymsphinxPayload {
pub fn build_reply(
self,
packet_encryption_key: &SurbEncryptionKey,
) -> Result<NymPayload, SurbAckRecoveryError> {
let key_digest = packet_encryption_key.compute_digest();
self.build::<ReplySurbEncryptionAlgorithm>(
packet_encryption_key.inner(),
@@ -67,7 +71,7 @@ impl NymsphinxPayloadBuilder {
self,
rng: &mut R,
recipient_encryption_key: &encryption::PublicKey,
) -> NymsphinxPayload
) -> Result<NymPayload, SurbAckRecoveryError>
where
R: RngCore + CryptoRng,
{
@@ -88,9 +92,9 @@ impl NymsphinxPayloadBuilder {
// the actual byte data that will be put into the sphinx packet paylaod.
// no more transformations are going to happen to it
// TODO: use that fact for some better compile time assertions
pub struct NymsphinxPayload(Vec<u8>);
pub struct NymPayload(Vec<u8>);
impl AsRef<[u8]> for NymsphinxPayload {
impl AsRef<[u8]> for NymPayload {
fn as_ref(&self) -> &[u8] {
&self.0
}

Some files were not shown because too many files have changed in this diff Show More