From 5ea084d2865e8ea7c9dcb3b7902b93bd3c3f41fa Mon Sep 17 00:00:00 2001 From: Dave Hrycyszyn Date: Tue, 29 Nov 2022 16:52:24 +0000 Subject: [PATCH 01/18] Starting with Rust sdk --- Cargo.lock | 9 +++ Cargo.toml | 1 + sdk/rust/nym-sdk/Cargo.toml | 13 +++++ sdk/rust/nym-sdk/examples/complex_config.rs | 23 ++++++++ sdk/rust/nym-sdk/examples/simple.rs | 22 ++++++++ sdk/rust/nym-sdk/src/lib.rs | 1 + sdk/rust/nym-sdk/src/mixnet/mod.rs | 62 +++++++++++++++++++++ 7 files changed, 131 insertions(+) create mode 100644 sdk/rust/nym-sdk/Cargo.toml create mode 100644 sdk/rust/nym-sdk/examples/complex_config.rs create mode 100644 sdk/rust/nym-sdk/examples/simple.rs create mode 100644 sdk/rust/nym-sdk/src/lib.rs create mode 100644 sdk/rust/nym-sdk/src/mixnet/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 44c8d18421..6506f5d6da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3575,6 +3575,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-sdk" +version = "0.1.0" +dependencies = [ + "client-core", + "rand 0.7.3", + "tokio", +] + [[package]] name = "nym-socks5-client" version = "1.1.4" diff --git a/Cargo.toml b/Cargo.toml index e756fc7bf8..ebe84c2705 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ members = [ "gateway/gateway-requests", "integrations/bity", "mixnode", + "sdk/rust/nym-sdk", "service-providers/network-requester", "service-providers/network-statistics", "nym-api", diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml new file mode 100644 index 0000000000..562b127604 --- /dev/null +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nym-sdk" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +client-core = { path = "../../../clients/client-core" } +rand = { version = "0.7.3" } + +[dev-dependencies] +tokio = { version = "1", features = ["full"] } \ No newline at end of file diff --git a/sdk/rust/nym-sdk/examples/complex_config.rs b/sdk/rust/nym-sdk/examples/complex_config.rs new file mode 100644 index 0000000000..38b0086f54 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/complex_config.rs @@ -0,0 +1,23 @@ +#[tokio::main] +async fn main() { + // specify some config options + let config = mixnet::Config { + option: value, + option2: value2, + keyfiles: "~/.flappapapappa/keys/", + }; + + let client = mixnet::Client::new(&config); // passing a config allows the user to set values + + let show_receive = move || println!("got a message from the mixnet: {}", message); // might need to bury this in a struct as a `FnOnce`, see https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust + client.on_receive(show_receive); // have some way to pipe any received info to a function for processing + + // connect to the mixnet, now we're listening for incoming + client.connect_to_mixnet(); + + // be able to get our client address + println!("Our client address is {}", client.nym_address); + + // send important info up the pipe to a buddy + client.send("foo.bar@blah", "flappappa"); +} diff --git a/sdk/rust/nym-sdk/examples/simple.rs b/sdk/rust/nym-sdk/examples/simple.rs new file mode 100644 index 0000000000..d6a79889de --- /dev/null +++ b/sdk/rust/nym-sdk/examples/simple.rs @@ -0,0 +1,22 @@ +use nym_sdk::mixnet; + +#[tokio::main] +async fn main() { + // here's what I'd actually like to write + let client = mixnet::Client::new(None); // passing no config makes the client fire up an ephemeral session and figure shit out on its own + + let show_receive = move || println!("got a message from the mixnet: {}", message); // might need to bury this in a struct as a `FnMut`, see https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust + client.on_receive(show_receive); // have some way to pipe any received info to a function for processing + + // connect to the mixnet, now we're listening for incoming + client.connect_to_mixnet(); + + // be able to get our client address + println!("Our client address is {}", client.nym_address); + + // send important string info up the pipe to a buddy + client.send_str("foo.bar@blah", "flappappa"); + + // send some bytes to a buddy + client.send_bytes("foo.bar@blah", "flappappa".as_bytes().to_vec()); +} diff --git a/sdk/rust/nym-sdk/src/lib.rs b/sdk/rust/nym-sdk/src/lib.rs new file mode 100644 index 0000000000..ae7f01c0eb --- /dev/null +++ b/sdk/rust/nym-sdk/src/lib.rs @@ -0,0 +1 @@ +pub mod mixnet; diff --git a/sdk/rust/nym-sdk/src/mixnet/mod.rs b/sdk/rust/nym-sdk/src/mixnet/mod.rs new file mode 100644 index 0000000000..c37c4f1995 --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/mod.rs @@ -0,0 +1,62 @@ +use std::path::PathBuf; + +pub struct Client { + pub nym_address: String, + config: Config, +} + +impl Client { + /// Create a new mixnet client. If no config options are supplied, creates a new client with ephemeral keys + /// stored in RAM, which will be discarded at application close. + /// + /// Callers have the option of supplying futher parameters to store persistent identities at a location on-disk, + /// if desired. + pub fn new(config_option: Option) -> Client { + if config_option.is_none() { + let config = Self::some_minimal_config(); + let nym_address = Self::new_nym_address(); + Client { + config, + nym_address, + } + } else { + let self_address = Self::new_nym_address(); + let nym_address = Self::new_nym_address(); + + Client { + config: config_option.unwrap(), + nym_address, + } + } + } + + /// Connects to the mixnet via the gateway in the client config + pub fn connect_to_mixnet(&self) {} + + /// Sets the callback function which is run when the client receives a message + /// from the mixnet + pub fn on_receive(&mut self, message: &str) { + println!("Message received: {}", message); + } + + /// Sends stringy data to the supplied Nym address + pub fn send_str(&self, address: &str, message: &str) {} + + /// Sends bytes to the supplied Nym address + pub fn send_bytes(&self, address: &str, message: Vec) {} + + fn new_nym_address() -> String { + "the.nym@address".to_string() + } + + fn some_minimal_config() -> Config { + let mut keys_path = PathBuf::new(); + Config { + keys: Some(keys_path), + } + } +} + +pub struct Config { + keys: Option, +} From 6c857b5dafd0a9684259d17d58669671959beb1a Mon Sep 17 00:00:00 2001 From: Dave Hrycyszyn Date: Mon, 5 Dec 2022 16:41:55 +0000 Subject: [PATCH 02/18] wip --- sdk/rust/nym-sdk/examples/complex_config.rs | 19 ++++++++++--------- sdk/rust/nym-sdk/examples/simple.rs | 6 +++--- sdk/rust/nym-sdk/src/mixnet/mod.rs | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/sdk/rust/nym-sdk/examples/complex_config.rs b/sdk/rust/nym-sdk/examples/complex_config.rs index 38b0086f54..c34038162e 100644 --- a/sdk/rust/nym-sdk/examples/complex_config.rs +++ b/sdk/rust/nym-sdk/examples/complex_config.rs @@ -1,16 +1,17 @@ +use std::path::PathBuf; + +use nym_sdk::mixnet; + #[tokio::main] async fn main() { // specify some config options - let config = mixnet::Config { - option: value, - option2: value2, - keyfiles: "~/.flappapapappa/keys/", - }; + let keys = Some(PathBuf::from("~/.nym/clients/superfoomp")); + let config = mixnet::Config { keys }; - let client = mixnet::Client::new(&config); // passing a config allows the user to set values + let client = mixnet::Client::new(Some(config)); // passing a config allows the user to set values - let show_receive = move || println!("got a message from the mixnet: {}", message); // might need to bury this in a struct as a `FnOnce`, see https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust - client.on_receive(show_receive); // have some way to pipe any received info to a function for processing + // let show_receive = move || println!("got a message from the mixnet: {}", message); // might need to bury this in a struct as a `FnOnce`, see https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust + // client.on_receive(show_receive); // have some way to pipe any received info to a function for processing // connect to the mixnet, now we're listening for incoming client.connect_to_mixnet(); @@ -19,5 +20,5 @@ async fn main() { println!("Our client address is {}", client.nym_address); // send important info up the pipe to a buddy - client.send("foo.bar@blah", "flappappa"); + client.send_str("foo.bar@blah", "flappappa"); } diff --git a/sdk/rust/nym-sdk/examples/simple.rs b/sdk/rust/nym-sdk/examples/simple.rs index d6a79889de..2ff8dae9c8 100644 --- a/sdk/rust/nym-sdk/examples/simple.rs +++ b/sdk/rust/nym-sdk/examples/simple.rs @@ -5,9 +5,7 @@ async fn main() { // here's what I'd actually like to write let client = mixnet::Client::new(None); // passing no config makes the client fire up an ephemeral session and figure shit out on its own - let show_receive = move || println!("got a message from the mixnet: {}", message); // might need to bury this in a struct as a `FnMut`, see https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust - client.on_receive(show_receive); // have some way to pipe any received info to a function for processing - + // let show_receive = move || println!("got a message from the mixnet: {}", message); // might need to bury this in a struct as a `FnMut`, see https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust // connect to the mixnet, now we're listening for incoming client.connect_to_mixnet(); @@ -19,4 +17,6 @@ async fn main() { // send some bytes to a buddy client.send_bytes("foo.bar@blah", "flappappa".as_bytes().to_vec()); + + } diff --git a/sdk/rust/nym-sdk/src/mixnet/mod.rs b/sdk/rust/nym-sdk/src/mixnet/mod.rs index c37c4f1995..46ea1aadde 100644 --- a/sdk/rust/nym-sdk/src/mixnet/mod.rs +++ b/sdk/rust/nym-sdk/src/mixnet/mod.rs @@ -58,5 +58,5 @@ impl Client { } pub struct Config { - keys: Option, + pub keys: Option, } From 9b9c01fb8f5a777ff614b3140f259ca10fa2a411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 20 Dec 2022 00:48:00 +0100 Subject: [PATCH 03/18] rust-sdk: initial version --- Cargo.lock | 71 ++-- .../client-core/src/client/base_client/mod.rs | 35 +- .../client/base_client/non_wasm_helpers.rs | 9 +- clients/client-core/src/client/key_manager.rs | 74 ++++- .../action_controller.rs | 19 +- .../replies/reply_controller/requests.rs | 18 ++ .../reply_storage/backend/browser_backend.rs | 1 + .../replies/reply_storage/backend/mod.rs | 9 +- .../src/config/persistence/key_pathfinder.rs | 41 ++- clients/client-core/src/error.rs | 3 + clients/client-core/src/init/helpers.rs | 15 +- clients/client-core/src/init/mod.rs | 76 ++++- clients/native/src/client/mod.rs | 14 +- clients/socks5/src/client/mod.rs | 11 +- common/task/src/manager.rs | 6 +- sdk/rust/nym-sdk/Cargo.toml | 20 +- sdk/rust/nym-sdk/examples-common/Cargo.toml | 10 + sdk/rust/nym-sdk/examples-common/src/lib.rs | 21 ++ sdk/rust/nym-sdk/examples/complex_config.rs | 40 ++- sdk/rust/nym-sdk/examples/complex_config2.rs | 45 +++ sdk/rust/nym-sdk/examples/simple.rs | 29 +- sdk/rust/nym-sdk/src/error.rs | 10 + sdk/rust/nym-sdk/src/lib.rs | 1 + sdk/rust/nym-sdk/src/mixnet.rs | 15 + sdk/rust/nym-sdk/src/mixnet/client.rs | 306 ++++++++++++++++++ sdk/rust/nym-sdk/src/mixnet/config.rs | 36 +++ .../nym-sdk/src/mixnet/connection_state.rs | 115 +++++++ sdk/rust/nym-sdk/src/mixnet/key_paths.rs | 142 ++++++++ sdk/rust/nym-sdk/src/mixnet/mod.rs | 62 ---- 29 files changed, 1077 insertions(+), 177 deletions(-) create mode 100644 sdk/rust/nym-sdk/examples-common/Cargo.toml create mode 100644 sdk/rust/nym-sdk/examples-common/src/lib.rs create mode 100644 sdk/rust/nym-sdk/examples/complex_config2.rs create mode 100644 sdk/rust/nym-sdk/src/error.rs create mode 100644 sdk/rust/nym-sdk/src/mixnet.rs create mode 100644 sdk/rust/nym-sdk/src/mixnet/client.rs create mode 100644 sdk/rust/nym-sdk/src/mixnet/config.rs create mode 100644 sdk/rust/nym-sdk/src/mixnet/connection_state.rs create mode 100644 sdk/rust/nym-sdk/src/mixnet/key_paths.rs delete mode 100644 sdk/rust/nym-sdk/src/mixnet/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 6506f5d6da..053b8f1790 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1778,6 +1778,14 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "examples-common" +version = "0.1.0" +dependencies = [ + "log", + "pretty_env_logger", +] + [[package]] name = "execute" version = "0.1.0" @@ -1970,9 +1978,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -1985,9 +1993,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -1995,15 +2003,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -2023,15 +2031,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -2040,21 +2048,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -3579,9 +3587,24 @@ dependencies = [ name = "nym-sdk" version = "0.1.0" dependencies = [ + "client-connections", "client-core", + "crypto", + "examples-common", + "futures", + "gateway-client", + "gateway-requests", + "log", + "network-defaults", + "nymsphinx", + "pretty_env_logger", "rand 0.7.3", + "tap", + "task", + "thiserror", "tokio", + "toml", + "url", ] [[package]] @@ -5986,18 +6009,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -6217,9 +6240,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" dependencies = [ "serde", ] diff --git a/clients/client-core/src/client/base_client/mod.rs b/clients/client-core/src/client/base_client/mod.rs index 4757c3bf2c..196ae058d8 100644 --- a/clients/client-core/src/client/base_client/mod.rs +++ b/clients/client-core/src/client/base_client/mod.rs @@ -33,12 +33,15 @@ use log::{debug, info}; use nymsphinx::acknowledgements::AckKey; use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::nodes::NodeIdentity; +use nymsphinx::receiver::ReconstructedMessage; use std::sync::Arc; use std::time::Duration; use tap::TapFallible; use task::{TaskClient, TaskManager}; use url::Url; +use super::received_buffer::ReceivedBufferMessage; + #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] pub mod non_wasm_helpers; @@ -48,10 +51,30 @@ pub struct ClientInput { } pub struct ClientOutput { - pub shared_lane_queue_lengths: LaneQueueLengths, pub received_buffer_request_sender: ReceivedBufferRequestSender, } +impl ClientOutput { + pub fn register_receiver( + &mut self, + ) -> Result>, ClientCoreError> { + let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded(); + + self.received_buffer_request_sender + .unbounded_send(ReceivedBufferMessage::ReceiverAnnounce( + reconstructed_sender, + )) + .map_err(|_| ClientCoreError::FailedToRegisterReceiver)?; + + Ok(reconstructed_receiver) + } +} + +pub struct ClientState { + pub shared_lane_queue_lengths: LaneQueueLengths, + pub reply_controller_sender: ReplyControllerSender, +} + pub enum ClientInputStatus { AwaitingProducer { client_input: ClientInput }, Connected, @@ -475,11 +498,13 @@ where }, client_output: ClientOutputStatus::AwaitingConsumer { client_output: ClientOutput { - shared_lane_queue_lengths, received_buffer_request_sender, }, }, - reply_controller_sender, + client_state: ClientState { + shared_lane_queue_lengths, + reply_controller_sender, + }, task_manager, }) } @@ -488,9 +513,7 @@ where pub struct BaseClient { pub client_input: ClientInputStatus, pub client_output: ClientOutputStatus, - - // it feels very wrong to put this channel here, but I can't think of any other way of passing it to the native client - pub reply_controller_sender: ReplyControllerSender, + pub client_state: ClientState, pub task_manager: TaskManager, } diff --git a/clients/client-core/src/client/base_client/non_wasm_helpers.rs b/clients/client-core/src/client/base_client/non_wasm_helpers.rs index 996dcc8fc3..6ed6116e63 100644 --- a/clients/client-core/src/client/base_client/non_wasm_helpers.rs +++ b/clients/client-core/src/client/base_client/non_wasm_helpers.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::replies::reply_storage::{ - fs_backend, CombinedReplyStorage, ReplyStorageBackend, + browser_backend, fs_backend, CombinedReplyStorage, ReplyStorageBackend, }; use crate::config::DebugConfig; use crate::error::ClientCoreError; @@ -85,3 +85,10 @@ pub async fn setup_fs_reply_surb_backend>( setup_fresh_backend(db_path, debug_config).await } } + +pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> browser_backend::Backend { + browser_backend::Backend::new( + debug_config.minimum_reply_surb_storage_threshold, + debug_config.maximum_reply_surb_storage_threshold, + ) +} diff --git a/clients/client-core/src/client/key_manager.rs b/clients/client-core/src/client/key_manager.rs index 6fa264f43f..4b6647ecbc 100644 --- a/clients/client-core/src/client/key_manager.rs +++ b/clients/client-core/src/client/key_manager.rs @@ -17,6 +17,8 @@ use std::sync::Arc; // use the old key after new one was issued. // Remember that Arc has Deref implementation for T +// WIP(JON): let's try not to have the clone +#[derive(Clone)] pub struct KeyManager { /// identity key associated with the client instance. identity_keypair: Arc, @@ -57,6 +59,20 @@ impl KeyManager { } } + pub fn new_from_keys( + id_keypair: identity::KeyPair, + enc_keypair: encryption::KeyPair, + gateway_shared_key: SharedKeys, + ack_key: AckKey, + ) -> Self { + Self { + identity_keypair: Arc::new(id_keypair), + encryption_keypair: Arc::new(enc_keypair), + gateway_shared_key: Some(Arc::new(gateway_shared_key)), + ack_key: Arc::new(ack_key), + } + } + // this is actually **NOT** dead code // I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute #[allow(dead_code)] @@ -65,8 +81,8 @@ impl KeyManager { self.gateway_shared_key = Some(gateway_shared_key) } - /// Loads previously stored keys from the disk. - pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result { + /// Loads previously stored client keys from the disk. + fn load_keys_client_only(client_pathfinder: &ClientKeyPathfinder) -> io::Result { let identity_keypair: identity::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new( client_pathfinder.private_identity_key().to_owned(), @@ -78,21 +94,47 @@ impl KeyManager { client_pathfinder.public_encryption_key().to_owned(), ))?; - let gateway_shared_key: SharedKeys = - pemstore::load_key(client_pathfinder.gateway_shared_key())?; - let ack_key: AckKey = pemstore::load_key(client_pathfinder.ack_key())?; - // TODO: ack key is never stored so it is generated now. But perhaps it should be stored - // after all for consistency sake? Ok(KeyManager { identity_keypair: Arc::new(identity_keypair), encryption_keypair: Arc::new(encryption_keypair), - gateway_shared_key: Some(Arc::new(gateway_shared_key)), + gateway_shared_key: None, ack_key: Arc::new(ack_key), }) } + /// Loads previously stored keys from the disk. Fails if not all, including the shared gateway + /// key, is available. + pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result { + let mut key_manager = Self::load_keys_client_only(client_pathfinder)?; + + let gateway_shared_key: SharedKeys = + pemstore::load_key(client_pathfinder.gateway_shared_key())?; + + key_manager.gateway_shared_key = Some(Arc::new(gateway_shared_key)); + + Ok(key_manager) + } + + pub fn load_keys_maybe_gateway(client_pathfinder: &ClientKeyPathfinder) -> io::Result { + let mut key_manager = Self::load_keys_client_only(client_pathfinder)?; + + let gateway_shared_key: Result = + pemstore::load_key(client_pathfinder.gateway_shared_key()); + + // It's ok if the gateway key was not found + let gateway_shared_key = match gateway_shared_key { + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(err), + Ok(key) => Ok(Some(key)), + }?; + + key_manager.gateway_shared_key = gateway_shared_key.map(Arc::new); + + Ok(key_manager) + } + // this is actually **NOT** dead code // I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute #[allow(dead_code)] @@ -119,7 +161,21 @@ impl KeyManager { pemstore::store_key(self.ack_key.as_ref(), client_pathfinder.ack_key())?; match self.gateway_shared_key.as_ref() { - None => warn!("No gateway shared key available to store!"), + None => debug!("No gateway shared key available to store!"), + Some(gate_key) => { + pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())? + } + } + + Ok(()) + } + + pub fn store_key_gateway_only( + &self, + client_pathfinder: &ClientKeyPathfinder, + ) -> io::Result<()> { + match self.gateway_shared_key.as_ref() { + None => panic!("WIP(JON)"), Some(gate_key) => { pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())? } diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index c65592b2f6..faa250aeac 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -213,7 +213,11 @@ impl ActionController { } // note: when the entry expires it's automatically removed from pending_acks_timers - fn handle_expired_ack_timer(&mut self, expired_ack: Expired) { + fn handle_expired_ack_timer( + &mut self, + expired_ack: Expired, + task_client: &mut task::TaskClient, + ) { // I'm honestly not sure how to handle it, because getting it means other things in our // system are already misbehaving. If we ever see this panic, then I guess we should worry // about it. Perhaps just reschedule it at later point? @@ -231,9 +235,16 @@ impl ActionController { // downgrading an arc and then upgrading vs cloning is difference of 30ns vs 15ns // so it's literally a NO difference while it might prevent us from unnecessarily // resending data (in maybe 1 in 1 million cases, but it's something) - self.retransmission_sender + if self + .retransmission_sender .unbounded_send(Arc::downgrade(pending_ack_data)) - .unwrap() + .is_err() + { + assert!( + task_client.is_shutdown_poll(), + "Failed to send pending ack for retransmission" + ); + } } else { // this shouldn't cause any issues but shouldn't have happened to begin with! error!("An already removed pending ack has expired") @@ -264,7 +275,7 @@ impl ActionController { } }, expired_ack = self.pending_acks_timers.next() => match expired_ack { - Some(expired_ack) => self.handle_expired_ack_timer(expired_ack), + Some(expired_ack) => self.handle_expired_ack_timer(expired_ack, &mut shutdown), None => { log::trace!("ActionController: Stopping since ack channel closed"); break; diff --git a/clients/client-core/src/client/replies/reply_controller/requests.rs b/clients/client-core/src/client/replies/reply_controller/requests.rs index 1f47f2ca07..6b910ebde2 100644 --- a/clients/client-core/src/client/replies/reply_controller/requests.rs +++ b/clients/client-core/src/client/replies/reply_controller/requests.rs @@ -99,6 +99,24 @@ impl ReplyControllerSender { } } +pub struct ReplyQueueLengths { + reply_controller_sender: ReplyControllerSender, +} + +impl ReplyQueueLengths { + pub fn new(reply_controller_sender: ReplyControllerSender) -> Self { + Self { + reply_controller_sender, + } + } + + pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize { + self.reply_controller_sender + .get_lane_queue_length(connection_id) + .await + } +} + pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver; #[derive(Debug)] diff --git a/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs b/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs index 9711ea759f..ff2da4ab72 100644 --- a/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs @@ -7,6 +7,7 @@ use async_trait::async_trait; // well, right now we don't have the browser storage : ( // so we keep everything in memory +#[derive(Debug)] pub struct Backend { empty: Empty, } diff --git a/clients/client-core/src/client/replies/reply_storage/backend/mod.rs b/clients/client-core/src/client/replies/reply_storage/backend/mod.rs index d80231ae1e..0920b37657 100644 --- a/clients/client-core/src/client/replies/reply_storage/backend/mod.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/mod.rs @@ -6,10 +6,12 @@ use async_trait::async_trait; use std::error::Error; use thiserror::Error; -#[cfg(target_arch = "wasm32")] +//#[cfg(target_arch = "wasm32")] +//#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] pub mod browser_backend; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] +//#[cfg(target_arch = "wasm32")] pub mod fs_backend; // #[cfg(all(test, feature = "std"))] @@ -19,10 +21,11 @@ pub mod fs_backend; #[error("no information provided")] pub struct UndefinedError; +#[derive(Debug)] pub struct Empty { // we need to keep 'basic' metadata here to "load" the CombinedReplyStorage - min_surb_threshold: usize, - max_surb_threshold: usize, + pub min_surb_threshold: usize, + pub max_surb_threshold: usize, } #[async_trait] diff --git a/clients/client-core/src/config/persistence/key_pathfinder.rs b/clients/client-core/src/config/persistence/key_pathfinder.rs index d5dda75a27..90f00eb730 100644 --- a/clients/client-core/src/config/persistence/key_pathfinder.rs +++ b/clients/client-core/src/config/persistence/key_pathfinder.rs @@ -7,12 +7,12 @@ use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct ClientKeyPathfinder { - identity_private_key: PathBuf, - identity_public_key: PathBuf, - encryption_private_key: PathBuf, - encryption_public_key: PathBuf, - gateway_shared_key: PathBuf, - ack_key: PathBuf, + pub identity_private_key: PathBuf, + pub identity_public_key: PathBuf, + pub encryption_private_key: PathBuf, + pub encryption_public_key: PathBuf, + pub gateway_shared_key: PathBuf, + pub ack_key: PathBuf, } impl ClientKeyPathfinder { @@ -22,13 +22,25 @@ impl ClientKeyPathfinder { ClientKeyPathfinder { identity_private_key: config_dir.join("private_identity.pem"), identity_public_key: config_dir.join("public_identity.pem"), - encryption_private_key: config_dir.join("public_encryption.pem"), - encryption_public_key: config_dir.join("private_encryption.pem"), + encryption_private_key: config_dir.join("private_encryption.pem"), + encryption_public_key: config_dir.join("public_encryption.pem"), gateway_shared_key: config_dir.join("gateway_shared.pem"), ack_key: config_dir.join("ack_key.pem"), } } + //pub fn new_from_dir(config_dir: &PathBuf) -> Self { + // dbg!(&config_dir); + // ClientKeyPathfinder { + // identity_private_key: config_dir.join("private_identity.pem"), + // identity_public_key: config_dir.join("public_identity.pem"), + // encryption_private_key: config_dir.join("public_encryption.pem"), + // encryption_public_key: config_dir.join("private_encryption.pem"), + // gateway_shared_key: config_dir.join("gateway_shared.pem"), + // ack_key: config_dir.join("ack_key.pem"), + // } + //} + pub fn new_from_config(config: &Config) -> Self { ClientKeyPathfinder { identity_private_key: config.get_private_identity_key_file(), @@ -40,6 +52,19 @@ impl ClientKeyPathfinder { } } + pub fn any_file_exists(&self) -> bool { + matches!(self.identity_public_key.try_exists(), Ok(true)) + || matches!(self.identity_private_key.try_exists(), Ok(true)) + || matches!(self.encryption_public_key.try_exists(), Ok(true)) + || matches!(self.encryption_private_key.try_exists(), Ok(true)) + || matches!(self.gateway_shared_key.try_exists(), Ok(true)) + || matches!(self.ack_key.try_exists(), Ok(true)) + } + + pub fn gateway_key_file_exists(&self) -> bool { + matches!(self.gateway_shared_key.try_exists(), Ok(true)) + } + pub fn private_identity_key(&self) -> &Path { &self.identity_private_key } diff --git a/clients/client-core/src/error.rs b/clients/client-core/src/error.rs index 07bd9442ec..6162a5d9be 100644 --- a/clients/client-core/src/error.rs +++ b/clients/client-core/src/error.rs @@ -55,6 +55,9 @@ pub enum ClientCoreError { #[error("The address of the gateway is unknown - did you run init?")] GatwayAddressUnknown, + #[error("failed to register receiver for reconstructed mixnet messages")] + FailedToRegisterReceiver, + #[error("Unexpected exit")] UnexpectedExit, } diff --git a/clients/client-core/src/init/helpers.rs b/clients/client-core/src/init/helpers.rs index b3662149b7..2d078a4751 100644 --- a/clients/client-core/src/init/helpers.rs +++ b/clients/client-core/src/init/helpers.rs @@ -10,7 +10,7 @@ use config::NymConfig; use crypto::asymmetric::identity; use gateway_client::GatewayClient; use gateway_requests::registration::handshake::SharedKeys; -use rand::{rngs::OsRng, seq::SliceRandom, thread_rng}; +use rand::{seq::SliceRandom, thread_rng}; use std::{sync::Arc, time::Duration}; use tap::TapFallible; use topology::{filter::VersionFilterable, gateway}; @@ -51,7 +51,7 @@ pub(super) async fn query_gateway_details( } } -async fn register_with_gateway( +pub(super) async fn register_with_gateway( gateway: &gateway::Node, our_identity: Arc, ) -> Result, ClientCoreError> { @@ -74,20 +74,13 @@ async fn register_with_gateway( Ok(shared_keys) } -pub(super) async fn register_with_gateway_and_store_keys( - gateway_details: gateway::Node, +pub(super) fn store_keys( + key_manager: &KeyManager, config: &Config, ) -> Result<(), ClientCoreError> where T: NymConfig, { - let mut rng = OsRng; - let mut key_manager = KeyManager::new(&mut rng); - - let shared_keys = - register_with_gateway(&gateway_details, key_manager.identity_keypair()).await?; - key_manager.insert_gateway_shared_key(shared_keys); - let pathfinder = ClientKeyPathfinder::new_from_config(config); Ok(key_manager .store_keys(&pathfinder) diff --git a/clients/client-core/src/init/mod.rs b/clients/client-core/src/init/mod.rs index 4d012d366b..f02d228242 100644 --- a/clients/client-core/src/init/mod.rs +++ b/clients/client-core/src/init/mod.rs @@ -6,23 +6,26 @@ use std::fmt::Display; use nymsphinx::addressing::{clients::Recipient, nodes::NodeIdentity}; +use rand::rngs::OsRng; use serde::Serialize; use tap::TapFallible; use config::NymConfig; use crypto::asymmetric::{encryption, identity}; +use url::Url; +use crate::client::key_manager::KeyManager; use crate::{ config::{ persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config, GatewayEndpointConfig, }, error::ClientCoreError, - init::helpers::{query_gateway_details, register_with_gateway_and_store_keys}, }; mod helpers; +/// Struct describing the results of the client initialization procedure. #[derive(Debug, Serialize)] pub struct InitResults { version: String, @@ -60,6 +63,12 @@ impl Display for InitResults { } } +/// Create a new set of client keys. +pub fn new_client_keys() -> KeyManager { + let mut rng = OsRng; + KeyManager::new(&mut rng) +} + /// Convenience function for setting up the gateway for a client. Depending on the arguments given /// it will do the sensible thing. pub async fn setup_gateway( @@ -74,7 +83,7 @@ where { let id = config.get_id(); if register_gateway { - register_with_gateway(user_chosen_gateway_id, config).await + register_with_gateway_and_store(user_chosen_gateway_id, config).await } else if let Some(user_chosen_gateway_id) = user_chosen_gateway_id { config_gateway_with_existing_keys(user_chosen_gateway_id, config).await } else { @@ -82,27 +91,51 @@ where } } +/// Get the gateway details by querying the validator-api. Either pick one at random or use +/// the chosen one if it's among the available ones. +pub async fn register_with_gateway( + key_manager: &mut KeyManager, + nym_api_endpoints: Vec, + chosen_gateway_id: Option, +) -> Result { + // Our identity is derived from our key + let our_identity = key_manager.identity_keypair(); + + // Get the gateway details of the gateway we will use + let gateway = helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id).await?; + log::debug!("Querying gateway gives: {}", gateway); + + // Establish connection, authenticate and generate keys for talking with the gateway + let shared_keys = helpers::register_with_gateway(&gateway, our_identity).await?; + key_manager.insert_gateway_shared_key(shared_keys); + + Ok(gateway.into()) +} + /// Get the gateway details by querying the validator-api. Either pick one at random or use /// the chosen one if it's among the available ones. /// Saves keys to disk, specified by the paths in `config`. -pub async fn register_with_gateway( - user_chosen_gateway_id: Option, +pub async fn register_with_gateway_and_store( + chosen_gateway_id: Option, config: &Config, ) -> Result where T: NymConfig, { println!("Configuring gateway"); - let gateway = - query_gateway_details(config.get_nym_api_endpoints(), user_chosen_gateway_id).await?; - log::debug!("Querying gateway gives: {}", gateway); + let mut key_manager = new_client_keys(); - // Registering with gateway by setting up and writing shared keys to disk - log::trace!("Registering gateway"); - register_with_gateway_and_store_keys(gateway.clone(), config).await?; + let gateway = register_with_gateway( + &mut key_manager, + config.get_nym_api_endpoints(), + chosen_gateway_id, + ) + .await?; + + helpers::store_keys(&key_manager, config)?; println!("Saved all generated keys"); - Ok(gateway.into()) + Ok(gateway) } /// Set the gateway using the usual procedue of querying the validator-api, but don't register or @@ -117,8 +150,11 @@ where T: NymConfig, { println!("Using gateway provided by user, keeping existing keys"); - let gateway = - query_gateway_details(config.get_nym_api_endpoints(), Some(user_chosen_gateway_id)).await?; + let gateway = helpers::query_gateway_details( + config.get_nym_api_endpoints(), + Some(user_chosen_gateway_id), + ) + .await?; log::debug!("Querying gateway gives: {}", gateway); Ok(gateway.into()) } @@ -143,6 +179,20 @@ where }) } +/// Get the full client address from the client keys and the gateway identity +pub fn get_client_address( + key_manager: &KeyManager, + gateway_config: &GatewayEndpointConfig, +) -> Recipient { + Recipient::new( + *key_manager.identity_keypair().public_key(), + *key_manager.encryption_keypair().public_key(), + // TODO: below only works under assumption that gateway address == gateway id + // (which currently is true) + NodeIdentity::from_base58_string(&gateway_config.gateway_id).unwrap(), + ) +} + /// Get the client address by loading the keys from stored files. pub fn get_client_address_from_stored_keys( config: &Config, diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index ca367018e8..1ba6e2a70d 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -8,12 +8,11 @@ use crate::error::ClientError; use crate::websocket; use client_connections::TransmissionLane; use client_core::client::base_client::{ - non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, + non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState, }; use client_core::client::inbound_messages::InputMessage; use client_core::client::key_manager::KeyManager; use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver}; -use client_core::client::replies::reply_controller::requests::ReplyControllerSender; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use futures::channel::mpsc; use gateway_client::bandwidth::BandwidthController; @@ -87,8 +86,8 @@ impl SocketClient { config: &Config, client_input: ClientInput, client_output: ClientOutput, + client_state: ClientState, self_address: &Recipient, - reply_controller_sender: ReplyControllerSender, shutdown: task::TaskClient, ) { info!("Starting websocket listener..."); @@ -99,10 +98,14 @@ impl SocketClient { } = client_input; let ClientOutput { - shared_lane_queue_lengths, received_buffer_request_sender, } = client_output; + let ClientState { + shared_lane_queue_lengths, + reply_controller_sender, + } = client_state; + let websocket_handler = websocket::HandlerBuilder::new( input_sender, connection_command_sender, @@ -151,13 +154,14 @@ impl SocketClient { let mut started_client = base_builder.start_base().await?; let client_input = started_client.client_input.register_producer(); let client_output = started_client.client_output.register_consumer(); + let client_state = started_client.client_state; Self::start_websocket_listener( &self.config, client_input, client_output, + client_state, &self_address, - started_client.reply_controller_sender, started_client.task_manager.subscribe(), ); diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index c7710056f8..1f22fff717 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -9,7 +9,7 @@ use crate::socks::{ server::SphinxSocksServer, }; use client_core::client::base_client::{ - non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, + non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState, }; use client_core::client::key_manager::KeyManager; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; @@ -95,6 +95,7 @@ impl NymClient { config: &Config, client_input: ClientInput, client_output: ClientOutput, + client_status: ClientState, self_address: Recipient, shutdown: TaskClient, ) { @@ -108,10 +109,14 @@ impl NymClient { } = client_input; let ClientOutput { - shared_lane_queue_lengths, received_buffer_request_sender, } = client_output; + let ClientState { + shared_lane_queue_lengths, + reply_controller_sender: _, + } = client_status; + let authenticator = Authenticator::new(auth_methods, allowed_users); let mut sphinx_socks = SphinxSocksServer::new( config.get_listening_port(), @@ -218,11 +223,13 @@ impl NymClient { let mut started_client = base_builder.start_base().await?; let client_input = started_client.client_input.register_producer(); let client_output = started_client.client_output.register_consumer(); + let client_state = started_client.client_state; Self::start_socks5_listener( &self.config, client_input, client_output, + client_state, self_address, started_client.task_manager.subscribe(), ); diff --git a/common/task/src/manager.rs b/common/task/src/manager.rs index f40a525527..7c8ab6586e 100644 --- a/common/task/src/manager.rs +++ b/common/task/src/manager.rs @@ -208,7 +208,7 @@ pub struct TaskClient { impl TaskClient { #[cfg(not(target_arch = "wasm32"))] - const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); + const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(1); fn new( notify: watch::Receiver<()>, @@ -300,8 +300,8 @@ impl TaskClient { has_changed } Err(err) => { - log::debug!("Polling shutdown failed: {err}"); - log::debug!("Assuming this means we should shutdown..."); + log::error!("Polling shutdown failed: {err}"); + log::error!("Assuming this means we should shutdown..."); true } } diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index 562b127604..314bf9e711 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -6,8 +6,24 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -client-core = { path = "../../../clients/client-core" } +client-connections = { path = "../../../common/client-connections" } +client-core = { path = "../../../clients/client-core", features = ["fs-surb-storage"]} +crypto = { path = "../../../common/crypto" } +gateway-client = { path = "../../../common/client-libs/gateway-client" } +gateway-requests = { path = "../../../gateway/gateway-requests" } +network-defaults = { path = "../../../common/network-defaults" } +nymsphinx = { path = "../../../common/nymsphinx" } +task = { path = "../../../common/task" } + +futures = "0.3" +log = "0.4" rand = { version = "0.7.3" } +tap = "1.0.1" +thiserror = "1.0.38" +url = "2.2" +toml = "0.5.10" [dev-dependencies] -tokio = { version = "1", features = ["full"] } \ No newline at end of file +pretty_env_logger = "0.4.0" +tokio = { version = "1", features = ["full"] } +examples-common = { path = "examples-common" } diff --git a/sdk/rust/nym-sdk/examples-common/Cargo.toml b/sdk/rust/nym-sdk/examples-common/Cargo.toml new file mode 100644 index 0000000000..338b766a8f --- /dev/null +++ b/sdk/rust/nym-sdk/examples-common/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "examples-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.17" +pretty_env_logger = "0.4.0" diff --git a/sdk/rust/nym-sdk/examples-common/src/lib.rs b/sdk/rust/nym-sdk/examples-common/src/lib.rs new file mode 100644 index 0000000000..7aa0420987 --- /dev/null +++ b/sdk/rust/nym-sdk/examples-common/src/lib.rs @@ -0,0 +1,21 @@ +pub fn setup_logging() { + let mut log_builder = pretty_env_logger::formatted_timed_builder(); + if let Ok(s) = ::std::env::var("RUST_LOG") { + log_builder.parse_filters(&s); + } else { + // default to 'Info' + log_builder.filter(None, log::LevelFilter::Info); + } + + log_builder + .filter_module("hyper", log::LevelFilter::Warn) + .filter_module("tokio_reactor", log::LevelFilter::Warn) + .filter_module("reqwest", log::LevelFilter::Warn) + .filter_module("mio", log::LevelFilter::Warn) + .filter_module("want", log::LevelFilter::Warn) + .filter_module("tungstenite", log::LevelFilter::Warn) + .filter_module("tokio_tungstenite", log::LevelFilter::Warn) + .filter_module("handlebars", log::LevelFilter::Warn) + .filter_module("sled", log::LevelFilter::Warn) + .init(); +} diff --git a/sdk/rust/nym-sdk/examples/complex_config.rs b/sdk/rust/nym-sdk/examples/complex_config.rs index c34038162e..757c95c8a0 100644 --- a/sdk/rust/nym-sdk/examples/complex_config.rs +++ b/sdk/rust/nym-sdk/examples/complex_config.rs @@ -1,24 +1,40 @@ use std::path::PathBuf; +use examples_common::setup_logging; use nym_sdk::mixnet; #[tokio::main] async fn main() { - // specify some config options - let keys = Some(PathBuf::from("~/.nym/clients/superfoomp")); - let config = mixnet::Config { keys }; + setup_logging(); - let client = mixnet::Client::new(Some(config)); // passing a config allows the user to set values + // Specify some config options + let config_dir = PathBuf::from("/tmp/mixnet-client"); - // let show_receive = move || println!("got a message from the mixnet: {}", message); // might need to bury this in a struct as a `FnOnce`, see https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust - // client.on_receive(show_receive); // have some way to pipe any received info to a function for processing + // Setting `KeyMode::Keep` will use existing keys, and existing config, if there is one. + // Regardles of `user_chosen_gateway`. + let keys = mixnet::KeyPaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir); - // connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet(); + // Provide key paths for the client to read/write keys to. + let mut client = mixnet::Client::new(None, Some(keys)).unwrap(); - // be able to get our client address - println!("Our client address is {}", client.nym_address); + // Connect to the mixnet, now we're listening for incoming + client.connect_to_mixnet().await; - // send important info up the pipe to a buddy - client.send_str("foo.bar@blah", "flappappa"); + // Be able to get our client address + let our_address = client.nym_address().unwrap(); + println!("Our client nym address is: {our_address}"); + + // Send a message throught the mixnet to ourselves + client + .send_str(&our_address.to_string(), "hello there") + .await; + + println!("Waiting for message"); + if let Some(received) = client.wait_for_messages().await { + for r in received { + println!("Received: {}", String::from_utf8_lossy(&r.message)); + } + } + + client.disconnect().await; } diff --git a/sdk/rust/nym-sdk/examples/complex_config2.rs b/sdk/rust/nym-sdk/examples/complex_config2.rs new file mode 100644 index 0000000000..413722769a --- /dev/null +++ b/sdk/rust/nym-sdk/examples/complex_config2.rs @@ -0,0 +1,45 @@ +use examples_common::setup_logging; +use nym_sdk::mixnet; + +#[tokio::main] +async fn main() { + setup_logging(); + + // We can set a few options + let user_chosen_gateway_id = None; + let nym_api_endpointgs = vec![]; + let config = mixnet::Config::new(user_chosen_gateway_id, nym_api_endpointgs); + + let mut client = mixnet::Client::new(Some(config), None).unwrap(); + + // In this we want to provide our own gateway config struct, and handle persisting this info to disk + // ourselves (e.g., as part of our own configuration file). + // NOTE: gateway shared key is written to disk according to the path given earlier + // Checks if we have a shared gateway key loaded + let first_run = true; + if first_run { + client.register_with_gateway().await; + write_to_storage(client.get_keys(), client.get_gateway_endpoint().unwrap()); + } else { + let (keys, gateway_config) = read_from_storage(); + client.set_keys(&keys); + client.set_gateway_endpoint(&gateway_config); + } + + // Connect to the mixnet, now we're listening for incoming + client.connect_to_mixnet().await; + + // Be able to get our client address + println!("Our client address is {}", client.nym_address().unwrap()); + + // Send important info up the pipe to a buddy + client.send_str("foo.bar@blah", "flappappa").await; +} + +fn write_to_storage(_keys: &mixnet::Keys, _gateway_config: &mixnet::GatewayEndpointConfig) { + todo!(); +} + +fn read_from_storage() -> (mixnet::Keys, mixnet::GatewayEndpointConfig) { + todo!(); +} diff --git a/sdk/rust/nym-sdk/examples/simple.rs b/sdk/rust/nym-sdk/examples/simple.rs index 2ff8dae9c8..f55bcc9095 100644 --- a/sdk/rust/nym-sdk/examples/simple.rs +++ b/sdk/rust/nym-sdk/examples/simple.rs @@ -1,22 +1,27 @@ +use examples_common::setup_logging; use nym_sdk::mixnet; #[tokio::main] async fn main() { - // here's what I'd actually like to write - let client = mixnet::Client::new(None); // passing no config makes the client fire up an ephemeral session and figure shit out on its own + setup_logging(); - // let show_receive = move || println!("got a message from the mixnet: {}", message); // might need to bury this in a struct as a `FnMut`, see https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust - // connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet(); + // Passing no config makes the client fire up an ephemeral session and figure shit out on its own + let mut client = mixnet::Client::new(None, None).unwrap(); - // be able to get our client address - println!("Our client address is {}", client.nym_address); + // Connect to the mixnet, now we're listening for incoming + client.connect_to_mixnet().await; - // send important string info up the pipe to a buddy - client.send_str("foo.bar@blah", "flappappa"); + // Be able to get our client address + let our_address = client.nym_address().unwrap(); + println!("Our client nym address is: {our_address}"); - // send some bytes to a buddy - client.send_bytes("foo.bar@blah", "flappappa".as_bytes().to_vec()); + // Send a message throught the mixnet to ourselves + client + .send_str(&our_address.to_string(), "hello there") + .await; - + println!("Waiting for message"); + client + .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) + .await; } diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs new file mode 100644 index 0000000000..5327aa142c --- /dev/null +++ b/sdk/rust/nym-sdk/src/error.rs @@ -0,0 +1,10 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("i/o error: {0}")] + IoError(#[from] std::io::Error), + + #[error("key file encountered that we don't want to overwrite")] + DontOverwrite, +} + +pub type Result = std::result::Result; diff --git a/sdk/rust/nym-sdk/src/lib.rs b/sdk/rust/nym-sdk/src/lib.rs index ae7f01c0eb..de6ec53412 100644 --- a/sdk/rust/nym-sdk/src/lib.rs +++ b/sdk/rust/nym-sdk/src/lib.rs @@ -1 +1,2 @@ +pub mod error; pub mod mixnet; diff --git a/sdk/rust/nym-sdk/src/mixnet.rs b/sdk/rust/nym-sdk/src/mixnet.rs new file mode 100644 index 0000000000..fc190053c9 --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet.rs @@ -0,0 +1,15 @@ +mod client; +mod config; +mod connection_state; +mod key_paths; + +pub use client_core::config::GatewayEndpointConfig; +pub use nymsphinx::{ + addressing::clients::{ClientIdentity, Recipient}, + receiver::ReconstructedMessage, +}; + +pub use key_paths::{GatewayKeyMode, KeyMode, KeyPaths, Keys}; + +pub use client::Client; +pub use config::Config; diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs new file mode 100644 index 0000000000..92ad59b262 --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -0,0 +1,306 @@ +use futures::StreamExt; +use nymsphinx::{ + addressing::clients::{ClientIdentity, Recipient}, + receiver::ReconstructedMessage, +}; +use tap::TapOptional; + +use client_connections::TransmissionLane; +use client_core::{ + client::{ + base_client::{non_wasm_helpers, BaseClientBuilder}, + inbound_messages::InputMessage, + key_manager::KeyManager, + }, + config::{persistence::key_pathfinder::ClientKeyPathfinder, GatewayEndpointConfig}, +}; + +use crate::error::{Error, Result}; + +use super::{connection_state::ConnectionState, Config, GatewayKeyMode, KeyPaths, Keys}; + +pub struct Client { + /// Keys handled by the client + key_manager: KeyManager, + + /// Client configuration + config: Config, + + /// Paths for client keys, including identity, encryption, ack and shared gateway keys. + key_paths: Option, + + /// The client can be in one of multiple states, depending on how it is created and if it's + /// connected to the mixnet. + connection_state: ConnectionState, +} + +impl Client { + /// Create a new mixnet client. If no config options are supplied, creates a new client with + /// ephemeral keys stored in RAM, which will be discarded at application close. + /// + /// Callers have the option of supplying futher parameters to store persistent identities at a + /// location on-disk, if desired. + pub fn new(config_option: Option, key_paths: Option) -> Result { + let config = config_option.unwrap_or_default(); + + // If we are provided paths to keys, use them if they are available. And if they are + // not, write the generated keys back to storage. + let key_manager = if let Some(ref key_paths) = key_paths { + let path_finder = ClientKeyPathfinder::from(key_paths.clone()); + + // Try load keys + match KeyManager::load_keys_maybe_gateway(&path_finder) { + Ok(key_manager) => key_manager, + Err(err) => { + log::debug!("Not loading keys: {err}"); + if path_finder.any_file_exists() && key_paths.operating_mode.is_keep() { + return Err(Error::DontOverwrite); + } + + // Create new keys and write to storage + let key_manager = client_core::init::new_client_keys(); + // WARN: this will overwrite! + key_manager.store_keys(&path_finder)?; + key_manager + } + } + } else { + // Ephemeral keys that we only store in memory + client_core::init::new_client_keys() + }; + + Ok(Client { + key_manager, + config, + key_paths, + connection_state: ConnectionState::New, + }) + } + + /// Get the client identity, which is the public key of the identity key pair. + pub fn identity(&self) -> ClientIdentity { + *self.key_manager.identity_keypair().public_key() + } + + /// Get the nym address for this client, if it is available. The nym address is composed of the + /// client identity, the client encryption key, and the gateway identity. + pub fn nym_address(&self) -> Option<&Recipient> { + self.connection_state.nym_address() + } + + /// Client keys are generated at client creation if none were found. The gateway shared + /// key, however, is created during the gateway registration handshake so it might not + /// necessarily be available. + pub fn has_gateway_key(&self) -> bool { + self.key_manager.gateway_key_set() + } + + pub fn set_keys(&mut self, _keys: &Keys) { + todo!(); + } + + pub fn get_keys(&self) -> &Keys { + todo!(); + } + + pub fn set_gateway_endpoint(&mut self, _gateway_endpoint_config: &GatewayEndpointConfig) { + todo!(); + } + + pub fn get_gateway_endpoint(&self) -> Option<&GatewayEndpointConfig> { + self.connection_state.gateway_endpoint_config() + } + + pub async fn register_with_gateway(&mut self) { + assert!( + matches!(self.connection_state, ConnectionState::New), + "can only setup gateway when in `New` connection state" + ); + + let gateway_config = client_core::init::register_with_gateway( + &mut self.key_manager, + self.config.nym_api_endpoints.clone(), + self.config.user_chosen_gateway.clone(), + ) + .await + .expect("WIP"); + + let nym_address = client_core::init::get_client_address(&self.key_manager, &gateway_config); + + self.connection_state = ConnectionState::Registered { + gateway_endpoint_config: gateway_config, + nym_address, + }; + } + + fn write_gateway_key(&self, key_mode: &GatewayKeyMode) { + let key_paths = self.key_paths.as_ref().unwrap().clone(); + + let path_finder = ClientKeyPathfinder::from(key_paths); + if path_finder.gateway_key_file_exists() && key_mode.is_keep() { + todo!("WIP"); + }; + self.key_manager + .store_key_gateway_only(&path_finder) + .expect("WIP"); + } + + fn write_gateway_endpoint_config(&self) { + let key_paths = self.key_paths.as_ref().unwrap().clone(); + let gateway_endpoint_config_path = key_paths.gateway_endpoint_config; + let gateway_endpoint_config = + toml::to_string(self.get_gateway_endpoint().unwrap()).unwrap(); + + // Ensure the whole directory structure exists + if let Some(parent_dir) = gateway_endpoint_config_path.parent() { + std::fs::create_dir_all(parent_dir).expect("WIP"); + } + std::fs::write(gateway_endpoint_config_path, gateway_endpoint_config).expect("WIP"); + } + + fn read_gateway_endpoint_config(&mut self) { + let key_paths = self.key_paths.as_ref().unwrap().clone(); + let gateway_endpoint_config: GatewayEndpointConfig = toml::from_str( + &std::fs::read_to_string(key_paths.gateway_endpoint_config).expect("WIP"), + ) + .expect("WIP"); + + let nym_address = + client_core::init::get_client_address(&self.key_manager, &gateway_endpoint_config); + + self.connection_state = ConnectionState::Registered { + gateway_endpoint_config, + nym_address, + }; + } + + /// Connects to the mixnet via the gateway in the client config + pub async fn connect_to_mixnet(&mut self) { + // For some simple cases we can figure how to setup gateway without it having to have been + // called in advance. + if matches!(self.connection_state, ConnectionState::New) { + if self.key_paths.is_some() { + if self.has_gateway_key() { + // If we have a gateway key from client, then we can just read the corresponding + // config + println!("Has gateway key: loading"); + self.read_gateway_endpoint_config(); + } else { + // If we didn't find any shared gateway key during creation, that means we first + // need to register a gateway + println!("NO gateway key: registering new"); + self.register_with_gateway().await; + self.write_gateway_key(&GatewayKeyMode::Overwrite); + self.write_gateway_endpoint_config(); + } + } else { + // If we don't have any key paths, just use ephemeral keys + self.register_with_gateway().await; + } + } + + // At this point we should be in a registered state, either at function entry or by the + // above convenience logic. + assert!(matches!( + self.connection_state, + ConnectionState::Registered { .. } + )); + + let bandwidth_controller = None; + + let reply_storage_backend = + non_wasm_helpers::setup_empty_reply_surb_backend(&self.config.debug_config); + + let disabled_credentials = true; + + let gateway_config = self.connection_state.gateway_endpoint_config().unwrap(); + + let base_builder = BaseClientBuilder::new( + gateway_config, + &self.config.debug_config, + self.key_manager.clone(), + bandwidth_controller, + reply_storage_backend, + disabled_credentials, + self.config.nym_api_endpoints.clone(), + ); + + let nym_address = base_builder.as_mix_recipient(); + + let mut started_client = base_builder.start_base().await.unwrap(); + let client_input = started_client.client_input.register_producer(); + let mut client_output = started_client.client_output.register_consumer(); + let client_state = started_client.client_state; + + // Register our receiver + let reconstructed_receiver = client_output.register_receiver().unwrap(); + + self.connection_state = ConnectionState::Connected { + nym_address, + client_input, + client_output, + client_state, + reconstructed_receiver, + task_manager: started_client.task_manager, + }; + } + + /// Sends stringy data to the supplied Nym address + pub async fn send_str(&self, address: &str, message: &str) { + log::debug!("send_str"); + let message_bytes = message.to_string().into_bytes(); + self.send_bytes(address, message_bytes).await; + } + + /// Sends bytes to the supplied Nym address + pub async fn send_bytes(&self, address: &str, message: Vec) { + log::debug!("send_bytes"); + let Some(client_input) = self.connection_state.client_input() else { + log::error!("Error: trying to send without being connected"); + return; + }; + + let lane = TransmissionLane::General; + let recipient = Recipient::try_from_base58_string(address).unwrap(); + let input_msg = InputMessage::new_regular(recipient, message, lane); + client_input.input_sender.send(input_msg).await.unwrap(); + } + + /// Wait for messages from the mixnet + pub async fn wait_for_messages(&mut self) -> Option> { + let receiver = self + .connection_state + .reconstructed_receiver() + .tap_none(|| log::error!("Error: trying to wait without being connected"))?; + + receiver.next().await + } + + pub fn wait_for_messages_split(&mut self) -> Option { + todo!(); + } + + pub async fn on_messages(&mut self, fun: F) + where + F: Fn(ReconstructedMessage), + { + while let Some(msgs) = self.wait_for_messages().await { + for msg in msgs { + fun(msg) + } + } + } + + /// Disconnect from the mixnet. Currently it is not supported to reconnect a disconnected + /// client. + pub async fn disconnect(&mut self) { + let Some(task_manager) = self.connection_state.task_manager() else { + log::error!("Trying to disconnect when not connected!"); + return; + }; + + task_manager.signal_shutdown().ok(); + task_manager.wait_for_shutdown().await; + self.connection_state = ConnectionState::Disconnected; + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/config.rs b/sdk/rust/nym-sdk/src/mixnet/config.rs new file mode 100644 index 0000000000..92abf0560d --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/config.rs @@ -0,0 +1,36 @@ +use client_core::config::DebugConfig; +use network_defaults::mainnet; +use url::Url; + +pub struct Config { + /// If the user has explicitly specified a gateway. + pub user_chosen_gateway: Option, + + /// List of nym-api endpoints + pub nym_api_endpoints: Vec, + + /// Flags controlling all sorts of internal client behaviour. + /// Changing these risk compromising network anonymity! + pub debug_config: DebugConfig, +} + +impl Default for Config { + fn default() -> Self { + let nym_api_endpoints = vec![mainnet::API_VALIDATOR.to_string().parse().unwrap()]; + Self { + user_chosen_gateway: Default::default(), + nym_api_endpoints, + debug_config: Default::default(), + } + } +} + +impl Config { + pub fn new(user_chosen_gateway: Option, nym_api_endpoints: Vec) -> Self { + Self { + user_chosen_gateway, + nym_api_endpoints, + debug_config: DebugConfig::default(), + } + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/connection_state.rs b/sdk/rust/nym-sdk/src/mixnet/connection_state.rs new file mode 100644 index 0000000000..5336143dc5 --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/connection_state.rs @@ -0,0 +1,115 @@ +use client_core::{ + client::{ + base_client::{ClientInput, ClientOutput, ClientState}, + received_buffer::ReconstructedMessagesReceiver, + }, + config::GatewayEndpointConfig, +}; +use nymsphinx::addressing::clients::Recipient; +use task::TaskManager; + +/// States that the client connection can be in. Currently the states can only progress linearly +/// from New -> Registered -> Connected -> Disconnected. In the future it could be useful to be able +/// to re-connect a disconnected client. +// WIP(JON): consider adding inner types +#[allow(clippy::large_enum_variant)] +pub(super) enum ConnectionState { + New, + Registered { + nym_address: Recipient, + gateway_endpoint_config: GatewayEndpointConfig, + }, + Connected { + nym_address: Recipient, + client_input: ClientInput, + #[allow(dead_code)] + client_output: ClientOutput, + #[allow(dead_code)] + client_state: ClientState, + reconstructed_receiver: ReconstructedMessagesReceiver, + task_manager: TaskManager, + }, + Disconnected, +} + +impl std::fmt::Debug for ConnectionState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::New => write!(f, "New"), + Self::Registered { + nym_address, + gateway_endpoint_config: gateway_config, + } => f + .debug_struct("Registered") + .field("nym_address", nym_address) + .field("gateway_config", gateway_config) + .finish(), + Self::Connected { + nym_address, + client_input: _, + client_output: _, + client_state: _, + reconstructed_receiver, + task_manager, + } => f + .debug_struct("Connected") + .field("nym_address", nym_address) + .field("reconstructed_receiver", reconstructed_receiver) + .field("task_manager", task_manager) + .finish(), + Self::Disconnected => write!(f, "Disconnected"), + } + } +} + +impl ConnectionState { + pub(super) fn client_input(&self) -> Option<&ClientInput> { + match self { + ConnectionState::New + | ConnectionState::Registered { .. } + | ConnectionState::Disconnected => None, + ConnectionState::Connected { client_input, .. } => Some(client_input), + } + } + + pub(super) fn reconstructed_receiver(&mut self) -> Option<&mut ReconstructedMessagesReceiver> { + match self { + ConnectionState::New + | ConnectionState::Registered { .. } + | ConnectionState::Disconnected => None, + ConnectionState::Connected { + reconstructed_receiver, + .. + } => Some(reconstructed_receiver), + } + } + + pub(super) fn gateway_endpoint_config(&self) -> Option<&GatewayEndpointConfig> { + match self { + ConnectionState::New + | ConnectionState::Connected { .. } + | ConnectionState::Disconnected => None, + ConnectionState::Registered { + gateway_endpoint_config: gateway_config, + .. + } => Some(gateway_config), + } + } + + pub(super) fn nym_address(&self) -> Option<&Recipient> { + match self { + ConnectionState::New | ConnectionState::Disconnected => None, + ConnectionState::Registered { nym_address, .. } + | ConnectionState::Connected { nym_address, .. } => Some(nym_address), + } + } + + pub(super) fn task_manager(&mut self) -> Option<&mut TaskManager> { + match self { + ConnectionState::New + | ConnectionState::Registered { .. } + | ConnectionState::Disconnected => None, + ConnectionState::Connected { task_manager, .. } => Some(task_manager), + } + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/key_paths.rs b/sdk/rust/nym-sdk/src/mixnet/key_paths.rs new file mode 100644 index 0000000000..630e562b45 --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/key_paths.rs @@ -0,0 +1,142 @@ +use client_core::{ + client::key_manager::KeyManager, config::persistence::key_pathfinder::ClientKeyPathfinder, +}; +use crypto::asymmetric::{encryption, identity}; +use gateway_requests::registration::handshake::SharedKeys; +use nymsphinx::acknowledgements::AckKey; + +use std::path::{Path, PathBuf}; + +#[derive(Clone, Debug)] +pub enum KeyMode { + /// Use existing key files if they exists, otherwise create new ones. + Keep, + /// Create new keys, overwriting any potential previously existing keys. + Overwrite, +} + +impl KeyMode { + pub(crate) fn is_keep(&self) -> bool { + matches!(self, KeyMode::Keep) + } +} + +pub enum GatewayKeyMode { + /// Keep shared gateway key if found, otherwise create a new one. + Keep, + /// Create a new shared key and overwrite any potential existing one. + Overwrite, +} + +impl GatewayKeyMode { + pub(crate) fn is_keep(&self) -> bool { + matches!(self, GatewayKeyMode::Keep) + } +} + +#[derive(Clone, Debug)] +pub struct KeyPaths { + // Determines how to handle existing key files found. + pub operating_mode: KeyMode, + + // Client identity keys + pub private_identity: PathBuf, + pub public_identity: PathBuf, + + // Client encryption keys + pub private_encryption: PathBuf, + pub public_encryption: PathBuf, + + // Key for handling acks + pub ack_key: PathBuf, + + // Key setup after authenticating with a gateway + pub gateway_shared_key: PathBuf, + + // The key isn't much use without knowing which entity it refers to. + // This is an `Option` in case the end user might want to read write keys to storage, but + // handle the gateway configuration manually through `set_gateway_endpoint`. + // WIP(JON): make it an option? + pub gateway_endpoint_config: PathBuf, + + pub credential_database_path: PathBuf, + + pub reply_surb_database_path: PathBuf, +} + +pub struct Keys { + pub identity_keypair: identity::KeyPair, + pub encryption_keypair: encryption::KeyPair, + pub ack_key: AckKey, + pub gateway_shared_key: SharedKeys, +} + +//#[derive(Clone, Debug)] +//pub struct GatewaySetup { +// pub gateway_shared_key: PathBuf, +// pub gateway_endpoint_config: GatewayEndpointConfig, +// //pub gateway_endpoint_config: GatewayConfig, +//} + +//#[derive(Clone, Debug)] +//pub enum GatewayConfig { +// File(PathBuf), +// Struct(GatewayEndpointConfig), +//} + +impl KeyPaths { + pub fn new_from_dir(operating_mode: KeyMode, dir: &Path) -> Self { + assert!(!dir.is_file(), "WIP"); + Self { + // These filenames were chosen to match the ones we use in `nym-client`. Consider + // changing the defaults + operating_mode, + private_identity: dir.join("private_identity.pem"), + public_identity: dir.join("public_identity.pem"), + private_encryption: dir.join("private_encryption.pem"), + public_encryption: dir.join("public_encryption.pem"), + ack_key: dir.join("ack_key.pem"), + gateway_shared_key: dir.join("gateway_shared.pem"), + gateway_endpoint_config: dir.join("gateway_endpoint_config.toml"), + credential_database_path: dir.join("db.sqlite"), + reply_surb_database_path: dir.join("persistent_reply_store.sqlite"), + } + } +} + +impl From for ClientKeyPathfinder { + fn from(paths: KeyPaths) -> Self { + Self { + identity_private_key: paths.private_identity, + identity_public_key: paths.public_identity, + encryption_private_key: paths.private_encryption, + encryption_public_key: paths.public_encryption, + gateway_shared_key: paths.gateway_shared_key, + ack_key: paths.ack_key, + } + } +} + +impl From for KeyManager { + fn from(keys: Keys) -> Self { + KeyManager::new_from_keys( + keys.identity_keypair, + keys.encryption_keypair, + keys.gateway_shared_key, + keys.ack_key, + ) + } +} + +//pub struct GatewayConfigPath { +// pub path: PathBuf, +//} +// +//impl GatewayConfigPath { +// pub fn new_from_dir(dir: PathBuf) -> Self { +// assert!(!dir.is_file(), "WIP"); +// Self { +// path: dir.join("gateway_endpoint_config.toml"), +// } +// } +//} diff --git a/sdk/rust/nym-sdk/src/mixnet/mod.rs b/sdk/rust/nym-sdk/src/mixnet/mod.rs deleted file mode 100644 index 46ea1aadde..0000000000 --- a/sdk/rust/nym-sdk/src/mixnet/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::path::PathBuf; - -pub struct Client { - pub nym_address: String, - config: Config, -} - -impl Client { - /// Create a new mixnet client. If no config options are supplied, creates a new client with ephemeral keys - /// stored in RAM, which will be discarded at application close. - /// - /// Callers have the option of supplying futher parameters to store persistent identities at a location on-disk, - /// if desired. - pub fn new(config_option: Option) -> Client { - if config_option.is_none() { - let config = Self::some_minimal_config(); - let nym_address = Self::new_nym_address(); - Client { - config, - nym_address, - } - } else { - let self_address = Self::new_nym_address(); - let nym_address = Self::new_nym_address(); - - Client { - config: config_option.unwrap(), - nym_address, - } - } - } - - /// Connects to the mixnet via the gateway in the client config - pub fn connect_to_mixnet(&self) {} - - /// Sets the callback function which is run when the client receives a message - /// from the mixnet - pub fn on_receive(&mut self, message: &str) { - println!("Message received: {}", message); - } - - /// Sends stringy data to the supplied Nym address - pub fn send_str(&self, address: &str, message: &str) {} - - /// Sends bytes to the supplied Nym address - pub fn send_bytes(&self, address: &str, message: Vec) {} - - fn new_nym_address() -> String { - "the.nym@address".to_string() - } - - fn some_minimal_config() -> Config { - let mut keys_path = PathBuf::new(); - Config { - keys: Some(keys_path), - } - } -} - -pub struct Config { - pub keys: Option, -} From b019786c5ae22947617d2f7b7e006cbcc7172eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 5 Jan 2023 10:00:48 +0100 Subject: [PATCH 04/18] rust-sdk: replace a bunch of unwrap with error return --- sdk/rust/nym-sdk/src/error.rs | 7 ++++- sdk/rust/nym-sdk/src/mixnet/client.rs | 45 ++++++++++++++------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs index 5327aa142c..45c5c342a2 100644 --- a/sdk/rust/nym-sdk/src/error.rs +++ b/sdk/rust/nym-sdk/src/error.rs @@ -2,9 +2,14 @@ pub enum Error { #[error("i/o error: {0}")] IoError(#[from] std::io::Error), - + #[error("toml error: {0}")] + TomlError(#[from] toml::de::Error), + #[error(transparent)] + ClientCoreError(#[from] client_core::error::ClientCoreError), #[error("key file encountered that we don't want to overwrite")] DontOverwrite, + #[error("shared gateway key file encountered that we don't want to overwrite")] + DontOverwriteGatewayKey, } pub type Result = std::result::Result; diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 92ad59b262..1444585dd7 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -111,7 +111,7 @@ impl Client { self.connection_state.gateway_endpoint_config() } - pub async fn register_with_gateway(&mut self) { + pub async fn register_with_gateway(&mut self) -> Result<()> { assert!( matches!(self.connection_state, ConnectionState::New), "can only setup gateway when in `New` connection state" @@ -122,8 +122,7 @@ impl Client { self.config.nym_api_endpoints.clone(), self.config.user_chosen_gateway.clone(), ) - .await - .expect("WIP"); + .await?; let nym_address = client_core::init::get_client_address(&self.key_manager, &gateway_config); @@ -131,21 +130,21 @@ impl Client { gateway_endpoint_config: gateway_config, nym_address, }; + Ok(()) } - fn write_gateway_key(&self, key_mode: &GatewayKeyMode) { + fn write_gateway_key(&self, key_mode: &GatewayKeyMode) -> Result<()> { let key_paths = self.key_paths.as_ref().unwrap().clone(); let path_finder = ClientKeyPathfinder::from(key_paths); if path_finder.gateway_key_file_exists() && key_mode.is_keep() { - todo!("WIP"); + return Err(Error::DontOverwriteGatewayKey); }; - self.key_manager - .store_key_gateway_only(&path_finder) - .expect("WIP"); + self.key_manager.store_key_gateway_only(&path_finder)?; + Ok(()) } - fn write_gateway_endpoint_config(&self) { + fn write_gateway_endpoint_config(&self) -> Result<()> { let key_paths = self.key_paths.as_ref().unwrap().clone(); let gateway_endpoint_config_path = key_paths.gateway_endpoint_config; let gateway_endpoint_config = @@ -153,17 +152,17 @@ impl Client { // Ensure the whole directory structure exists if let Some(parent_dir) = gateway_endpoint_config_path.parent() { - std::fs::create_dir_all(parent_dir).expect("WIP"); + std::fs::create_dir_all(parent_dir)?; } - std::fs::write(gateway_endpoint_config_path, gateway_endpoint_config).expect("WIP"); + std::fs::write(gateway_endpoint_config_path, gateway_endpoint_config)?; + Ok(()) } - fn read_gateway_endpoint_config(&mut self) { + fn read_gateway_endpoint_config(&mut self) -> Result<()> { let key_paths = self.key_paths.as_ref().unwrap().clone(); - let gateway_endpoint_config: GatewayEndpointConfig = toml::from_str( - &std::fs::read_to_string(key_paths.gateway_endpoint_config).expect("WIP"), - ) - .expect("WIP"); + let gateway_endpoint_config: GatewayEndpointConfig = + std::fs::read_to_string(key_paths.gateway_endpoint_config) + .map(|str| toml::from_str(&str))??; let nym_address = client_core::init::get_client_address(&self.key_manager, &gateway_endpoint_config); @@ -172,10 +171,11 @@ impl Client { gateway_endpoint_config, nym_address, }; + Ok(()) } /// Connects to the mixnet via the gateway in the client config - pub async fn connect_to_mixnet(&mut self) { + pub async fn connect_to_mixnet(&mut self) -> Result<()> { // For some simple cases we can figure how to setup gateway without it having to have been // called in advance. if matches!(self.connection_state, ConnectionState::New) { @@ -184,18 +184,18 @@ impl Client { // If we have a gateway key from client, then we can just read the corresponding // config println!("Has gateway key: loading"); - self.read_gateway_endpoint_config(); + self.read_gateway_endpoint_config()?; } else { // If we didn't find any shared gateway key during creation, that means we first // need to register a gateway println!("NO gateway key: registering new"); - self.register_with_gateway().await; - self.write_gateway_key(&GatewayKeyMode::Overwrite); - self.write_gateway_endpoint_config(); + self.register_with_gateway().await?; + self.write_gateway_key(&GatewayKeyMode::Overwrite)?; + self.write_gateway_endpoint_config()?; } } else { // If we don't have any key paths, just use ephemeral keys - self.register_with_gateway().await; + self.register_with_gateway().await?; } } @@ -243,6 +243,7 @@ impl Client { reconstructed_receiver, task_manager: started_client.task_manager, }; + Ok(()) } /// Sends stringy data to the supplied Nym address From 775ce0f95dc4b66cc33dc8341487d231f63796c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 5 Jan 2023 15:17:10 +0100 Subject: [PATCH 05/18] rust-sdk: fix some issues identified during draft review --- Cargo.lock | 10 +------ .../client-core/src/client/base_client/mod.rs | 30 +++++++++++++++++-- .../client/base_client/non_wasm_helpers.rs | 12 ++++---- clients/client-core/src/client/key_manager.rs | 7 ++++- .../action_controller.rs | 5 +--- .../real_traffic_stream.rs | 4 +-- .../replies/reply_storage/backend/mod.rs | 4 +-- .../src/config/persistence/key_pathfinder.rs | 12 -------- clients/socks5/src/socks/mixnet_responses.rs | 7 +---- clients/webassembly/src/client/mod.rs | 12 ++++++-- .../src/connection_controller.rs | 6 +--- common/task/src/manager.rs | 12 ++++---- sdk/rust/nym-sdk/Cargo.toml | 2 +- sdk/rust/nym-sdk/examples-common/Cargo.toml | 10 ------- sdk/rust/nym-sdk/examples-common/src/lib.rs | 21 ------------- sdk/rust/nym-sdk/examples/complex_config.rs | 3 +- sdk/rust/nym-sdk/examples/complex_config2.rs | 3 +- sdk/rust/nym-sdk/examples/simple.rs | 3 +- sdk/rust/nym-sdk/src/mixnet/client.rs | 12 ++++---- 19 files changed, 73 insertions(+), 102 deletions(-) delete mode 100644 sdk/rust/nym-sdk/examples-common/Cargo.toml delete mode 100644 sdk/rust/nym-sdk/examples-common/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 053b8f1790..a5e6151786 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1778,14 +1778,6 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" -[[package]] -name = "examples-common" -version = "0.1.0" -dependencies = [ - "log", - "pretty_env_logger", -] - [[package]] name = "execute" version = "0.1.0" @@ -3590,11 +3582,11 @@ dependencies = [ "client-connections", "client-core", "crypto", - "examples-common", "futures", "gateway-client", "gateway-requests", "log", + "logging", "network-defaults", "nymsphinx", "pretty_env_logger", diff --git a/clients/client-core/src/client/base_client/mod.rs b/clients/client-core/src/client/base_client/mod.rs index 196ae058d8..4286f9999f 100644 --- a/clients/client-core/src/client/base_client/mod.rs +++ b/clients/client-core/src/client/base_client/mod.rs @@ -103,6 +103,32 @@ impl ClientOutputStatus { } } +#[derive(PartialEq, Eq)] +pub enum CredentialsToggle { + Enabled, + Disabled, +} + +impl CredentialsToggle { + pub fn is_enabled(&self) -> bool { + self == &CredentialsToggle::Enabled + } + + pub fn is_disabled(&self) -> bool { + self == &CredentialsToggle::Disabled + } +} + +impl From for CredentialsToggle { + fn from(value: bool) -> Self { + if value { + CredentialsToggle::Enabled + } else { + CredentialsToggle::Disabled + } + } +} + pub struct BaseClientBuilder<'a, B> { // due to wasm limitations I had to split it like this : ( gateway_config: &'a GatewayEndpointConfig, @@ -142,13 +168,13 @@ where key_manager: KeyManager, bandwidth_controller: Option, reply_storage_backend: B, - disabled_credentials: bool, + credentials_toggle: CredentialsToggle, nym_api_endpoints: Vec, ) -> BaseClientBuilder<'a, B> { BaseClientBuilder { gateway_config, debug_config, - disabled_credentials, + disabled_credentials: credentials_toggle.is_disabled(), nym_api_endpoints, reply_storage_backend, bandwidth_controller, diff --git a/clients/client-core/src/client/base_client/non_wasm_helpers.rs b/clients/client-core/src/client/base_client/non_wasm_helpers.rs index 6ed6116e63..6ed3e329e9 100644 --- a/clients/client-core/src/client/base_client/non_wasm_helpers.rs +++ b/clients/client-core/src/client/base_client/non_wasm_helpers.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::replies::reply_storage::{ - browser_backend, fs_backend, CombinedReplyStorage, ReplyStorageBackend, + self, fs_backend, CombinedReplyStorage, ReplyStorageBackend, }; use crate::config::DebugConfig; use crate::error::ClientCoreError; @@ -86,9 +86,9 @@ pub async fn setup_fs_reply_surb_backend>( } } -pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> browser_backend::Backend { - browser_backend::Backend::new( - debug_config.minimum_reply_surb_storage_threshold, - debug_config.maximum_reply_surb_storage_threshold, - ) +pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> reply_storage::Empty { + reply_storage::Empty { + min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold, + max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold, + } } diff --git a/clients/client-core/src/client/key_manager.rs b/clients/client-core/src/client/key_manager.rs index 4b6647ecbc..69192a3394 100644 --- a/clients/client-core/src/client/key_manager.rs +++ b/clients/client-core/src/client/key_manager.rs @@ -175,7 +175,12 @@ impl KeyManager { client_pathfinder: &ClientKeyPathfinder, ) -> io::Result<()> { match self.gateway_shared_key.as_ref() { - None => panic!("WIP(JON)"), + None => { + return Err(io::Error::new( + io::ErrorKind::Other, + "trying to store a non-existing key", + )) + } Some(gate_key) => { pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())? } diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index faa250aeac..fe79e24183 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -286,10 +286,7 @@ impl ActionController { } } } - #[cfg(not(target_arch = "wasm32"))] - tokio::time::timeout(Duration::from_secs(5), shutdown.recv()) - .await - .expect("Task stopped without shutdown called"); + shutdown.recv_timeout().await; log::debug!("ActionController: Exiting"); } } diff --git a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs index 2abbd4661e..6721d1ddb4 100644 --- a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -535,9 +535,7 @@ where } } } - tokio::time::timeout(Duration::from_secs(5), shutdown.recv()) - .await - .expect("Task stopped without shutdown called"); + shutdown.recv_timeout().await; } #[cfg(target_arch = "wasm32")] diff --git a/clients/client-core/src/client/replies/reply_storage/backend/mod.rs b/clients/client-core/src/client/replies/reply_storage/backend/mod.rs index 0920b37657..362ce051bd 100644 --- a/clients/client-core/src/client/replies/reply_storage/backend/mod.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/mod.rs @@ -6,12 +6,10 @@ use async_trait::async_trait; use std::error::Error; use thiserror::Error; -//#[cfg(target_arch = "wasm32")] -//#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] +#[cfg(target_arch = "wasm32")] pub mod browser_backend; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] -//#[cfg(target_arch = "wasm32")] pub mod fs_backend; // #[cfg(all(test, feature = "std"))] diff --git a/clients/client-core/src/config/persistence/key_pathfinder.rs b/clients/client-core/src/config/persistence/key_pathfinder.rs index 90f00eb730..67a8fb010a 100644 --- a/clients/client-core/src/config/persistence/key_pathfinder.rs +++ b/clients/client-core/src/config/persistence/key_pathfinder.rs @@ -29,18 +29,6 @@ impl ClientKeyPathfinder { } } - //pub fn new_from_dir(config_dir: &PathBuf) -> Self { - // dbg!(&config_dir); - // ClientKeyPathfinder { - // identity_private_key: config_dir.join("private_identity.pem"), - // identity_public_key: config_dir.join("public_identity.pem"), - // encryption_private_key: config_dir.join("public_encryption.pem"), - // encryption_public_key: config_dir.join("private_encryption.pem"), - // gateway_shared_key: config_dir.join("gateway_shared.pem"), - // ack_key: config_dir.join("ack_key.pem"), - // } - //} - pub fn new_from_config(config: &Config) -> Self { ClientKeyPathfinder { identity_private_key: config.get_private_identity_key_file(), diff --git a/clients/socks5/src/socks/mixnet_responses.rs b/clients/socks5/src/socks/mixnet_responses.rs index 85d0a7da86..f562ee5b68 100644 --- a/clients/socks5/src/socks/mixnet_responses.rs +++ b/clients/socks5/src/socks/mixnet_responses.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - use futures::channel::mpsc; use futures::StreamExt; use log::*; @@ -116,10 +114,7 @@ impl MixnetResponseListener { } } } - #[cfg(not(target_arch = "wasm32"))] - tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv()) - .await - .expect("Task stopped without shutdown called"); + self.shutdown.recv_timeout().await; log::debug!("MixnetResponseListener: Exiting"); } } diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index bd9ceab576..01689febf0 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -5,7 +5,9 @@ use self::config::Config; use crate::client::helpers::InputSender; use crate::client::response_pusher::ResponsePusher; use client_connections::TransmissionLane; -use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput}; +use client_core::client::base_client::{ + BaseClientBuilder, ClientInput, ClientOutput, CredentialsToggle, +}; use client_core::client::replies::reply_storage::browser_backend; use client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager}; use gateway_client::bandwidth::BandwidthController; @@ -92,13 +94,19 @@ impl NymClientBuilder { future_to_promise(async move { console_log!("Starting the wasm client"); + let disabled_credentials = if self.disabled_credentials { + CredentialsToggle::Disabled + } else { + CredentialsToggle::Enabled + }; + let base_builder = BaseClientBuilder::new( &self.config.gateway_endpoint, &self.config.debug, self.key_manager, self.bandwidth_controller, self.reply_surb_storage_backend, - self.disabled_credentials, + disabled_credentials, vec![self.config.nym_api_url.clone()], ); diff --git a/common/socks5/proxy-helpers/src/connection_controller.rs b/common/socks5/proxy-helpers/src/connection_controller.rs index 66c8b94a7a..bfe0fbae55 100644 --- a/common/socks5/proxy-helpers/src/connection_controller.rs +++ b/common/socks5/proxy-helpers/src/connection_controller.rs @@ -254,11 +254,7 @@ impl Controller { }, } } - #[cfg(not(target_arch = "wasm32"))] - tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv()) - .await - .expect("Task stopped without shutdown called"); - assert!(self.shutdown.is_shutdown_poll()); + self.shutdown.recv_timeout().await; log::debug!("SOCKS5 Controller: Exiting"); } } diff --git a/common/task/src/manager.rs b/common/task/src/manager.rs index 7c8ab6586e..b458634566 100644 --- a/common/task/src/manager.rs +++ b/common/task/src/manager.rs @@ -208,7 +208,7 @@ pub struct TaskClient { impl TaskClient { #[cfg(not(target_arch = "wasm32"))] - const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(1); + const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(5); fn new( notify: watch::Receiver<()>, @@ -231,7 +231,6 @@ impl TaskClient { let (_notify_tx, notify_rx) = watch::channel(()); let (task_halt_tx, _task_halt_rx) = mpsc::unbounded_channel(); let (task_drop_tx, _task_drop_rx) = mpsc::unbounded_channel(); - //let (task_status_tx, _task_status_rx) = futures::channel::mpsc::unbounded(); let (task_status_tx, _task_status_rx) = futures::channel::mpsc::channel(128); TaskClient { shutdown: false, @@ -280,9 +279,12 @@ impl TaskClient { return pending().await; } #[cfg(not(target_arch = "wasm32"))] - tokio::time::timeout(Self::SHUTDOWN_TIMEOUT, self.recv()) - .await - .expect("Task stopped without shutdown called"); + tokio::time::timeout( + Self::SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT, + self.recv(), + ) + .await + .expect("Task stopped without shutdown called"); } pub fn is_shutdown_poll(&mut self) -> bool { diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index 314bf9e711..13abe47233 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -26,4 +26,4 @@ toml = "0.5.10" [dev-dependencies] pretty_env_logger = "0.4.0" tokio = { version = "1", features = ["full"] } -examples-common = { path = "examples-common" } +logging = { path = "../../../common/logging" } diff --git a/sdk/rust/nym-sdk/examples-common/Cargo.toml b/sdk/rust/nym-sdk/examples-common/Cargo.toml deleted file mode 100644 index 338b766a8f..0000000000 --- a/sdk/rust/nym-sdk/examples-common/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "examples-common" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -log = "0.4.17" -pretty_env_logger = "0.4.0" diff --git a/sdk/rust/nym-sdk/examples-common/src/lib.rs b/sdk/rust/nym-sdk/examples-common/src/lib.rs deleted file mode 100644 index 7aa0420987..0000000000 --- a/sdk/rust/nym-sdk/examples-common/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub fn setup_logging() { - let mut log_builder = pretty_env_logger::formatted_timed_builder(); - if let Ok(s) = ::std::env::var("RUST_LOG") { - log_builder.parse_filters(&s); - } else { - // default to 'Info' - log_builder.filter(None, log::LevelFilter::Info); - } - - log_builder - .filter_module("hyper", log::LevelFilter::Warn) - .filter_module("tokio_reactor", log::LevelFilter::Warn) - .filter_module("reqwest", log::LevelFilter::Warn) - .filter_module("mio", log::LevelFilter::Warn) - .filter_module("want", log::LevelFilter::Warn) - .filter_module("tungstenite", log::LevelFilter::Warn) - .filter_module("tokio_tungstenite", log::LevelFilter::Warn) - .filter_module("handlebars", log::LevelFilter::Warn) - .filter_module("sled", log::LevelFilter::Warn) - .init(); -} diff --git a/sdk/rust/nym-sdk/examples/complex_config.rs b/sdk/rust/nym-sdk/examples/complex_config.rs index 757c95c8a0..2251174e6d 100644 --- a/sdk/rust/nym-sdk/examples/complex_config.rs +++ b/sdk/rust/nym-sdk/examples/complex_config.rs @@ -1,11 +1,10 @@ use std::path::PathBuf; -use examples_common::setup_logging; use nym_sdk::mixnet; #[tokio::main] async fn main() { - setup_logging(); + logging::setup_logging(); // Specify some config options let config_dir = PathBuf::from("/tmp/mixnet-client"); diff --git a/sdk/rust/nym-sdk/examples/complex_config2.rs b/sdk/rust/nym-sdk/examples/complex_config2.rs index 413722769a..4e31918935 100644 --- a/sdk/rust/nym-sdk/examples/complex_config2.rs +++ b/sdk/rust/nym-sdk/examples/complex_config2.rs @@ -1,9 +1,8 @@ -use examples_common::setup_logging; use nym_sdk::mixnet; #[tokio::main] async fn main() { - setup_logging(); + logging::setup_logging(); // We can set a few options let user_chosen_gateway_id = None; diff --git a/sdk/rust/nym-sdk/examples/simple.rs b/sdk/rust/nym-sdk/examples/simple.rs index f55bcc9095..4a5c96a0ad 100644 --- a/sdk/rust/nym-sdk/examples/simple.rs +++ b/sdk/rust/nym-sdk/examples/simple.rs @@ -1,9 +1,8 @@ -use examples_common::setup_logging; use nym_sdk::mixnet; #[tokio::main] async fn main() { - setup_logging(); + logging::setup_logging(); // Passing no config makes the client fire up an ephemeral session and figure shit out on its own let mut client = mixnet::Client::new(None, None).unwrap(); diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 1444585dd7..d166bfd077 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -8,7 +8,7 @@ use tap::TapOptional; use client_connections::TransmissionLane; use client_core::{ client::{ - base_client::{non_wasm_helpers, BaseClientBuilder}, + base_client::{non_wasm_helpers, BaseClientBuilder, CredentialsToggle}, inbound_messages::InputMessage, key_manager::KeyManager, }, @@ -206,22 +206,22 @@ impl Client { ConnectionState::Registered { .. } )); + let gateway_config = self.connection_state.gateway_endpoint_config().unwrap(); + + // TODO: we currently don't support having a bandwidth controller let bandwidth_controller = None; + // TODO: currently we only support in-memory reply surb storage. let reply_storage_backend = non_wasm_helpers::setup_empty_reply_surb_backend(&self.config.debug_config); - let disabled_credentials = true; - - let gateway_config = self.connection_state.gateway_endpoint_config().unwrap(); - let base_builder = BaseClientBuilder::new( gateway_config, &self.config.debug_config, self.key_manager.clone(), bandwidth_controller, reply_storage_backend, - disabled_credentials, + CredentialsToggle::Disabled, self.config.nym_api_endpoints.clone(), ); From 1d522143a27e6e0e1ed22013dc286a860c6bf442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 5 Jan 2023 15:44:38 +0100 Subject: [PATCH 06/18] rust-sdk: remove more unwraps --- .../client-core/src/client/base_client/mod.rs | 2 +- sdk/rust/nym-sdk/src/error.rs | 6 +++-- sdk/rust/nym-sdk/src/mixnet/client.rs | 27 +++++++++---------- sdk/rust/nym-sdk/src/mixnet/key_paths.rs | 3 --- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/clients/client-core/src/client/base_client/mod.rs b/clients/client-core/src/client/base_client/mod.rs index 4286f9999f..cfa0c36da9 100644 --- a/clients/client-core/src/client/base_client/mod.rs +++ b/clients/client-core/src/client/base_client/mod.rs @@ -103,7 +103,7 @@ impl ClientOutputStatus { } } -#[derive(PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq)] pub enum CredentialsToggle { Enabled, Disabled, diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs index 45c5c342a2..cdc6f4fa2d 100644 --- a/sdk/rust/nym-sdk/src/error.rs +++ b/sdk/rust/nym-sdk/src/error.rs @@ -2,8 +2,10 @@ pub enum Error { #[error("i/o error: {0}")] IoError(#[from] std::io::Error), - #[error("toml error: {0}")] - TomlError(#[from] toml::de::Error), + #[error("toml serialization error: {0}")] + TomlSerializationError(#[from] toml::ser::Error), + #[error("toml deserialization error: {0}")] + TomlDeserializationError(#[from] toml::de::Error), #[error(transparent)] ClientCoreError(#[from] client_core::error::ClientCoreError), #[error("key file encountered that we don't want to overwrite")] diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index d166bfd077..5565c6df88 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -1,3 +1,5 @@ +use std::path::Path; + use futures::StreamExt; use nymsphinx::{ addressing::clients::{ClientIdentity, Recipient}, @@ -133,9 +135,7 @@ impl Client { Ok(()) } - fn write_gateway_key(&self, key_mode: &GatewayKeyMode) -> Result<()> { - let key_paths = self.key_paths.as_ref().unwrap().clone(); - + fn write_gateway_key(&self, key_paths: KeyPaths, key_mode: &GatewayKeyMode) -> Result<()> { let path_finder = ClientKeyPathfinder::from(key_paths); if path_finder.gateway_key_file_exists() && key_mode.is_keep() { return Err(Error::DontOverwriteGatewayKey); @@ -144,11 +144,8 @@ impl Client { Ok(()) } - fn write_gateway_endpoint_config(&self) -> Result<()> { - let key_paths = self.key_paths.as_ref().unwrap().clone(); - let gateway_endpoint_config_path = key_paths.gateway_endpoint_config; - let gateway_endpoint_config = - toml::to_string(self.get_gateway_endpoint().unwrap()).unwrap(); + fn write_gateway_endpoint_config(gateway_endpoint_config_path: &Path) -> Result<()> { + let gateway_endpoint_config = toml::to_string(gateway_endpoint_config_path)?; // Ensure the whole directory structure exists if let Some(parent_dir) = gateway_endpoint_config_path.parent() { @@ -158,10 +155,9 @@ impl Client { Ok(()) } - fn read_gateway_endpoint_config(&mut self) -> Result<()> { - let key_paths = self.key_paths.as_ref().unwrap().clone(); + fn read_gateway_endpoint_config(&mut self, gateway_endpoint_config_path: &Path) -> Result<()> { let gateway_endpoint_config: GatewayEndpointConfig = - std::fs::read_to_string(key_paths.gateway_endpoint_config) + std::fs::read_to_string(gateway_endpoint_config_path) .map(|str| toml::from_str(&str))??; let nym_address = @@ -179,19 +175,20 @@ impl Client { // For some simple cases we can figure how to setup gateway without it having to have been // called in advance. if matches!(self.connection_state, ConnectionState::New) { - if self.key_paths.is_some() { + if let Some(key_paths) = &self.key_paths { + let key_paths = key_paths.clone(); if self.has_gateway_key() { // If we have a gateway key from client, then we can just read the corresponding // config println!("Has gateway key: loading"); - self.read_gateway_endpoint_config()?; + self.read_gateway_endpoint_config(&key_paths.gateway_endpoint_config)?; } else { // If we didn't find any shared gateway key during creation, that means we first // need to register a gateway println!("NO gateway key: registering new"); self.register_with_gateway().await?; - self.write_gateway_key(&GatewayKeyMode::Overwrite)?; - self.write_gateway_endpoint_config()?; + self.write_gateway_key(key_paths.clone(), &GatewayKeyMode::Overwrite)?; + Self::write_gateway_endpoint_config(&key_paths.gateway_endpoint_config)?; } } else { // If we don't have any key paths, just use ephemeral keys diff --git a/sdk/rust/nym-sdk/src/mixnet/key_paths.rs b/sdk/rust/nym-sdk/src/mixnet/key_paths.rs index 630e562b45..374f807c65 100644 --- a/sdk/rust/nym-sdk/src/mixnet/key_paths.rs +++ b/sdk/rust/nym-sdk/src/mixnet/key_paths.rs @@ -54,9 +54,6 @@ pub struct KeyPaths { pub gateway_shared_key: PathBuf, // The key isn't much use without knowing which entity it refers to. - // This is an `Option` in case the end user might want to read write keys to storage, but - // handle the gateway configuration manually through `set_gateway_endpoint`. - // WIP(JON): make it an option? pub gateway_endpoint_config: PathBuf, pub credential_database_path: PathBuf, From 23c13a409af4ac4e97a4faa0d40007b0350ab710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 5 Jan 2023 16:56:09 +0100 Subject: [PATCH 07/18] rust-sdk: rename key_paths to paths --- sdk/rust/nym-sdk/examples/complex_config.rs | 2 +- sdk/rust/nym-sdk/src/mixnet.rs | 4 +- sdk/rust/nym-sdk/src/mixnet/client.rs | 28 +++++------ .../src/mixnet/{key_paths.rs => paths.rs} | 50 +++++-------------- 4 files changed, 30 insertions(+), 54 deletions(-) rename sdk/rust/nym-sdk/src/mixnet/{key_paths.rs => paths.rs} (81%) diff --git a/sdk/rust/nym-sdk/examples/complex_config.rs b/sdk/rust/nym-sdk/examples/complex_config.rs index 2251174e6d..4883ead34f 100644 --- a/sdk/rust/nym-sdk/examples/complex_config.rs +++ b/sdk/rust/nym-sdk/examples/complex_config.rs @@ -11,7 +11,7 @@ async fn main() { // Setting `KeyMode::Keep` will use existing keys, and existing config, if there is one. // Regardles of `user_chosen_gateway`. - let keys = mixnet::KeyPaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir); + let keys = mixnet::StoragePaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir); // Provide key paths for the client to read/write keys to. let mut client = mixnet::Client::new(None, Some(keys)).unwrap(); diff --git a/sdk/rust/nym-sdk/src/mixnet.rs b/sdk/rust/nym-sdk/src/mixnet.rs index fc190053c9..50972de756 100644 --- a/sdk/rust/nym-sdk/src/mixnet.rs +++ b/sdk/rust/nym-sdk/src/mixnet.rs @@ -1,7 +1,7 @@ mod client; mod config; mod connection_state; -mod key_paths; +mod paths; pub use client_core::config::GatewayEndpointConfig; pub use nymsphinx::{ @@ -9,7 +9,7 @@ pub use nymsphinx::{ receiver::ReconstructedMessage, }; -pub use key_paths::{GatewayKeyMode, KeyMode, KeyPaths, Keys}; +pub use paths::{GatewayKeyMode, KeyMode, Keys, StoragePaths}; pub use client::Client; pub use config::Config; diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 5565c6df88..2d0ed126e7 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -19,7 +19,7 @@ use client_core::{ use crate::error::{Error, Result}; -use super::{connection_state::ConnectionState, Config, GatewayKeyMode, KeyPaths, Keys}; +use super::{connection_state::ConnectionState, Config, GatewayKeyMode, StoragePaths, Keys}; pub struct Client { /// Keys handled by the client @@ -29,7 +29,7 @@ pub struct Client { config: Config, /// Paths for client keys, including identity, encryption, ack and shared gateway keys. - key_paths: Option, + storage_paths: Option, /// The client can be in one of multiple states, depending on how it is created and if it's /// connected to the mixnet. @@ -42,20 +42,20 @@ impl Client { /// /// Callers have the option of supplying futher parameters to store persistent identities at a /// location on-disk, if desired. - pub fn new(config_option: Option, key_paths: Option) -> Result { + pub fn new(config_option: Option, paths: Option) -> Result { let config = config_option.unwrap_or_default(); // If we are provided paths to keys, use them if they are available. And if they are // not, write the generated keys back to storage. - let key_manager = if let Some(ref key_paths) = key_paths { - let path_finder = ClientKeyPathfinder::from(key_paths.clone()); + let key_manager = if let Some(ref paths) = paths { + let path_finder = ClientKeyPathfinder::from(paths.clone()); // Try load keys match KeyManager::load_keys_maybe_gateway(&path_finder) { Ok(key_manager) => key_manager, Err(err) => { log::debug!("Not loading keys: {err}"); - if path_finder.any_file_exists() && key_paths.operating_mode.is_keep() { + if path_finder.any_file_exists() && paths.operating_mode.is_keep() { return Err(Error::DontOverwrite); } @@ -74,7 +74,7 @@ impl Client { Ok(Client { key_manager, config, - key_paths, + storage_paths: paths, connection_state: ConnectionState::New, }) } @@ -135,8 +135,8 @@ impl Client { Ok(()) } - fn write_gateway_key(&self, key_paths: KeyPaths, key_mode: &GatewayKeyMode) -> Result<()> { - let path_finder = ClientKeyPathfinder::from(key_paths); + fn write_gateway_key(&self, paths: StoragePaths, key_mode: &GatewayKeyMode) -> Result<()> { + let path_finder = ClientKeyPathfinder::from(paths); if path_finder.gateway_key_file_exists() && key_mode.is_keep() { return Err(Error::DontOverwriteGatewayKey); }; @@ -175,20 +175,20 @@ impl Client { // For some simple cases we can figure how to setup gateway without it having to have been // called in advance. if matches!(self.connection_state, ConnectionState::New) { - if let Some(key_paths) = &self.key_paths { - let key_paths = key_paths.clone(); + if let Some(paths) = &self.storage_paths { + let paths = paths.clone(); if self.has_gateway_key() { // If we have a gateway key from client, then we can just read the corresponding // config println!("Has gateway key: loading"); - self.read_gateway_endpoint_config(&key_paths.gateway_endpoint_config)?; + self.read_gateway_endpoint_config(&paths.gateway_endpoint_config)?; } else { // If we didn't find any shared gateway key during creation, that means we first // need to register a gateway println!("NO gateway key: registering new"); self.register_with_gateway().await?; - self.write_gateway_key(key_paths.clone(), &GatewayKeyMode::Overwrite)?; - Self::write_gateway_endpoint_config(&key_paths.gateway_endpoint_config)?; + self.write_gateway_key(paths.clone(), &GatewayKeyMode::Overwrite)?; + Self::write_gateway_endpoint_config(&paths.gateway_endpoint_config)?; } } else { // If we don't have any key paths, just use ephemeral keys diff --git a/sdk/rust/nym-sdk/src/mixnet/key_paths.rs b/sdk/rust/nym-sdk/src/mixnet/paths.rs similarity index 81% rename from sdk/rust/nym-sdk/src/mixnet/key_paths.rs rename to sdk/rust/nym-sdk/src/mixnet/paths.rs index 374f807c65..8f18c57b2f 100644 --- a/sdk/rust/nym-sdk/src/mixnet/key_paths.rs +++ b/sdk/rust/nym-sdk/src/mixnet/paths.rs @@ -35,7 +35,7 @@ impl GatewayKeyMode { } #[derive(Clone, Debug)] -pub struct KeyPaths { +pub struct StoragePaths { // Determines how to handle existing key files found. pub operating_mode: KeyMode, @@ -56,32 +56,14 @@ pub struct KeyPaths { // The key isn't much use without knowing which entity it refers to. pub gateway_endpoint_config: PathBuf, + // The database containing credentials pub credential_database_path: PathBuf, + // The database storing reply surbs in-between sessions pub reply_surb_database_path: PathBuf, } -pub struct Keys { - pub identity_keypair: identity::KeyPair, - pub encryption_keypair: encryption::KeyPair, - pub ack_key: AckKey, - pub gateway_shared_key: SharedKeys, -} - -//#[derive(Clone, Debug)] -//pub struct GatewaySetup { -// pub gateway_shared_key: PathBuf, -// pub gateway_endpoint_config: GatewayEndpointConfig, -// //pub gateway_endpoint_config: GatewayConfig, -//} - -//#[derive(Clone, Debug)] -//pub enum GatewayConfig { -// File(PathBuf), -// Struct(GatewayEndpointConfig), -//} - -impl KeyPaths { +impl StoragePaths { pub fn new_from_dir(operating_mode: KeyMode, dir: &Path) -> Self { assert!(!dir.is_file(), "WIP"); Self { @@ -101,8 +83,8 @@ impl KeyPaths { } } -impl From for ClientKeyPathfinder { - fn from(paths: KeyPaths) -> Self { +impl From for ClientKeyPathfinder { + fn from(paths: StoragePaths) -> Self { Self { identity_private_key: paths.private_identity, identity_public_key: paths.public_identity, @@ -114,6 +96,13 @@ impl From for ClientKeyPathfinder { } } +pub struct Keys { + pub identity_keypair: identity::KeyPair, + pub encryption_keypair: encryption::KeyPair, + pub ack_key: AckKey, + pub gateway_shared_key: SharedKeys, +} + impl From for KeyManager { fn from(keys: Keys) -> Self { KeyManager::new_from_keys( @@ -124,16 +113,3 @@ impl From for KeyManager { ) } } - -//pub struct GatewayConfigPath { -// pub path: PathBuf, -//} -// -//impl GatewayConfigPath { -// pub fn new_from_dir(dir: PathBuf) -> Self { -// assert!(!dir.is_file(), "WIP"); -// Self { -// path: dir.join("gateway_endpoint_config.toml"), -// } -// } -//} From afae6fc9a5068205a52ed9340a9394a119860ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 5 Jan 2023 17:44:03 +0100 Subject: [PATCH 08/18] rust-sdk: move Keys to its own file --- sdk/rust/nym-sdk/examples/complex_config.rs | 2 +- sdk/rust/nym-sdk/examples/complex_config2.rs | 4 +-- sdk/rust/nym-sdk/examples/simple.rs | 2 +- sdk/rust/nym-sdk/src/mixnet.rs | 4 ++- sdk/rust/nym-sdk/src/mixnet/keys.rs | 22 +++++++++++++++++ sdk/rust/nym-sdk/src/mixnet/paths.rs | 26 +------------------- 6 files changed, 30 insertions(+), 30 deletions(-) create mode 100644 sdk/rust/nym-sdk/src/mixnet/keys.rs diff --git a/sdk/rust/nym-sdk/examples/complex_config.rs b/sdk/rust/nym-sdk/examples/complex_config.rs index 4883ead34f..32d97d1cc8 100644 --- a/sdk/rust/nym-sdk/examples/complex_config.rs +++ b/sdk/rust/nym-sdk/examples/complex_config.rs @@ -17,7 +17,7 @@ async fn main() { let mut client = mixnet::Client::new(None, Some(keys)).unwrap(); // Connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet().await; + client.connect_to_mixnet().await.unwrap(); // Be able to get our client address let our_address = client.nym_address().unwrap(); diff --git a/sdk/rust/nym-sdk/examples/complex_config2.rs b/sdk/rust/nym-sdk/examples/complex_config2.rs index 4e31918935..bacb942b41 100644 --- a/sdk/rust/nym-sdk/examples/complex_config2.rs +++ b/sdk/rust/nym-sdk/examples/complex_config2.rs @@ -17,7 +17,7 @@ async fn main() { // Checks if we have a shared gateway key loaded let first_run = true; if first_run { - client.register_with_gateway().await; + client.register_with_gateway().await.unwrap(); write_to_storage(client.get_keys(), client.get_gateway_endpoint().unwrap()); } else { let (keys, gateway_config) = read_from_storage(); @@ -26,7 +26,7 @@ async fn main() { } // Connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet().await; + client.connect_to_mixnet().await.unwrap(); // Be able to get our client address println!("Our client address is {}", client.nym_address().unwrap()); diff --git a/sdk/rust/nym-sdk/examples/simple.rs b/sdk/rust/nym-sdk/examples/simple.rs index 4a5c96a0ad..35a2af1fae 100644 --- a/sdk/rust/nym-sdk/examples/simple.rs +++ b/sdk/rust/nym-sdk/examples/simple.rs @@ -8,7 +8,7 @@ async fn main() { let mut client = mixnet::Client::new(None, None).unwrap(); // Connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet().await; + client.connect_to_mixnet().await.unwrap(); // Be able to get our client address let our_address = client.nym_address().unwrap(); diff --git a/sdk/rust/nym-sdk/src/mixnet.rs b/sdk/rust/nym-sdk/src/mixnet.rs index 50972de756..a717b022ab 100644 --- a/sdk/rust/nym-sdk/src/mixnet.rs +++ b/sdk/rust/nym-sdk/src/mixnet.rs @@ -1,6 +1,7 @@ mod client; mod config; mod connection_state; +mod keys; mod paths; pub use client_core::config::GatewayEndpointConfig; @@ -9,7 +10,8 @@ pub use nymsphinx::{ receiver::ReconstructedMessage, }; -pub use paths::{GatewayKeyMode, KeyMode, Keys, StoragePaths}; +pub use keys::Keys; +pub use paths::{GatewayKeyMode, KeyMode, StoragePaths}; pub use client::Client; pub use config::Config; diff --git a/sdk/rust/nym-sdk/src/mixnet/keys.rs b/sdk/rust/nym-sdk/src/mixnet/keys.rs new file mode 100644 index 0000000000..cc1940f3cc --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/keys.rs @@ -0,0 +1,22 @@ +use client_core::client::key_manager::KeyManager; +use crypto::asymmetric::{encryption, identity}; +use gateway_requests::registration::handshake::SharedKeys; +use nymsphinx::acknowledgements::AckKey; + +pub struct Keys { + pub identity_keypair: identity::KeyPair, + pub encryption_keypair: encryption::KeyPair, + pub ack_key: AckKey, + pub gateway_shared_key: SharedKeys, +} + +impl From for KeyManager { + fn from(keys: Keys) -> Self { + KeyManager::new_from_keys( + keys.identity_keypair, + keys.encryption_keypair, + keys.gateway_shared_key, + keys.ack_key, + ) + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/paths.rs b/sdk/rust/nym-sdk/src/mixnet/paths.rs index 8f18c57b2f..7a824ecce4 100644 --- a/sdk/rust/nym-sdk/src/mixnet/paths.rs +++ b/sdk/rust/nym-sdk/src/mixnet/paths.rs @@ -1,10 +1,4 @@ -use client_core::{ - client::key_manager::KeyManager, config::persistence::key_pathfinder::ClientKeyPathfinder, -}; -use crypto::asymmetric::{encryption, identity}; -use gateway_requests::registration::handshake::SharedKeys; -use nymsphinx::acknowledgements::AckKey; - +use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use std::path::{Path, PathBuf}; #[derive(Clone, Debug)] @@ -95,21 +89,3 @@ impl From for ClientKeyPathfinder { } } } - -pub struct Keys { - pub identity_keypair: identity::KeyPair, - pub encryption_keypair: encryption::KeyPair, - pub ack_key: AckKey, - pub gateway_shared_key: SharedKeys, -} - -impl From for KeyManager { - fn from(keys: Keys) -> Self { - KeyManager::new_from_keys( - keys.identity_keypair, - keys.encryption_keypair, - keys.gateway_shared_key, - keys.ack_key, - ) - } -} From 326d5fcec8e3eb4e436a8cb56ee2a491e4740ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 9 Jan 2023 10:02:46 +0100 Subject: [PATCH 09/18] rustfmt --- sdk/rust/nym-sdk/src/mixnet/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 2d0ed126e7..ec7f267dd8 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -19,7 +19,7 @@ use client_core::{ use crate::error::{Error, Result}; -use super::{connection_state::ConnectionState, Config, GatewayKeyMode, StoragePaths, Keys}; +use super::{connection_state::ConnectionState, Config, GatewayKeyMode, Keys, StoragePaths}; pub struct Client { /// Keys handled by the client From 95db26c35baccc6fede91d19e0e698f1ec2e1d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 9 Jan 2023 10:43:42 +0100 Subject: [PATCH 10/18] changelog: add note --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a372b8376..985d149645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ### Added - socks5: send status message for service ready, and network-requester error response +- nym-sdk: added initial version of a Rust client sdk ### Changed From 11e2ba33e7bc0a2799af3ffb7421f43b21378c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 9 Jan 2023 10:44:02 +0100 Subject: [PATCH 11/18] fix compilation after merge in latest develop --- sdk/rust/nym-sdk/src/mixnet/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/rust/nym-sdk/src/mixnet/config.rs b/sdk/rust/nym-sdk/src/mixnet/config.rs index 92abf0560d..641b1c01a5 100644 --- a/sdk/rust/nym-sdk/src/mixnet/config.rs +++ b/sdk/rust/nym-sdk/src/mixnet/config.rs @@ -16,7 +16,7 @@ pub struct Config { impl Default for Config { fn default() -> Self { - let nym_api_endpoints = vec![mainnet::API_VALIDATOR.to_string().parse().unwrap()]; + let nym_api_endpoints = vec![mainnet::NYM_API.to_string().parse().unwrap()]; Self { user_chosen_gateway: Default::default(), nym_api_endpoints, From 96ab4325e38bf4cf96da8d2e81696e20ffa2c1ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 9 Jan 2023 12:39:23 +0100 Subject: [PATCH 12/18] rust-sdk: tidy --- clients/client-core/src/client/key_manager.rs | 20 +++++----- ....rs => manually_handle_keys_and_config.rs} | 38 ++++++++++++++----- .../{complex_config.rs => persist_keys.rs} | 0 sdk/rust/nym-sdk/src/error.rs | 2 + sdk/rust/nym-sdk/src/mixnet/client.rs | 17 +++++---- sdk/rust/nym-sdk/src/mixnet/keys.rs | 2 +- 6 files changed, 51 insertions(+), 28 deletions(-) rename sdk/rust/nym-sdk/examples/{complex_config2.rs => manually_handle_keys_and_config.rs} (52%) rename sdk/rust/nym-sdk/examples/{complex_config.rs => persist_keys.rs} (100%) diff --git a/clients/client-core/src/client/key_manager.rs b/clients/client-core/src/client/key_manager.rs index 69192a3394..83c9ba61f2 100644 --- a/clients/client-core/src/client/key_manager.rs +++ b/clients/client-core/src/client/key_manager.rs @@ -17,7 +17,6 @@ use std::sync::Arc; // use the old key after new one was issued. // Remember that Arc has Deref implementation for T -// WIP(JON): let's try not to have the clone #[derive(Clone)] pub struct KeyManager { /// identity key associated with the client instance. @@ -59,7 +58,7 @@ impl KeyManager { } } - pub fn new_from_keys( + pub fn from_keys( id_keypair: identity::KeyPair, enc_keypair: encryption::KeyPair, gateway_shared_key: SharedKeys, @@ -82,7 +81,7 @@ impl KeyManager { } /// Loads previously stored client keys from the disk. - fn load_keys_client_only(client_pathfinder: &ClientKeyPathfinder) -> io::Result { + fn load_client_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result { let identity_keypair: identity::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new( client_pathfinder.private_identity_key().to_owned(), @@ -107,7 +106,7 @@ impl KeyManager { /// Loads previously stored keys from the disk. Fails if not all, including the shared gateway /// key, is available. pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result { - let mut key_manager = Self::load_keys_client_only(client_pathfinder)?; + let mut key_manager = Self::load_client_keys(client_pathfinder)?; let gateway_shared_key: SharedKeys = pemstore::load_key(client_pathfinder.gateway_shared_key())?; @@ -117,8 +116,12 @@ impl KeyManager { Ok(key_manager) } - pub fn load_keys_maybe_gateway(client_pathfinder: &ClientKeyPathfinder) -> io::Result { - let mut key_manager = Self::load_keys_client_only(client_pathfinder)?; + /// Loads previously stored keys from the disk. Fails if client keys are not availabe, but the + /// shared gateway key is optional. + pub fn load_keys_but_gateway_is_optional( + client_pathfinder: &ClientKeyPathfinder, + ) -> io::Result { + let mut key_manager = Self::load_client_keys(client_pathfinder)?; let gateway_shared_key: Result = pemstore::load_key(client_pathfinder.gateway_shared_key()); @@ -170,10 +173,7 @@ impl KeyManager { Ok(()) } - pub fn store_key_gateway_only( - &self, - client_pathfinder: &ClientKeyPathfinder, - ) -> io::Result<()> { + pub fn store_gateway_key(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> { match self.gateway_shared_key.as_ref() { None => { return Err(io::Error::new( diff --git a/sdk/rust/nym-sdk/examples/complex_config2.rs b/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs similarity index 52% rename from sdk/rust/nym-sdk/examples/complex_config2.rs rename to sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs index bacb942b41..c9c196aad9 100644 --- a/sdk/rust/nym-sdk/examples/complex_config2.rs +++ b/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs @@ -6,21 +6,24 @@ async fn main() { // We can set a few options let user_chosen_gateway_id = None; - let nym_api_endpointgs = vec![]; - let config = mixnet::Config::new(user_chosen_gateway_id, nym_api_endpointgs); + let nym_api_endpoints = vec!["https://validator.nymtech.net/api/".parse().unwrap()]; + + let config = mixnet::Config::new(user_chosen_gateway_id, nym_api_endpoints); let mut client = mixnet::Client::new(Some(config), None).unwrap(); + // Just some plain data to pretend we have some external storage that the application + // implementer is using. + let mut mock_storage = MockStorage::empty(); + // In this we want to provide our own gateway config struct, and handle persisting this info to disk // ourselves (e.g., as part of our own configuration file). - // NOTE: gateway shared key is written to disk according to the path given earlier - // Checks if we have a shared gateway key loaded let first_run = true; if first_run { client.register_with_gateway().await.unwrap(); - write_to_storage(client.get_keys(), client.get_gateway_endpoint().unwrap()); + mock_storage.write(client.get_keys(), client.get_gateway_endpoint().unwrap()); } else { - let (keys, gateway_config) = read_from_storage(); + let (keys, gateway_config) = mock_storage.read(); client.set_keys(&keys); client.set_gateway_endpoint(&gateway_config); } @@ -35,10 +38,25 @@ async fn main() { client.send_str("foo.bar@blah", "flappappa").await; } -fn write_to_storage(_keys: &mixnet::Keys, _gateway_config: &mixnet::GatewayEndpointConfig) { - todo!(); +#[allow(unused)] +struct MockStorage { + pub gateway_config: Option, + pub keys: Option>, } -fn read_from_storage() -> (mixnet::Keys, mixnet::GatewayEndpointConfig) { - todo!(); +impl MockStorage { + fn read(&self) -> (mixnet::Keys, mixnet::GatewayEndpointConfig) { + todo!(); + } + + fn write(&mut self, _keys: &mixnet::Keys, _gateway_config: &mixnet::GatewayEndpointConfig) { + todo!(); + } + + fn empty() -> Self { + Self { + gateway_config: None, + keys: None, + } + } } diff --git a/sdk/rust/nym-sdk/examples/complex_config.rs b/sdk/rust/nym-sdk/examples/persist_keys.rs similarity index 100% rename from sdk/rust/nym-sdk/examples/complex_config.rs rename to sdk/rust/nym-sdk/examples/persist_keys.rs diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs index cdc6f4fa2d..18e5718e13 100644 --- a/sdk/rust/nym-sdk/src/error.rs +++ b/sdk/rust/nym-sdk/src/error.rs @@ -12,6 +12,8 @@ pub enum Error { DontOverwrite, #[error("shared gateway key file encountered that we don't want to overwrite")] DontOverwriteGatewayKey, + #[error("no gateway config available for writing")] + GatewayNotAvailableForWriting, } pub type Result = std::result::Result; diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index ec7f267dd8..70b64ad9f2 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -51,7 +51,7 @@ impl Client { let path_finder = ClientKeyPathfinder::from(paths.clone()); // Try load keys - match KeyManager::load_keys_maybe_gateway(&path_finder) { + match KeyManager::load_keys_but_gateway_is_optional(&path_finder) { Ok(key_manager) => key_manager, Err(err) => { log::debug!("Not loading keys: {err}"); @@ -140,12 +140,15 @@ impl Client { if path_finder.gateway_key_file_exists() && key_mode.is_keep() { return Err(Error::DontOverwriteGatewayKey); }; - self.key_manager.store_key_gateway_only(&path_finder)?; + self.key_manager.store_gateway_key(&path_finder)?; Ok(()) } - fn write_gateway_endpoint_config(gateway_endpoint_config_path: &Path) -> Result<()> { - let gateway_endpoint_config = toml::to_string(gateway_endpoint_config_path)?; + fn write_gateway_endpoint_config(&self, gateway_endpoint_config_path: &Path) -> Result<()> { + let gateway_endpoint_config = toml::to_string( + self.get_gateway_endpoint() + .ok_or(Error::GatewayNotAvailableForWriting)?, + )?; // Ensure the whole directory structure exists if let Some(parent_dir) = gateway_endpoint_config_path.parent() { @@ -180,15 +183,15 @@ impl Client { if self.has_gateway_key() { // If we have a gateway key from client, then we can just read the corresponding // config - println!("Has gateway key: loading"); + log::trace!("Gateway key found: loading"); self.read_gateway_endpoint_config(&paths.gateway_endpoint_config)?; } else { // If we didn't find any shared gateway key during creation, that means we first // need to register a gateway - println!("NO gateway key: registering new"); + log::trace!("Gateway key NOT found: registering new"); self.register_with_gateway().await?; self.write_gateway_key(paths.clone(), &GatewayKeyMode::Overwrite)?; - Self::write_gateway_endpoint_config(&paths.gateway_endpoint_config)?; + self.write_gateway_endpoint_config(&paths.gateway_endpoint_config)?; } } else { // If we don't have any key paths, just use ephemeral keys diff --git a/sdk/rust/nym-sdk/src/mixnet/keys.rs b/sdk/rust/nym-sdk/src/mixnet/keys.rs index cc1940f3cc..c6a60f3494 100644 --- a/sdk/rust/nym-sdk/src/mixnet/keys.rs +++ b/sdk/rust/nym-sdk/src/mixnet/keys.rs @@ -12,7 +12,7 @@ pub struct Keys { impl From for KeyManager { fn from(keys: Keys) -> Self { - KeyManager::new_from_keys( + KeyManager::from_keys( keys.identity_keypair, keys.encryption_keypair, keys.gateway_shared_key, From 666cbcf2cc8507dff4287249ec243a3ab5003f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 10 Jan 2023 01:36:27 +0100 Subject: [PATCH 13/18] rust-sdk: extract out builder --- .../manually_handle_keys_and_config.rs | 6 +- sdk/rust/nym-sdk/examples/persist_keys.rs | 6 +- sdk/rust/nym-sdk/examples/simple.rs | 7 +- sdk/rust/nym-sdk/src/mixnet.rs | 2 +- sdk/rust/nym-sdk/src/mixnet/client.rs | 156 +++++++++--------- .../nym-sdk/src/mixnet/connection_state.rs | 110 +----------- 6 files changed, 99 insertions(+), 188 deletions(-) diff --git a/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs b/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs index c9c196aad9..ed215d30af 100644 --- a/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs +++ b/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs @@ -10,7 +10,7 @@ async fn main() { let config = mixnet::Config::new(user_chosen_gateway_id, nym_api_endpoints); - let mut client = mixnet::Client::new(Some(config), None).unwrap(); + let mut client = mixnet::ClientBuilder::new(Some(config), None).unwrap(); // Just some plain data to pretend we have some external storage that the application // implementer is using. @@ -29,10 +29,10 @@ async fn main() { } // Connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet().await.unwrap(); + let client = client.connect_to_mixnet().await.unwrap(); // Be able to get our client address - println!("Our client address is {}", client.nym_address().unwrap()); + println!("Our client address is {}", client.nym_address()); // Send important info up the pipe to a buddy client.send_str("foo.bar@blah", "flappappa").await; diff --git a/sdk/rust/nym-sdk/examples/persist_keys.rs b/sdk/rust/nym-sdk/examples/persist_keys.rs index 32d97d1cc8..0e88293764 100644 --- a/sdk/rust/nym-sdk/examples/persist_keys.rs +++ b/sdk/rust/nym-sdk/examples/persist_keys.rs @@ -14,13 +14,13 @@ async fn main() { let keys = mixnet::StoragePaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir); // Provide key paths for the client to read/write keys to. - let mut client = mixnet::Client::new(None, Some(keys)).unwrap(); + let client = mixnet::ClientBuilder::new(None, Some(keys)).unwrap(); // Connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet().await.unwrap(); + let mut client = client.connect_to_mixnet().await.unwrap(); // Be able to get our client address - let our_address = client.nym_address().unwrap(); + let our_address = client.nym_address(); println!("Our client nym address is: {our_address}"); // Send a message throught the mixnet to ourselves diff --git a/sdk/rust/nym-sdk/examples/simple.rs b/sdk/rust/nym-sdk/examples/simple.rs index 35a2af1fae..a61833cb0e 100644 --- a/sdk/rust/nym-sdk/examples/simple.rs +++ b/sdk/rust/nym-sdk/examples/simple.rs @@ -5,13 +5,10 @@ async fn main() { logging::setup_logging(); // Passing no config makes the client fire up an ephemeral session and figure shit out on its own - let mut client = mixnet::Client::new(None, None).unwrap(); - - // Connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet().await.unwrap(); + let mut client = mixnet::Client::connect().await.unwrap(); // Be able to get our client address - let our_address = client.nym_address().unwrap(); + let our_address = client.nym_address(); println!("Our client nym address is: {our_address}"); // Send a message throught the mixnet to ourselves diff --git a/sdk/rust/nym-sdk/src/mixnet.rs b/sdk/rust/nym-sdk/src/mixnet.rs index a717b022ab..21d9369790 100644 --- a/sdk/rust/nym-sdk/src/mixnet.rs +++ b/sdk/rust/nym-sdk/src/mixnet.rs @@ -13,5 +13,5 @@ pub use nymsphinx::{ pub use keys::Keys; pub use paths::{GatewayKeyMode, KeyMode, StoragePaths}; -pub use client::Client; +pub use client::{Client, ClientBuilder}; pub use config::Config; diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 70b64ad9f2..bcda8f5603 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -1,27 +1,29 @@ use std::path::Path; +use client_connections::TransmissionLane; +use client_core::{ + client::{ + base_client::{ + non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState, + CredentialsToggle, + }, + inbound_messages::InputMessage, + key_manager::KeyManager, + received_buffer::ReconstructedMessagesReceiver, + }, + config::{persistence::key_pathfinder::ClientKeyPathfinder, GatewayEndpointConfig}, +}; use futures::StreamExt; use nymsphinx::{ addressing::clients::{ClientIdentity, Recipient}, receiver::ReconstructedMessage, }; -use tap::TapOptional; - -use client_connections::TransmissionLane; -use client_core::{ - client::{ - base_client::{non_wasm_helpers, BaseClientBuilder, CredentialsToggle}, - inbound_messages::InputMessage, - key_manager::KeyManager, - }, - config::{persistence::key_pathfinder::ClientKeyPathfinder, GatewayEndpointConfig}, -}; +use task::TaskManager; +use super::{connection_state::BuilderState, Config, GatewayKeyMode, Keys, StoragePaths}; use crate::error::{Error, Result}; -use super::{connection_state::ConnectionState, Config, GatewayKeyMode, Keys, StoragePaths}; - -pub struct Client { +pub struct ClientBuilder { /// Keys handled by the client key_manager: KeyManager, @@ -33,16 +35,35 @@ pub struct Client { /// The client can be in one of multiple states, depending on how it is created and if it's /// connected to the mixnet. - connection_state: ConnectionState, + state: BuilderState, } -impl Client { +pub struct Client { + nym_address: Recipient, + + /// Keys handled by the client + key_manager: KeyManager, + + client_input: ClientInput, + + #[allow(dead_code)] + client_output: ClientOutput, + + #[allow(dead_code)] + client_state: ClientState, + + reconstructed_receiver: ReconstructedMessagesReceiver, + + task_manager: TaskManager, +} + +impl ClientBuilder { /// Create a new mixnet client. If no config options are supplied, creates a new client with /// ephemeral keys stored in RAM, which will be discarded at application close. /// /// Callers have the option of supplying futher parameters to store persistent identities at a /// location on-disk, if desired. - pub fn new(config_option: Option, paths: Option) -> Result { + pub fn new(config_option: Option, paths: Option) -> Result { let config = config_option.unwrap_or_default(); // If we are provided paths to keys, use them if they are available. And if they are @@ -71,29 +92,18 @@ impl Client { client_core::init::new_client_keys() }; - Ok(Client { + Ok(Self { key_manager, config, storage_paths: paths, - connection_state: ConnectionState::New, + state: BuilderState::New, }) } - /// Get the client identity, which is the public key of the identity key pair. - pub fn identity(&self) -> ClientIdentity { - *self.key_manager.identity_keypair().public_key() - } - - /// Get the nym address for this client, if it is available. The nym address is composed of the - /// client identity, the client encryption key, and the gateway identity. - pub fn nym_address(&self) -> Option<&Recipient> { - self.connection_state.nym_address() - } - /// Client keys are generated at client creation if none were found. The gateway shared /// key, however, is created during the gateway registration handshake so it might not /// necessarily be available. - pub fn has_gateway_key(&self) -> bool { + fn has_gateway_key(&self) -> bool { self.key_manager.gateway_key_set() } @@ -110,12 +120,12 @@ impl Client { } pub fn get_gateway_endpoint(&self) -> Option<&GatewayEndpointConfig> { - self.connection_state.gateway_endpoint_config() + self.state.gateway_endpoint_config() } pub async fn register_with_gateway(&mut self) -> Result<()> { assert!( - matches!(self.connection_state, ConnectionState::New), + matches!(self.state, BuilderState::New), "can only setup gateway when in `New` connection state" ); @@ -126,11 +136,8 @@ impl Client { ) .await?; - let nym_address = client_core::init::get_client_address(&self.key_manager, &gateway_config); - - self.connection_state = ConnectionState::Registered { + self.state = BuilderState::Registered { gateway_endpoint_config: gateway_config, - nym_address, }; Ok(()) } @@ -163,21 +170,17 @@ impl Client { std::fs::read_to_string(gateway_endpoint_config_path) .map(|str| toml::from_str(&str))??; - let nym_address = - client_core::init::get_client_address(&self.key_manager, &gateway_endpoint_config); - - self.connection_state = ConnectionState::Registered { + self.state = BuilderState::Registered { gateway_endpoint_config, - nym_address, }; Ok(()) } /// Connects to the mixnet via the gateway in the client config - pub async fn connect_to_mixnet(&mut self) -> Result<()> { + pub async fn connect_to_mixnet(mut self) -> Result { // For some simple cases we can figure how to setup gateway without it having to have been // called in advance. - if matches!(self.connection_state, ConnectionState::New) { + if matches!(self.state, BuilderState::New) { if let Some(paths) = &self.storage_paths { let paths = paths.clone(); if self.has_gateway_key() { @@ -201,12 +204,12 @@ impl Client { // At this point we should be in a registered state, either at function entry or by the // above convenience logic. - assert!(matches!( - self.connection_state, - ConnectionState::Registered { .. } - )); + let BuilderState::Registered { gateway_endpoint_config } = self.state else { + todo!(); + }; - let gateway_config = self.connection_state.gateway_endpoint_config().unwrap(); + let nym_address = + client_core::init::get_client_address(&self.key_manager, &gateway_endpoint_config); // TODO: we currently don't support having a bandwidth controller let bandwidth_controller = None; @@ -216,7 +219,7 @@ impl Client { non_wasm_helpers::setup_empty_reply_surb_backend(&self.config.debug_config); let base_builder = BaseClientBuilder::new( - gateway_config, + &gateway_endpoint_config, &self.config.debug_config, self.key_manager.clone(), bandwidth_controller, @@ -225,8 +228,6 @@ impl Client { self.config.nym_api_endpoints.clone(), ); - let nym_address = base_builder.as_mix_recipient(); - let mut started_client = base_builder.start_base().await.unwrap(); let client_input = started_client.client_input.register_producer(); let mut client_output = started_client.client_output.register_consumer(); @@ -235,15 +236,33 @@ impl Client { // Register our receiver let reconstructed_receiver = client_output.register_receiver().unwrap(); - self.connection_state = ConnectionState::Connected { + Ok(Client { nym_address, + key_manager: self.key_manager, client_input, client_output, client_state, reconstructed_receiver, task_manager: started_client.task_manager, - }; - Ok(()) + }) + } +} + +impl Client { + pub async fn connect() -> Result { + let client = ClientBuilder::new(None, None)?; + client.connect_to_mixnet().await + } + + /// Get the client identity, which is the public key of the identity key pair. + pub fn identity(&self) -> ClientIdentity { + *self.key_manager.identity_keypair().public_key() + } + + /// Get the nym address for this client, if it is available. The nym address is composed of the + /// client identity, the client encryption key, and the gateway identity. + pub fn nym_address(&self) -> &Recipient { + &self.nym_address } /// Sends stringy data to the supplied Nym address @@ -256,25 +275,20 @@ impl Client { /// Sends bytes to the supplied Nym address pub async fn send_bytes(&self, address: &str, message: Vec) { log::debug!("send_bytes"); - let Some(client_input) = self.connection_state.client_input() else { - log::error!("Error: trying to send without being connected"); - return; - }; let lane = TransmissionLane::General; let recipient = Recipient::try_from_base58_string(address).unwrap(); let input_msg = InputMessage::new_regular(recipient, message, lane); - client_input.input_sender.send(input_msg).await.unwrap(); + self.client_input + .input_sender + .send(input_msg) + .await + .unwrap(); } /// Wait for messages from the mixnet pub async fn wait_for_messages(&mut self) -> Option> { - let receiver = self - .connection_state - .reconstructed_receiver() - .tap_none(|| log::error!("Error: trying to wait without being connected"))?; - - receiver.next().await + self.reconstructed_receiver.next().await } pub fn wait_for_messages_split(&mut self) -> Option { @@ -295,13 +309,7 @@ impl Client { /// Disconnect from the mixnet. Currently it is not supported to reconnect a disconnected /// client. pub async fn disconnect(&mut self) { - let Some(task_manager) = self.connection_state.task_manager() else { - log::error!("Trying to disconnect when not connected!"); - return; - }; - - task_manager.signal_shutdown().ok(); - task_manager.wait_for_shutdown().await; - self.connection_state = ConnectionState::Disconnected; + self.task_manager.signal_shutdown().ok(); + self.task_manager.wait_for_shutdown().await; } } diff --git a/sdk/rust/nym-sdk/src/mixnet/connection_state.rs b/sdk/rust/nym-sdk/src/mixnet/connection_state.rs index 5336143dc5..2988948a60 100644 --- a/sdk/rust/nym-sdk/src/mixnet/connection_state.rs +++ b/sdk/rust/nym-sdk/src/mixnet/connection_state.rs @@ -1,115 +1,21 @@ -use client_core::{ - client::{ - base_client::{ClientInput, ClientOutput, ClientState}, - received_buffer::ReconstructedMessagesReceiver, - }, - config::GatewayEndpointConfig, -}; -use nymsphinx::addressing::clients::Recipient; -use task::TaskManager; +use client_core::config::GatewayEndpointConfig; -/// States that the client connection can be in. Currently the states can only progress linearly -/// from New -> Registered -> Connected -> Disconnected. In the future it could be useful to be able -/// to re-connect a disconnected client. -// WIP(JON): consider adding inner types -#[allow(clippy::large_enum_variant)] -pub(super) enum ConnectionState { +#[derive(Debug)] +pub(super) enum BuilderState { New, Registered { - nym_address: Recipient, gateway_endpoint_config: GatewayEndpointConfig, }, - Connected { - nym_address: Recipient, - client_input: ClientInput, - #[allow(dead_code)] - client_output: ClientOutput, - #[allow(dead_code)] - client_state: ClientState, - reconstructed_receiver: ReconstructedMessagesReceiver, - task_manager: TaskManager, - }, - Disconnected, } -impl std::fmt::Debug for ConnectionState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::New => write!(f, "New"), - Self::Registered { - nym_address, - gateway_endpoint_config: gateway_config, - } => f - .debug_struct("Registered") - .field("nym_address", nym_address) - .field("gateway_config", gateway_config) - .finish(), - Self::Connected { - nym_address, - client_input: _, - client_output: _, - client_state: _, - reconstructed_receiver, - task_manager, - } => f - .debug_struct("Connected") - .field("nym_address", nym_address) - .field("reconstructed_receiver", reconstructed_receiver) - .field("task_manager", task_manager) - .finish(), - Self::Disconnected => write!(f, "Disconnected"), - } - } -} - -impl ConnectionState { - pub(super) fn client_input(&self) -> Option<&ClientInput> { - match self { - ConnectionState::New - | ConnectionState::Registered { .. } - | ConnectionState::Disconnected => None, - ConnectionState::Connected { client_input, .. } => Some(client_input), - } - } - - pub(super) fn reconstructed_receiver(&mut self) -> Option<&mut ReconstructedMessagesReceiver> { - match self { - ConnectionState::New - | ConnectionState::Registered { .. } - | ConnectionState::Disconnected => None, - ConnectionState::Connected { - reconstructed_receiver, - .. - } => Some(reconstructed_receiver), - } - } - +impl BuilderState { pub(super) fn gateway_endpoint_config(&self) -> Option<&GatewayEndpointConfig> { match self { - ConnectionState::New - | ConnectionState::Connected { .. } - | ConnectionState::Disconnected => None, - ConnectionState::Registered { - gateway_endpoint_config: gateway_config, + BuilderState::New => None, + BuilderState::Registered { + gateway_endpoint_config, .. - } => Some(gateway_config), - } - } - - pub(super) fn nym_address(&self) -> Option<&Recipient> { - match self { - ConnectionState::New | ConnectionState::Disconnected => None, - ConnectionState::Registered { nym_address, .. } - | ConnectionState::Connected { nym_address, .. } => Some(nym_address), - } - } - - pub(super) fn task_manager(&mut self) -> Option<&mut TaskManager> { - match self { - ConnectionState::New - | ConnectionState::Registered { .. } - | ConnectionState::Disconnected => None, - ConnectionState::Connected { task_manager, .. } => Some(task_manager), + } => Some(gateway_endpoint_config), } } } From 2746cabeccf2da5c35266dc6531015a14f5321df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 10 Jan 2023 01:43:50 +0100 Subject: [PATCH 14/18] rust-sdk: reorder in client file --- sdk/rust/nym-sdk/src/mixnet/client.rs | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index bcda8f5603..ea2f5f372f 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -38,25 +38,6 @@ pub struct ClientBuilder { state: BuilderState, } -pub struct Client { - nym_address: Recipient, - - /// Keys handled by the client - key_manager: KeyManager, - - client_input: ClientInput, - - #[allow(dead_code)] - client_output: ClientOutput, - - #[allow(dead_code)] - client_state: ClientState, - - reconstructed_receiver: ReconstructedMessagesReceiver, - - task_manager: TaskManager, -} - impl ClientBuilder { /// Create a new mixnet client. If no config options are supplied, creates a new client with /// ephemeral keys stored in RAM, which will be discarded at application close. @@ -248,6 +229,25 @@ impl ClientBuilder { } } +pub struct Client { + nym_address: Recipient, + + /// Keys handled by the client + key_manager: KeyManager, + + client_input: ClientInput, + + #[allow(dead_code)] + client_output: ClientOutput, + + #[allow(dead_code)] + client_state: ClientState, + + reconstructed_receiver: ReconstructedMessagesReceiver, + + task_manager: TaskManager, +} + impl Client { pub async fn connect() -> Result { let client = ClientBuilder::new(None, None)?; From df3d478caa7d38658cb631005d7fa9ecdf530460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 10 Jan 2023 23:28:26 +0100 Subject: [PATCH 15/18] rust-sdk: make new_from_dir return result --- Cargo.lock | 31 +++++++++++++++-------- sdk/rust/nym-sdk/examples/persist_keys.rs | 2 +- sdk/rust/nym-sdk/src/error.rs | 6 +++++ sdk/rust/nym-sdk/src/mixnet/paths.rs | 13 +++++++--- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5e6151786..eaa4d1aad9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -686,7 +686,7 @@ dependencies = [ [[package]] name = "client-core" -version = "1.1.4" +version = "1.1.5" dependencies = [ "async-trait", "client-connections", @@ -1788,7 +1788,7 @@ dependencies = [ [[package]] name = "explorer-api" -version = "1.1.1" +version = "1.1.2" dependencies = [ "chrono", "clap 4.0.26", @@ -2085,6 +2085,7 @@ dependencies = [ "nymsphinx", "pemstore", "rand 0.7.3", + "serde", "task", "thiserror", "tokio", @@ -3233,7 +3234,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.4" +version = "1.1.5" dependencies = [ "anyhow", "async-trait", @@ -3329,7 +3330,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.4" +version = "1.1.5" dependencies = [ "anyhow", "base64", @@ -3360,14 +3361,18 @@ dependencies = [ "bs58", "cfg-if 1.0.0", "clap 4.0.26", + "coconut-bandwidth-contract-common", + "coconut-dkg-common", "comfy-table", "cosmrs", "cosmwasm-std", + "cw-utils", "handlebars", "humantime-serde", "k256", "log", "mixnet-contract-common", + "multisig-contract-common", "network-defaults", "rand 0.6.5", "serde", @@ -3383,7 +3388,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.4" +version = "1.1.5" dependencies = [ "build-information", "clap 4.0.26", @@ -3423,10 +3428,11 @@ dependencies = [ [[package]] name = "nym-gateway" -version = "1.1.4" +version = "1.1.5" dependencies = [ "anyhow", "async-trait", + "atty", "bip39", "bs58", "build-information", @@ -3450,12 +3456,14 @@ dependencies = [ "mixnode-common", "network-defaults", "nym-api-requests", + "nym-types", "nymsphinx", "once_cell", "pemstore", "pretty_env_logger", "rand 0.7.3", "serde", + "serde_json", "sqlx 0.5.11", "statistics-common", "subtle-encoding", @@ -3471,9 +3479,10 @@ dependencies = [ [[package]] name = "nym-mixnode" -version = "1.1.4" +version = "1.1.5" dependencies = [ "anyhow", + "atty", "bs58", "build-information", "clap 4.0.26", @@ -3492,6 +3501,7 @@ dependencies = [ "mixnet-client", "mixnode-common", "nonexhaustive-delayqueue", + "nym-types", "nymsphinx", "nymsphinx-params", "nymsphinx-types", @@ -3500,6 +3510,7 @@ dependencies = [ "rand 0.7.3", "rocket", "serde", + "serde_json", "sysinfo", "task", "tokio", @@ -3513,7 +3524,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.4" +version = "1.1.5" dependencies = [ "async-trait", "clap 4.0.26", @@ -3545,7 +3556,7 @@ dependencies = [ [[package]] name = "nym-network-statistics" -version = "1.1.4" +version = "1.1.5" dependencies = [ "dirs", "log", @@ -3601,7 +3612,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.4" +version = "1.1.5" dependencies = [ "build-information", "clap 4.0.26", diff --git a/sdk/rust/nym-sdk/examples/persist_keys.rs b/sdk/rust/nym-sdk/examples/persist_keys.rs index 0e88293764..8cf0567edc 100644 --- a/sdk/rust/nym-sdk/examples/persist_keys.rs +++ b/sdk/rust/nym-sdk/examples/persist_keys.rs @@ -11,7 +11,7 @@ async fn main() { // Setting `KeyMode::Keep` will use existing keys, and existing config, if there is one. // Regardles of `user_chosen_gateway`. - let keys = mixnet::StoragePaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir); + let keys = mixnet::StoragePaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir).unwrap(); // Provide key paths for the client to read/write keys to. let client = mixnet::ClientBuilder::new(None, Some(keys)).unwrap(); diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs index 18e5718e13..77f6c1492c 100644 --- a/sdk/rust/nym-sdk/src/error.rs +++ b/sdk/rust/nym-sdk/src/error.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("i/o error: {0}")] @@ -8,12 +10,16 @@ pub enum Error { TomlDeserializationError(#[from] toml::de::Error), #[error(transparent)] ClientCoreError(#[from] client_core::error::ClientCoreError), + #[error("key file encountered that we don't want to overwrite")] DontOverwrite, #[error("shared gateway key file encountered that we don't want to overwrite")] DontOverwriteGatewayKey, #[error("no gateway config available for writing")] GatewayNotAvailableForWriting, + + #[error("expected to received a directory, received: {0}")] + ExpectedDirectory(PathBuf), } pub type Result = std::result::Result; diff --git a/sdk/rust/nym-sdk/src/mixnet/paths.rs b/sdk/rust/nym-sdk/src/mixnet/paths.rs index 7a824ecce4..ad66f7bbcd 100644 --- a/sdk/rust/nym-sdk/src/mixnet/paths.rs +++ b/sdk/rust/nym-sdk/src/mixnet/paths.rs @@ -1,6 +1,8 @@ use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use std::path::{Path, PathBuf}; +use crate::error::{Error, Result}; + #[derive(Clone, Debug)] pub enum KeyMode { /// Use existing key files if they exists, otherwise create new ones. @@ -58,9 +60,12 @@ pub struct StoragePaths { } impl StoragePaths { - pub fn new_from_dir(operating_mode: KeyMode, dir: &Path) -> Self { - assert!(!dir.is_file(), "WIP"); - Self { + pub fn new_from_dir(operating_mode: KeyMode, dir: &Path) -> Result { + if !dir.is_file() { + return Err(Error::ExpectedDirectory(dir.to_owned())) + } + + Ok(Self { // These filenames were chosen to match the ones we use in `nym-client`. Consider // changing the defaults operating_mode, @@ -73,7 +78,7 @@ impl StoragePaths { gateway_endpoint_config: dir.join("gateway_endpoint_config.toml"), credential_database_path: dir.join("db.sqlite"), reply_surb_database_path: dir.join("persistent_reply_store.sqlite"), - } + }) } } From 87fad25ac3c0d20dba5e3a82242a909637ce45cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 11 Jan 2023 00:13:28 +0100 Subject: [PATCH 16/18] rust-sdk: implement manualy set/get keys --- .../client-core/src/client/base_client/mod.rs | 2 +- clients/client-core/src/client/key_manager.rs | 43 +++++++++++++------ .../manually_handle_keys_and_config.rs | 6 +-- sdk/rust/nym-sdk/src/mixnet.rs | 2 +- sdk/rust/nym-sdk/src/mixnet/client.rs | 26 +++++++---- sdk/rust/nym-sdk/src/mixnet/keys.rs | 31 +++++++++++++ sdk/rust/nym-sdk/src/mixnet/paths.rs | 2 +- 7 files changed, 83 insertions(+), 29 deletions(-) diff --git a/clients/client-core/src/client/base_client/mod.rs b/clients/client-core/src/client/base_client/mod.rs index cfa0c36da9..25556c6240 100644 --- a/clients/client-core/src/client/base_client/mod.rs +++ b/clients/client-core/src/client/base_client/mod.rs @@ -297,7 +297,7 @@ where .map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?; // disgusting wasm workaround since there's no key persistence there (nor `client init`) - let shared_key = if self.key_manager.gateway_key_set() { + let shared_key = if self.key_manager.is_gateway_key_set() { Some(self.key_manager.gateway_shared_key()) } else { None diff --git a/clients/client-core/src/client/key_manager.rs b/clients/client-core/src/client/key_manager.rs index 83c9ba61f2..779df52cf7 100644 --- a/clients/client-core/src/client/key_manager.rs +++ b/clients/client-core/src/client/key_manager.rs @@ -72,14 +72,6 @@ impl KeyManager { } } - // this is actually **NOT** dead code - // I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute - #[allow(dead_code)] - /// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`]. - pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc) { - self.gateway_shared_key = Some(gateway_shared_key) - } - /// Loads previously stored client keys from the disk. fn load_client_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result { let identity_keypair: identity::KeyPair = @@ -189,16 +181,44 @@ impl KeyManager { Ok(()) } + /// Overwrite the existing identity keypair + pub fn set_identity_keypair(&mut self, id_keypair: identity::KeyPair) { + self.identity_keypair = Arc::new(id_keypair); + } + /// Gets an atomically reference counted pointer to [`identity::KeyPair`]. pub fn identity_keypair(&self) -> Arc { Arc::clone(&self.identity_keypair) } + /// Overwrite the existing encryption keypair + pub fn set_encryption_keypair(&mut self, enc_keypair: encryption::KeyPair) { + self.encryption_keypair = Arc::new(enc_keypair); + } + /// Gets an atomically reference counted pointer to [`encryption::KeyPair`]. pub fn encryption_keypair(&self) -> Arc { Arc::clone(&self.encryption_keypair) } + /// Overwrite the existing ack key + pub fn set_ack_key(&mut self, ack_key: AckKey) { + self.ack_key = Arc::new(ack_key); + } + + /// Gets an atomically reference counted pointer to [`AckKey`]. + pub fn ack_key(&self) -> Arc { + Arc::clone(&self.ack_key) + } + + // this is actually **NOT** dead code + // I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute + #[allow(dead_code)] + /// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`]. + pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc) { + self.gateway_shared_key = Some(gateway_shared_key) + } + /// Gets an atomically reference counted pointer to [`SharedKey`]. // since this function is not fully public, it is not expected to be used externally and // hence it's up to us to ensure it's called in correct context @@ -210,12 +230,7 @@ impl KeyManager { ) } - pub fn gateway_key_set(&self) -> bool { + pub fn is_gateway_key_set(&self) -> bool { self.gateway_shared_key.is_some() } - - /// Gets an atomically reference counted pointer to [`AckKey`]. - pub fn ack_key(&self) -> Arc { - Arc::clone(&self.ack_key) - } } diff --git a/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs b/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs index ed215d30af..a0b3da3eec 100644 --- a/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs +++ b/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs @@ -24,8 +24,8 @@ async fn main() { mock_storage.write(client.get_keys(), client.get_gateway_endpoint().unwrap()); } else { let (keys, gateway_config) = mock_storage.read(); - client.set_keys(&keys); - client.set_gateway_endpoint(&gateway_config); + client.set_keys(keys); + client.set_gateway_endpoint(gateway_config); } // Connect to the mixnet, now we're listening for incoming @@ -49,7 +49,7 @@ impl MockStorage { todo!(); } - fn write(&mut self, _keys: &mixnet::Keys, _gateway_config: &mixnet::GatewayEndpointConfig) { + fn write(&mut self, _keys: mixnet::KeysArc, _gateway_config: &mixnet::GatewayEndpointConfig) { todo!(); } diff --git a/sdk/rust/nym-sdk/src/mixnet.rs b/sdk/rust/nym-sdk/src/mixnet.rs index 21d9369790..9aa3a3be19 100644 --- a/sdk/rust/nym-sdk/src/mixnet.rs +++ b/sdk/rust/nym-sdk/src/mixnet.rs @@ -10,7 +10,7 @@ pub use nymsphinx::{ receiver::ReconstructedMessage, }; -pub use keys::Keys; +pub use keys::{Keys, KeysArc}; pub use paths::{GatewayKeyMode, KeyMode, StoragePaths}; pub use client::{Client, ClientBuilder}; diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index ea2f5f372f..bbe2a89cd3 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::{path::Path, sync::Arc}; use client_connections::TransmissionLane; use client_core::{ @@ -20,7 +20,7 @@ use nymsphinx::{ }; use task::TaskManager; -use super::{connection_state::BuilderState, Config, GatewayKeyMode, Keys, StoragePaths}; +use super::{connection_state::BuilderState, Config, GatewayKeyMode, Keys, KeysArc, StoragePaths}; use crate::error::{Error, Result}; pub struct ClientBuilder { @@ -85,19 +85,27 @@ impl ClientBuilder { /// key, however, is created during the gateway registration handshake so it might not /// necessarily be available. fn has_gateway_key(&self) -> bool { - self.key_manager.gateway_key_set() + self.key_manager.is_gateway_key_set() } - pub fn set_keys(&mut self, _keys: &Keys) { - todo!(); + pub fn set_keys(&mut self, keys: Keys) { + self.key_manager.set_identity_keypair(keys.identity_keypair); + self.key_manager + .set_encryption_keypair(keys.encryption_keypair); + self.key_manager.set_ack_key(keys.ack_key); + + self.key_manager + .insert_gateway_shared_key(Arc::new(keys.gateway_shared_key)); } - pub fn get_keys(&self) -> &Keys { - todo!(); + pub fn get_keys(&self) -> KeysArc { + KeysArc::from(&self.key_manager) } - pub fn set_gateway_endpoint(&mut self, _gateway_endpoint_config: &GatewayEndpointConfig) { - todo!(); + pub fn set_gateway_endpoint(&mut self, gateway_endpoint_config: GatewayEndpointConfig) { + self.state = BuilderState::Registered { + gateway_endpoint_config, + } } pub fn get_gateway_endpoint(&self) -> Option<&GatewayEndpointConfig> { diff --git a/sdk/rust/nym-sdk/src/mixnet/keys.rs b/sdk/rust/nym-sdk/src/mixnet/keys.rs index c6a60f3494..76a3bab39d 100644 --- a/sdk/rust/nym-sdk/src/mixnet/keys.rs +++ b/sdk/rust/nym-sdk/src/mixnet/keys.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use client_core::client::key_manager::KeyManager; use crypto::asymmetric::{encryption, identity}; use gateway_requests::registration::handshake::SharedKeys; @@ -10,6 +12,13 @@ pub struct Keys { pub gateway_shared_key: SharedKeys, } +pub struct KeysArc { + pub identity_keypair: Arc, + pub encryption_keypair: Arc, + pub ack_key: Arc, + pub gateway_shared_key: Arc, +} + impl From for KeyManager { fn from(keys: Keys) -> Self { KeyManager::from_keys( @@ -20,3 +29,25 @@ impl From for KeyManager { ) } } + +impl From for KeysArc { + fn from(keys: Keys) -> Self { + KeysArc { + identity_keypair: keys.identity_keypair.into(), + encryption_keypair: keys.encryption_keypair.into(), + ack_key: keys.ack_key.into(), + gateway_shared_key: keys.gateway_shared_key.into(), + } + } +} + +impl From<&KeyManager> for KeysArc { + fn from(key_manager: &KeyManager) -> Self { + KeysArc { + identity_keypair: key_manager.identity_keypair(), + encryption_keypair: key_manager.encryption_keypair(), + ack_key: key_manager.ack_key(), + gateway_shared_key: key_manager.gateway_shared_key(), + } + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/paths.rs b/sdk/rust/nym-sdk/src/mixnet/paths.rs index ad66f7bbcd..1c044ef60c 100644 --- a/sdk/rust/nym-sdk/src/mixnet/paths.rs +++ b/sdk/rust/nym-sdk/src/mixnet/paths.rs @@ -62,7 +62,7 @@ pub struct StoragePaths { impl StoragePaths { pub fn new_from_dir(operating_mode: KeyMode, dir: &Path) -> Result { if !dir.is_file() { - return Err(Error::ExpectedDirectory(dir.to_owned())) + return Err(Error::ExpectedDirectory(dir.to_owned())); } Ok(Self { From 36496a519aea59fb5f2a8d752aa3aea72f7cafa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 11 Jan 2023 00:13:45 +0100 Subject: [PATCH 17/18] rust-sdk: add additinal example --- .../examples/simple_but_manually_connect.rs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 sdk/rust/nym-sdk/examples/simple_but_manually_connect.rs diff --git a/sdk/rust/nym-sdk/examples/simple_but_manually_connect.rs b/sdk/rust/nym-sdk/examples/simple_but_manually_connect.rs new file mode 100644 index 0000000000..0d79556dfd --- /dev/null +++ b/sdk/rust/nym-sdk/examples/simple_but_manually_connect.rs @@ -0,0 +1,27 @@ +use nym_sdk::mixnet; + +#[tokio::main] +async fn main() { + logging::setup_logging(); + + // Create client builder, including ephemeral keys. The builder can be usable in the context + // where you don't want to connect just yet + let client = mixnet::ClientBuilder::new(None, None).unwrap(); + + // Now we connect to the mixnet, using ephemeral keys already created + let mut client = client.connect_to_mixnet().await.unwrap(); + + // Be able to get our client address + let our_address = client.nym_address(); + println!("Our client nym address is: {our_address}"); + + // Send a message throught the mixnet to ourselves + client + .send_str(&our_address.to_string(), "hello there") + .await; + + println!("Waiting for message"); + client + .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) + .await; +} From b97a12186f289afa2f85689c15a7a429e971f872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 11 Jan 2023 00:32:21 +0100 Subject: [PATCH 18/18] rust-fmt: append path in error --- .../src/config/persistence/key_pathfinder.rs | 16 ++++++++++++++++ nym-wallet/Cargo.lock | 2 +- sdk/rust/nym-sdk/src/error.rs | 8 ++++---- sdk/rust/nym-sdk/src/mixnet/client.rs | 14 +++++++++++--- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/clients/client-core/src/config/persistence/key_pathfinder.rs b/clients/client-core/src/config/persistence/key_pathfinder.rs index 67a8fb010a..70bfe3610f 100644 --- a/clients/client-core/src/config/persistence/key_pathfinder.rs +++ b/clients/client-core/src/config/persistence/key_pathfinder.rs @@ -49,6 +49,15 @@ impl ClientKeyPathfinder { || matches!(self.ack_key.try_exists(), Ok(true)) } + pub fn any_file_exists_and_return(&self) -> Option { + file_exists(&self.identity_public_key) + .or_else(|| file_exists(&self.identity_private_key)) + .or_else(|| file_exists(&self.encryption_public_key)) + .or_else(|| file_exists(&self.encryption_private_key)) + .or_else(|| file_exists(&self.gateway_shared_key)) + .or_else(|| file_exists(&self.ack_key)) + } + pub fn gateway_key_file_exists(&self) -> bool { matches!(self.gateway_shared_key.try_exists(), Ok(true)) } @@ -77,3 +86,10 @@ impl ClientKeyPathfinder { &self.ack_key } } + +fn file_exists(path: &Path) -> Option { + if matches!(path.try_exists(), Ok(true)) { + return Some(path.to_path_buf()); + } + None +} diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 0a9ba7103f..7e52cca234 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -2932,7 +2932,7 @@ dependencies = [ [[package]] name = "nym_wallet" -version = "1.1.5" +version = "1.1.6" dependencies = [ "aes-gcm", "argon2 0.3.4", diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs index 77f6c1492c..8f059ef001 100644 --- a/sdk/rust/nym-sdk/src/error.rs +++ b/sdk/rust/nym-sdk/src/error.rs @@ -11,10 +11,10 @@ pub enum Error { #[error(transparent)] ClientCoreError(#[from] client_core::error::ClientCoreError), - #[error("key file encountered that we don't want to overwrite")] - DontOverwrite, - #[error("shared gateway key file encountered that we don't want to overwrite")] - DontOverwriteGatewayKey, + #[error("key file encountered that we don't want to overwrite: {0}")] + DontOverwrite(PathBuf), + #[error("shared gateway key file encountered that we don't want to overwrite: {0}")] + DontOverwriteGatewayKey(PathBuf), #[error("no gateway config available for writing")] GatewayNotAvailableForWriting, diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index bbe2a89cd3..826e6aea23 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -57,10 +57,16 @@ impl ClientBuilder { Ok(key_manager) => key_manager, Err(err) => { log::debug!("Not loading keys: {err}"); - if path_finder.any_file_exists() && paths.operating_mode.is_keep() { - return Err(Error::DontOverwrite); + if let Some(path) = path_finder.any_file_exists_and_return() { + if paths.operating_mode.is_keep() { + return Err(Error::DontOverwrite(path)); + } } + // Double check using a function that has slightly different internal logic. I + // know this is a bit defensive, but I don't want to overwrite + assert!(!(path_finder.any_file_exists() && paths.operating_mode.is_keep())); + // Create new keys and write to storage let key_manager = client_core::init::new_client_keys(); // WARN: this will overwrite! @@ -134,7 +140,9 @@ impl ClientBuilder { fn write_gateway_key(&self, paths: StoragePaths, key_mode: &GatewayKeyMode) -> Result<()> { let path_finder = ClientKeyPathfinder::from(paths); if path_finder.gateway_key_file_exists() && key_mode.is_keep() { - return Err(Error::DontOverwriteGatewayKey); + return Err(Error::DontOverwriteGatewayKey( + path_finder.gateway_shared_key().to_path_buf(), + )); }; self.key_manager.store_gateway_key(&path_finder)?; Ok(())