From dc800aee083c4b0a10e00d83d98be89b71e4cbd9 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 26 Aug 2024 17:14:41 +0200 Subject: [PATCH] first pass rust sdk --- .../new_docs/pages/sdk/rust/_meta.json | 3 +- .../pages/sdk/rust/concepts/_meta.json | 5 + .../pages/sdk/rust/concepts/abstractions.mdx | 0 .../pages/sdk/rust/concepts/messages.mdx | 0 .../pages/sdk/rust/concepts/overview.mdx | 0 .../new_docs/pages/sdk/rust/examples.md | 9 +- .../pages/sdk/rust/examples/_meta.json | 9 + .../sdk/rust/examples/builders/_meta.json | 4 + .../examples/builders/builder-with-storage.md | 72 ++++++ .../sdk/rust/examples/builders/builder.md | 41 ++++ .../new_docs/pages/sdk/rust/examples/cargo.md | 35 --- .../pages/sdk/rust/examples/credential.md | 8 - .../rust/examples/custom-topology/_meta.json | 5 + .../{ => custom-topology}/custom-network.md | 17 +- .../custom-topology/custom-provider.md | 89 +++++++ .../manually-overwrite-topology.md | 102 ++++++++ .../new_docs/pages/sdk/rust/examples/keys.md | 28 --- .../pages/sdk/rust/examples/simple.md | 34 ++- .../new_docs/pages/sdk/rust/examples/socks.md | 50 +++- .../pages/sdk/rust/examples/split-send.md | 49 +++- .../pages/sdk/rust/examples/storage.md | 228 +++++++++++++++++- .../new_docs/pages/sdk/rust/examples/surbs.md | 81 ++++++- 22 files changed, 758 insertions(+), 111 deletions(-) create mode 100644 documentation/new_docs/pages/sdk/rust/concepts/_meta.json create mode 100644 documentation/new_docs/pages/sdk/rust/concepts/abstractions.mdx create mode 100644 documentation/new_docs/pages/sdk/rust/concepts/messages.mdx create mode 100644 documentation/new_docs/pages/sdk/rust/concepts/overview.mdx create mode 100644 documentation/new_docs/pages/sdk/rust/examples/_meta.json create mode 100644 documentation/new_docs/pages/sdk/rust/examples/builders/_meta.json create mode 100644 documentation/new_docs/pages/sdk/rust/examples/builders/builder-with-storage.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/builders/builder.md delete mode 100644 documentation/new_docs/pages/sdk/rust/examples/cargo.md delete mode 100644 documentation/new_docs/pages/sdk/rust/examples/credential.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/custom-topology/_meta.json rename documentation/new_docs/pages/sdk/rust/examples/{ => custom-topology}/custom-network.md (52%) create mode 100644 documentation/new_docs/pages/sdk/rust/examples/custom-topology/custom-provider.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/custom-topology/manually-overwrite-topology.md delete mode 100644 documentation/new_docs/pages/sdk/rust/examples/keys.md diff --git a/documentation/new_docs/pages/sdk/rust/_meta.json b/documentation/new_docs/pages/sdk/rust/_meta.json index eb62a04bb3..4257b1cec2 100644 --- a/documentation/new_docs/pages/sdk/rust/_meta.json +++ b/documentation/new_docs/pages/sdk/rust/_meta.json @@ -1,7 +1,8 @@ { "index": "Introduction", + "concepts": "Core Concepts", + "examples": "Basic Examples", "message-helpers": "Message Helpers", "message-types": "Message Types", - "examples": "Examples", "troubleshooting": "Troubleshooting" } diff --git a/documentation/new_docs/pages/sdk/rust/concepts/_meta.json b/documentation/new_docs/pages/sdk/rust/concepts/_meta.json new file mode 100644 index 0000000000..0a5c5e2a93 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/concepts/_meta.json @@ -0,0 +1,5 @@ +{ + "overview": "Overview", + "messages": "Message Based Paradigm", + "abstractions": "Connection Abstractions" +} diff --git a/documentation/new_docs/pages/sdk/rust/concepts/abstractions.mdx b/documentation/new_docs/pages/sdk/rust/concepts/abstractions.mdx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/documentation/new_docs/pages/sdk/rust/concepts/messages.mdx b/documentation/new_docs/pages/sdk/rust/concepts/messages.mdx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/documentation/new_docs/pages/sdk/rust/concepts/overview.mdx b/documentation/new_docs/pages/sdk/rust/concepts/overview.mdx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/documentation/new_docs/pages/sdk/rust/examples.md b/documentation/new_docs/pages/sdk/rust/examples.md index 648c32fa0d..9bac32f1b1 100644 --- a/documentation/new_docs/pages/sdk/rust/examples.md +++ b/documentation/new_docs/pages/sdk/rust/examples.md @@ -1,12 +1,9 @@ # Examples -All the following examples can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/master/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there with: +All the following examples can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/master/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there with: -```sh +```sh cargo run --example ``` -If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates. - -An example `Cargo.toml` file can be found [here](examples/cargo.md). - +If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates. diff --git a/documentation/new_docs/pages/sdk/rust/examples/_meta.json b/documentation/new_docs/pages/sdk/rust/examples/_meta.json new file mode 100644 index 0000000000..0319f02862 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/_meta.json @@ -0,0 +1,9 @@ +{ + "simple": "Simple Send", + "builders": "Builder Patterns", + "custom-topology": "Custom Network Topologies", + "split-send": "Concurrent Send & Receive", + "socks": "Socks Proxy", + "storage": "Manually Handle Storage", + "surbs": "Anonymous Replies" +} diff --git a/documentation/new_docs/pages/sdk/rust/examples/builders/_meta.json b/documentation/new_docs/pages/sdk/rust/examples/builders/_meta.json new file mode 100644 index 0000000000..778b1bce9d --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/builders/_meta.json @@ -0,0 +1,4 @@ +{ + "builder": "Ephemeral", + "builder-with-storage": "With Storage" +} diff --git a/documentation/new_docs/pages/sdk/rust/examples/builders/builder-with-storage.md b/documentation/new_docs/pages/sdk/rust/examples/builders/builder-with-storage.md new file mode 100644 index 0000000000..88ecf42085 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/builders/builder-with-storage.md @@ -0,0 +1,72 @@ +# Mixnet Client Builder with Storage + +The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys. + +> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder_with_storage.rs). + +```rust +use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; +use std::path::PathBuf; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + // Specify some config options + let config_dir = PathBuf::from("/tmp/mixnet-client"); + let storage_paths = mixnet::StoragePaths::new_from_dir(&config_dir).unwrap(); + + // Create the client with a storage backend, and enable it by giving it some paths. If keys + // exists at these paths, they will be loaded, otherwise they will be generated. + let client = mixnet::MixnetClientBuilder::new_with_default_storage(storage_paths) + .await + .unwrap() + .build() + .unwrap(); + + // Now we connect to the mixnet, using keys now stored in the paths provided. + 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_plain_message(*our_address, "hello there") + .await + .unwrap(); + + 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; +} +``` + +As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple. + +Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated: +``` +$ tree /tmp/mixnet-client + +mixnet-client +├── ack_key.pem +├── db.sqlite +├── db.sqlite-shm +├── db.sqlite-wal +├── gateway_details.json +├── gateway_shared.pem +├── persistent_reply_store.sqlite +├── private_encryption.pem +├── private_identity.pem +├── public_encryption.pem +└── public_identity.pem + +1 directory, 11 files +``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/builders/builder.md b/documentation/new_docs/pages/sdk/rust/examples/builders/builder.md new file mode 100644 index 0000000000..5e786e7420 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/builders/builder.md @@ -0,0 +1,41 @@ +# Mixnet Client Builder + +> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder.rs). + +```rust +use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; + +#[tokio::main] +async fn main() { + nym_bin_common::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::MixnetClientBuilder::new_ephemeral() + .build() + .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 through the mixnet to ourselves + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); + + 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/documentation/new_docs/pages/sdk/rust/examples/cargo.md b/documentation/new_docs/pages/sdk/rust/examples/cargo.md deleted file mode 100644 index e425e124ed..0000000000 --- a/documentation/new_docs/pages/sdk/rust/examples/cargo.md +++ /dev/null @@ -1,35 +0,0 @@ -# Example Cargo File -This file imports the basic requirements for running these pieces of example code, and can be used as the basis for your own cargo project. - -```toml -[package] -name = "your_app" -version = "x.y.z" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -# Async runtime -tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } -# Used for (de)serialising incoming and outgoing messages -serde = "1.0.152" -serde_json = "1.0.91" -# Nym clients, addressing, etc -nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" } -nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", branch = "master" } -nym-bin-common = { git = "https://github.com/nymtech/nym", branch = "master" } -nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", branch = "master" } -# Additional dependencies if you're interacting with Nyx or another Cosmos SDK blockchain -cosmrs = "=0.14.0" -nym-validator-client = { git = "https://github.com/nymtech/nym", branch = "master" } - -# If you're building an app with a client and server / serivce this might be a useful structure for your repo -[[bin]] -name = "client" -path = "bin/client.rs" - -[[bin]] -name = "service" -path = "bin/service.rs" -``` \ No newline at end of file diff --git a/documentation/new_docs/pages/sdk/rust/examples/credential.md b/documentation/new_docs/pages/sdk/rust/examples/credential.md deleted file mode 100644 index f29d6748ba..0000000000 --- a/documentation/new_docs/pages/sdk/rust/examples/credential.md +++ /dev/null @@ -1,8 +0,0 @@ -# Coconut credential generation -The following code shows how you can use the SDK to create and use a credential representing paid bandwidth on the Sandbox testnet. - -```rust,noplayground -{{#include ../../../../../../sdk/rust/nym-sdk/examples/bandwidth.rs}} -``` - -You can read more about Coconut credentials (also referred to as `zk-Nym`) [here](https://nymtech.net/docs/coconut.html). diff --git a/documentation/new_docs/pages/sdk/rust/examples/custom-topology/_meta.json b/documentation/new_docs/pages/sdk/rust/examples/custom-topology/_meta.json new file mode 100644 index 0000000000..99dd5dc332 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/custom-topology/_meta.json @@ -0,0 +1,5 @@ +{ + "custom-network": "Different Options", + "custom-provider": "Custom Topology Provider", + "manual-topology": "Manually Overwrite Topology" +} diff --git a/documentation/new_docs/pages/sdk/rust/examples/custom-network.md b/documentation/new_docs/pages/sdk/rust/examples/custom-topology/custom-network.md similarity index 52% rename from documentation/new_docs/pages/sdk/rust/examples/custom-network.md rename to documentation/new_docs/pages/sdk/rust/examples/custom-topology/custom-network.md index ae0f145a3b..1a62c06b9c 100644 --- a/documentation/new_docs/pages/sdk/rust/examples/custom-network.md +++ b/documentation/new_docs/pages/sdk/rust/examples/custom-topology/custom-network.md @@ -1,18 +1,11 @@ # Importing and using a custom network topology -If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html) (`examples/custom_topology_provider.rs`). +If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html). + There are two ways to do this: -## Import a custom Nym API endpoint -If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood): - -```rust,noplayground -{{#include ../../../../../../sdk/rust/nym-sdk/examples/custom_topology_provider.rs}} -``` +## Custom Topology Provider +If you are also running a Validator and Nym API for your network, you can specify that endpoint. Clients will then use this endpoint to grab a network topology on startup. You can also use this to specify using a testnet. ## Import a specific topology manually -If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually: - -```rust,noplayground -{{#include ../../../../../../sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs}} -``` +If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can also overwrite the grabbed topology manually. diff --git a/documentation/new_docs/pages/sdk/rust/examples/custom-topology/custom-provider.md b/documentation/new_docs/pages/sdk/rust/examples/custom-topology/custom-provider.md new file mode 100644 index 0000000000..40b4a87f91 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/custom-topology/custom-provider.md @@ -0,0 +1,89 @@ +# Custom Topology Provider + +If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood). + +> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/custom_topology_provider.rs) + +```rust +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; +use nym_topology::provider_trait::{async_trait, TopologyProvider}; +use nym_topology::{nym_topology_from_detailed, NymTopology}; +use url::Url; + +struct MyTopologyProvider { + validator_client: nym_validator_client::client::NymApiClient, +} + +impl MyTopologyProvider { + fn new(nym_api_url: Url) -> MyTopologyProvider { + MyTopologyProvider { + validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url), + } + } + + async fn get_topology(&self) -> NymTopology { + let mixnodes = self + .validator_client + .get_cached_active_mixnodes() + .await + .unwrap(); + + // in our topology provider only use mixnodes that have mix_id divisible by 3 + // and have more than 100k nym (i.e. 100'000'000'000 unym) in stake + // why? because this is just an example to showcase arbitrary uses and capabilities of this trait + let filtered_mixnodes = mixnodes + .into_iter() + .filter(|mix| { + mix.mix_id() % 3 == 0 && mix.total_stake() > "100000000000".parse().unwrap() + }) + .collect::>(); + + let gateways = self.validator_client.get_cached_gateways().await.unwrap(); + + nym_topology_from_detailed(filtered_mixnodes, gateways) + } +} + +#[async_trait] +impl TopologyProvider for MyTopologyProvider { + // this will be manually refreshed on a timer specified inside mixnet client config + async fn get_new_topology(&mut self) -> Option { + Some(self.get_topology().await) + } +} + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + let nym_api = "https://validator.nymtech.net/api/".parse().unwrap(); + let my_topology_provider = MyTopologyProvider::new(nym_api); + + // Passing no config makes the client fire up an ephemeral session and figure things out on its own + let mut client = mixnet::MixnetClientBuilder::new_ephemeral() + .custom_topology_provider(Box::new(my_topology_provider)) + .build() + .unwrap() + .connect_to_mixnet() + .await + .unwrap(); + + let our_address = client.nym_address(); + println!("Our client nym address is: {our_address}"); + + // Send a message through the mixnet to ourselves + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); + + println!("Waiting for message (ctrl-c to exit)"); + client + .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) + .await; +} +``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/custom-topology/manually-overwrite-topology.md b/documentation/new_docs/pages/sdk/rust/examples/custom-topology/manually-overwrite-topology.md new file mode 100644 index 0000000000..dd150ad35b --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/custom-topology/manually-overwrite-topology.md @@ -0,0 +1,102 @@ +# Manually Overwrite Topology + +If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually. + +> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs) + +```rust +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; +use nym_topology::mix::Layer; +use nym_topology::{mix, NymTopology}; +use std::collections::BTreeMap; + +#[tokio::main] +async fn main() { + nym_bin_common::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::MixnetClient::connect_new().await.unwrap(); + let starting_topology = client.read_current_topology().await.unwrap(); + + // but we don't like our default topology, we want to use only those very specific, hardcoded, nodes: + let mut mixnodes = BTreeMap::new(); + mixnodes.insert( + 1, + vec![mix::Node { + mix_id: 63, + owner: None, + host: "172.105.92.48".parse().unwrap(), + mix_host: "172.105.92.48:1789".parse().unwrap(), + identity_key: "GLdR2NRVZBiCoCbv4fNqt9wUJZAnNjGXHkx3TjVAUzrK" + .parse() + .unwrap(), + sphinx_key: "CBmYewWf43iarBq349KhbfYMc9ys2ebXWd4Vp4CLQ5Rq" + .parse() + .unwrap(), + layer: Layer::One, + version: "1.1.0".into(), + }], + ); + mixnodes.insert( + 2, + vec![mix::Node { + mix_id: 23, + owner: None, + host: "178.79.143.65".parse().unwrap(), + mix_host: "178.79.143.65:1789".parse().unwrap(), + identity_key: "4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz" + .parse() + .unwrap(), + sphinx_key: "8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG" + .parse() + .unwrap(), + layer: Layer::Two, + version: "1.1.0".into(), + }], + ); + mixnodes.insert( + 3, + vec![mix::Node { + mix_id: 66, + owner: None, + host: "139.162.247.97".parse().unwrap(), + mix_host: "139.162.247.97:1789".parse().unwrap(), + identity_key: "66UngapebhJRni3Nj52EW1qcNsWYiuonjkWJzHFsmyYY" + .parse() + .unwrap(), + sphinx_key: "7KyZh8Z8KxuVunqytAJ2eXFuZkCS7BLTZSzujHJZsGa2" + .parse() + .unwrap(), + layer: Layer::Three, + version: "1.1.0".into(), + }], + ); + + // but we like the available gateways, so keep using them! + // (we like them because the author of this example is too lazy to use the same hardcoded gateway + // during client initialisation to make sure we are able to send to ourselves : ) ) + let custom_topology = NymTopology::new(mixnodes, starting_topology.gateways().to_vec()); + + client.manually_overwrite_topology(custom_topology).await; + + // and everything we send now should only ever go via those nodes + + let our_address = client.nym_address(); + println!("Our client nym address is: {our_address}"); + + // Send a message through the mixnet to ourselves + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); + + println!("Waiting for message (ctrl-c to exit)"); + client + .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) + .await; +} +``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/keys.md b/documentation/new_docs/pages/sdk/rust/examples/keys.md deleted file mode 100644 index 84746dcfd6..0000000000 --- a/documentation/new_docs/pages/sdk/rust/examples/keys.md +++ /dev/null @@ -1,28 +0,0 @@ -# Key Creation and Use -The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys (`examples/builder_with_storage`): - -```rust,noplayground -{{#include ../../../../../../sdk/rust/nym-sdk/examples/builder_with_storage.rs}} -``` - -As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple. - -Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated: -``` -$ tree /tmp/mixnet-client - -mixnet-client -├── ack_key.pem -├── db.sqlite -├── db.sqlite-shm -├── db.sqlite-wal -├── gateway_details.json -├── gateway_shared.pem -├── persistent_reply_store.sqlite -├── private_encryption.pem -├── private_identity.pem -├── public_encryption.pem -└── public_identity.pem - -1 directory, 11 files -``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/simple.md b/documentation/new_docs/pages/sdk/rust/examples/simple.md index 20872ce96b..9df473686f 100644 --- a/documentation/new_docs/pages/sdk/rust/examples/simple.md +++ b/documentation/new_docs/pages/sdk/rust/examples/simple.md @@ -1,8 +1,34 @@ -# Simple Send -Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code (`examples/simple.rs`). +# Simple Send +Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code. Simply importing the `nym_sdk` crate into your project allows you to create a client and send traffic through the mixnet. -```rust,noplayground -{{#include ../../../../../../sdk/rust/nym-sdk/examples/simple.rs}} +> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/simple.rs) + +```rust +use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; + +#[tokio::main] +async fn main() { + nym_bin_common::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::MixnetClient::connect_new().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 through the mixnet to ourselves + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); + + println!("Waiting for message (ctrl-c to exit)"); + client + .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) + .await; +} ``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/socks.md b/documentation/new_docs/pages/sdk/rust/examples/socks.md index de027e9011..01ff848513 100644 --- a/documentation/new_docs/pages/sdk/rust/examples/socks.md +++ b/documentation/new_docs/pages/sdk/rust/examples/socks.md @@ -1,10 +1,48 @@ -# Socks Proxy -There is also the option to embed the [`socks5-client`](../../../clients/socks5-client.md) into your app code (`examples/socks5.rs`): +# Socks Proxy -```admonish info If you are looking at implementing Nym as a transport layer for a crypto wallet or desktop app, this is probably the best place to start if they can speak SOCKS5, 4a, or 4. -``` -```rust,noplayground -{{#include ../../../../../../sdk/rust/nym-sdk/examples/socks5.rs}} +> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/socks5.rs) + +```rust +use nym_sdk::mixnet; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + println!("Connecting receiver"); + let mut receiving_client = mixnet::MixnetClient::connect_new().await.unwrap(); + + let socks5_config = mixnet::Socks5::new(receiving_client.nym_address().to_string()); + let sending_client = mixnet::MixnetClientBuilder::new_ephemeral() + .socks5_config(socks5_config) + .build() + .unwrap(); + + println!("Connecting sender"); + let sending_client = sending_client.connect_to_mixnet_via_socks5().await.unwrap(); + + let proxy = reqwest::Proxy::all(sending_client.socks5_url()).unwrap(); + let reqwest_client = reqwest::Client::builder().proxy(proxy).build().unwrap(); + tokio::spawn(async move { + println!("Sending socks5-wrapped http request"); + // Message should be sent through the mixnet, via socks5 + // We don't expect to get anything, as there is no network requester on the other end + reqwest_client.get("https://nymtech.net").send().await.ok() + }); + + println!("Waiting for message"); + if let Some(received) = receiving_client.wait_for_messages().await { + for r in received { + println!( + "Received socks5 message requesting for endpoint: {}", + String::from_utf8_lossy(&r.message[10..27]) + ); + } + } + + receiving_client.disconnect().await; + sending_client.disconnect().await; +} ``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/split-send.md b/documentation/new_docs/pages/sdk/rust/examples/split-send.md index 6b7cf69789..b01e3a133b 100644 --- a/documentation/new_docs/pages/sdk/rust/examples/split-send.md +++ b/documentation/new_docs/pages/sdk/rust/examples/split-send.md @@ -1,6 +1,49 @@ # Send and Receive in Different Tasks -If you need to split the different actions of your client across different tasks, you can do so like this: +If you need to split the different actions of your client across different tasks, you can do so like this. You can think of this analogously to spliting a Tcp Stream into read/write. This functionality is also useful for embedding a sending and receiving client into different tasks. -```rust, noplayground -{{#include ../../../../../../sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs}} +> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs) + +```rust +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use futures::StreamExt; +use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + // Passing no config makes the client fire up an ephemeral session and figure stuff out on its own + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + + // Be able to get our client address + let our_address = *client.nym_address(); + println!("Our client nym address is: {our_address}"); + + let sender = client.split_sender(); + + // receiving task + let receiving_task_handle = tokio::spawn(async move { + if let Some(received) = client.next().await { + println!("Received: {}", String::from_utf8_lossy(&received.message)); + } + + client.disconnect().await; + }); + + // sending task + let sending_task_handle = tokio::spawn(async move { + sender + .send_plain_message(our_address, "hello from a different task!") + .await + .unwrap(); + }); + + // wait for both tasks to be done + println!("waiting for shutdown"); + sending_task_handle.await.unwrap(); + receiving_task_handle.await.unwrap(); +} ``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/storage.md b/documentation/new_docs/pages/sdk/rust/examples/storage.md index bc68bca9ff..7d82ce2993 100644 --- a/documentation/new_docs/pages/sdk/rust/examples/storage.md +++ b/documentation/new_docs/pages/sdk/rust/examples/storage.md @@ -1,6 +1,226 @@ -# Manually Handled Storage -If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform the actions taken automatically above (`examples/manually_handle_keys_and_config.rs`) +# Manually Handle Storage +If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform these actions. -```rust,noplayground -{{#include ../../../../../../sdk/rust/nym-sdk/examples/manually_handle_storage.rs}} +> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_handle_storage.rs) + +```rust +use nym_sdk::mixnet::{ + self, ActiveGateway, BadGateway, ClientKeys, EmptyReplyStorage, EphemeralCredentialStorage, + GatewayRegistration, GatewaysDetailsStore, KeyStore, MixnetClientStorage, MixnetMessageSender, +}; +use nym_topology::provider_trait::async_trait; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + // Just some plain data to pretend we have some external storage that the application + // implementer is using. + let mock_storage = MockClientStorage::empty(); + let mut client = mixnet::MixnetClientBuilder::new_with_storage(mock_storage) + .build() + .unwrap() + .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 important info up the pipe to a buddy + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); + + 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; +} + +#[allow(unused)] +struct MockClientStorage { + pub key_store: MockKeyStore, + pub gateway_details_store: MockGatewayDetailsStore, + pub reply_store: EmptyReplyStorage, + pub credential_store: EphemeralCredentialStorage, +} + +impl MockClientStorage { + fn empty() -> Self { + Self { + key_store: MockKeyStore, + gateway_details_store: MockGatewayDetailsStore, + reply_store: EmptyReplyStorage::default(), + credential_store: EphemeralCredentialStorage::default(), + } + } +} + +impl MixnetClientStorage for MockClientStorage { + type KeyStore = MockKeyStore; + type ReplyStore = EmptyReplyStorage; + type CredentialStore = EphemeralCredentialStorage; + type GatewaysDetailsStore = MockGatewayDetailsStore; + + fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) { + (self.reply_store, self.credential_store) + } + + fn key_store(&self) -> &Self::KeyStore { + &self.key_store + } + + fn reply_store(&self) -> &Self::ReplyStore { + &self.reply_store + } + + fn credential_store(&self) -> &Self::CredentialStore { + &self.credential_store + } + + fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore { + &self.gateway_details_store + } +} + +struct MockKeyStore; + +#[async_trait] +impl KeyStore for MockKeyStore { + type StorageError = MyError; + + async fn load_keys(&self) -> Result { + println!("loading stored keys"); + + Err(MyError) + } + + async fn store_keys(&self, _keys: &ClientKeys) -> Result<(), Self::StorageError> { + println!("storing keys"); + + Ok(()) + } +} + +struct MockGatewayDetailsStore; + +#[async_trait] +impl GatewaysDetailsStore for MockGatewayDetailsStore { + type StorageError = MyError; + + async fn active_gateway(&self) -> Result { + println!("getting active gateway"); + + Err(MyError) + } + + async fn set_active_gateway(&self, _gateway_id: &str) -> Result<(), Self::StorageError> { + println!("setting active gateway"); + + Ok(()) + } + + async fn all_gateways(&self) -> Result, Self::StorageError> { + println!("getting all registered gateways"); + + Err(MyError) + } + + async fn has_gateway_details(&self, _gateway_id: &str) -> Result { + println!("checking for gateway details"); + + Err(MyError) + } + + async fn load_gateway_details( + &self, + _gateway_id: &str, + ) -> Result { + println!("loading gateway details"); + + Err(MyError) + } + + async fn store_gateway_details( + &self, + _details: &GatewayRegistration, + ) -> Result<(), Self::StorageError> { + println!("storing gateway details"); + + Ok(()) + } + + async fn remove_gateway_details(&self, _gateway_id: &str) -> Result<(), Self::StorageError> { + println!("removing gateway details"); + + Ok(()) + } +} + +// +// struct MockReplyStore; +// +// #[async_trait] +// impl ReplyStorageBackend for MockReplyStore { +// type StorageError = MyError; +// +// async fn flush_surb_storage( +// &mut self, +// _storage: &CombinedReplyStorage, +// ) -> Result<(), Self::StorageError> { +// todo!() +// } +// +// async fn init_fresh(&mut self, _fresh: &CombinedReplyStorage) -> Result<(), Self::StorageError> { +// todo!() +// } +// +// async fn load_surb_storage(&self) -> Result { +// todo!() +// } +// } +// +// struct MockCredentialStore; +// +// #[async_trait] +// impl CredentialStorage for MockCredentialStore { +// type StorageError = MyError; +// +// async fn insert_coconut_credential( +// &self, +// _voucher_value: String, +// _voucher_info: String, +// _serial_number: String, +// _binding_number: String, +// _signature: String, +// _epoch_id: String, +// ) -> Result<(), Self::StorageError> { +// todo!() +// } +// +// async fn get_next_coconut_credential(&self) -> Result { +// todo!() +// } +// +// async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError> { +// todo!() +// } +// } + +#[derive(thiserror::Error, Debug)] +#[error("foobar")] +struct MyError; + +impl From for MyError { + fn from(_: BadGateway) -> Self { + MyError + } +} ``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/surbs.md b/documentation/new_docs/pages/sdk/rust/examples/surbs.md index d3b6b2da1b..74cb4803db 100644 --- a/documentation/new_docs/pages/sdk/rust/examples/surbs.md +++ b/documentation/new_docs/pages/sdk/rust/examples/surbs.md @@ -1,10 +1,83 @@ # Anonymous Replies with SURBs (Single Use Reply Blocks) Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default. -You can read more about how SURBs function under the hood [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs). +You can read more about how SURBs function under the hood [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs). **TODO change link** -In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to: +In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to. -```rust,noplayground -{{#include ../../../../../../sdk/rust/nym-sdk/examples/surb_reply.rs}} +> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/surb_reply.rs) + +```rust +use nym_sdk::mixnet::{ + AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage, + StoragePaths, +}; +use std::path::PathBuf; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + // Specify some config options + let config_dir = PathBuf::from("/tmp/surb-example"); + let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap(); + + // Create the client with a storage backend, and enable it by giving it some paths. If keys + // exists at these paths, they will be loaded, otherwise they will be generated. + let client = MixnetClientBuilder::new_with_default_storage(storage_paths) + .await + .unwrap() + .build() + .unwrap(); + + // Now we connect to the mixnet, using keys now stored in the paths provided. + let mut client = client.connect_to_mixnet().await.unwrap(); + + // Be able to get our client address + let our_address = client.nym_address(); + println!("\nOur client nym address is: {our_address}"); + + // Send a message through the mixnet to ourselves using our nym address + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); + + // we're going to parse the sender_tag (AnonymousSenderTag) from the incoming message and use it to 'reply' to ourselves instead of our Nym address. + // we know there will be a sender_tag since the sdk sends SURBs along with messages by default. + println!("Waiting for message\n"); + + // get the actual message - discard the empty vec sent along with a potential SURB topup request + let mut message: Vec = Vec::new(); + while let Some(new_message) = client.wait_for_messages().await { + if new_message.is_empty() { + continue; + } + message = new_message; + break; + } + + let mut parsed = String::new(); + if let Some(r) = message.first() { + parsed = String::from_utf8(r.message.clone()).unwrap(); + } + // parse sender_tag: we will use this to reply to sender without needing their Nym address + let return_recipient: AnonymousSenderTag = message[0].sender_tag.unwrap(); + println!( + "\nReceived the following message: {} \nfrom sender with surb bucket {}", + parsed, return_recipient + ); + + // reply to self with it: note we use `send_str_reply` instead of `send_str` + println!("Replying with using SURBs"); + client + .send_reply(return_recipient, "hi an0n!") + .await + .unwrap(); + + println!("Waiting for message (once you see it, ctrl-c to exit)\n"); + client + .on_messages(|msg| println!("\nReceived: {}", String::from_utf8_lossy(&msg.message))) + .await; +} ```