Merge pull request #2764 from nymtech/feature/rust-sdk-initial-version

Initial Rust client SDK
This commit is contained in:
Jon Häggblad
2023-01-11 10:06:15 +01:00
committed by GitHub
35 changed files with 8279 additions and 104 deletions
+2
View File
@@ -6,6 +6,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
### Added
- nym-sdk: added initial version of a Rust client sdk
### Changed
- renamed all references to validator_api to nym_api
Generated
+7166
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -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",
@@ -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<mpsc::UnboundedReceiver<Vec<ReconstructedMessage>>, 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,
@@ -80,6 +103,32 @@ impl ClientOutputStatus {
}
}
#[derive(Copy, Clone, 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<bool> 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,
@@ -119,13 +168,13 @@ where
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController>,
reply_storage_backend: B,
disabled_credentials: bool,
credentials_toggle: CredentialsToggle,
nym_api_endpoints: Vec<Url>,
) -> BaseClientBuilder<'a, B> {
BaseClientBuilder {
gateway_config,
debug_config,
disabled_credentials,
disabled_credentials: credentials_toggle.is_disabled(),
nym_api_endpoints,
reply_storage_backend,
bandwidth_controller,
@@ -248,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
@@ -475,11 +524,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 +539,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,
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::replies::reply_storage::{
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
self, fs_backend, CombinedReplyStorage, ReplyStorageBackend,
};
use crate::config::DebugConfig;
use crate::error::ClientCoreError;
@@ -85,3 +85,10 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
setup_fresh_backend(db_path, debug_config).await
}
}
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,
}
}
+97 -21
View File
@@ -17,6 +17,7 @@ use std::sync::Arc;
// use the old key after new one was issued.
// Remember that Arc<T> has Deref implementation for T
#[derive(Clone)]
pub struct KeyManager {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
@@ -57,16 +58,22 @@ 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<SharedKeys>) {
self.gateway_shared_key = Some(gateway_shared_key)
pub fn 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),
}
}
/// Loads previously stored keys from the disk.
pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
/// Loads previously stored client keys from the disk.
fn load_client_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
let identity_keypair: identity::KeyPair =
pemstore::load_keypair(&pemstore::KeyPairPath::new(
client_pathfinder.private_identity_key().to_owned(),
@@ -78,21 +85,51 @@ 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<Self> {
let mut key_manager = Self::load_client_keys(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)
}
/// 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<Self> {
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
let gateway_shared_key: Result<SharedKeys, io::Error> =
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 +156,7 @@ 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())?
}
@@ -128,16 +165,60 @@ impl KeyManager {
Ok(())
}
pub fn store_gateway_key(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
match self.gateway_shared_key.as_ref() {
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())?
}
}
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<identity::KeyPair> {
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<encryption::KeyPair> {
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<AckKey> {
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<SharedKeys>) {
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
@@ -149,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<AckKey> {
Arc::clone(&self.ack_key)
}
}
@@ -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<FragmentIdentifier>) {
fn handle_expired_ack_timer(
&mut self,
expired_ack: Expired<FragmentIdentifier>,
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;
@@ -275,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");
}
}
@@ -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")]
@@ -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<ReplyControllerMessage>;
#[derive(Debug)]
@@ -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,
}
@@ -19,10 +19,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]
@@ -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,8 +22,8 @@ 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"),
}
@@ -40,6 +40,28 @@ 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 any_file_exists_and_return(&self) -> Option<PathBuf> {
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))
}
pub fn private_identity_key(&self) -> &Path {
&self.identity_private_key
}
@@ -64,3 +86,10 @@ impl ClientKeyPathfinder {
&self.ack_key
}
}
fn file_exists(path: &Path) -> Option<PathBuf> {
if matches!(path.try_exists(), Ok(true)) {
return Some(path.to_path_buf());
}
None
}
+3
View File
@@ -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,
}
+4 -11
View File
@@ -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<identity::KeyPair>,
) -> Result<Arc<SharedKeys>, ClientCoreError> {
@@ -74,20 +74,13 @@ async fn register_with_gateway(
Ok(shared_keys)
}
pub(super) async fn register_with_gateway_and_store_keys<T>(
gateway_details: gateway::Node,
pub(super) fn store_keys<T>(
key_manager: &KeyManager,
config: &Config<T>,
) -> 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)
+63 -13
View File
@@ -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<C, T>(
@@ -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<Url>,
chosen_gateway_id: Option<String>,
) -> Result<GatewayEndpointConfig, ClientCoreError> {
// 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<T>(
user_chosen_gateway_id: Option<String>,
pub async fn register_with_gateway_and_store<T>(
chosen_gateway_id: Option<String>,
config: &Config<T>,
) -> Result<GatewayEndpointConfig, ClientCoreError>
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<T>(
config: &Config<T>,
+9 -5
View File
@@ -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(),
);
+9 -2
View File
@@ -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(),
);
+1 -6
View File
@@ -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");
}
}
+10 -2
View File
@@ -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()],
);
@@ -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");
}
}
+9 -7
View File
@@ -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_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 {
@@ -300,8 +302,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
}
}
+1 -1
View File
@@ -2932,7 +2932,7 @@ dependencies = [
[[package]]
name = "nym_wallet"
version = "1.1.5"
version = "1.1.6"
dependencies = [
"aes-gcm",
"argon2 0.3.4",
+29
View File
@@ -0,0 +1,29 @@
[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-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]
pretty_env_logger = "0.4.0"
tokio = { version = "1", features = ["full"] }
logging = { path = "../../../common/logging" }
@@ -0,0 +1,62 @@
use nym_sdk::mixnet;
#[tokio::main]
async fn main() {
logging::setup_logging();
// We can set a few options
let user_chosen_gateway_id = None;
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::ClientBuilder::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).
let first_run = true;
if first_run {
client.register_with_gateway().await.unwrap();
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);
}
// Connect to the mixnet, now we're listening for incoming
let client = client.connect_to_mixnet().await.unwrap();
// 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_str("foo.bar@blah", "flappappa").await;
}
#[allow(unused)]
struct MockStorage {
pub gateway_config: Option<mixnet::GatewayEndpointConfig>,
pub keys: Option<Vec<u8>>,
}
impl MockStorage {
fn read(&self) -> (mixnet::Keys, mixnet::GatewayEndpointConfig) {
todo!();
}
fn write(&mut self, _keys: mixnet::KeysArc, _gateway_config: &mixnet::GatewayEndpointConfig) {
todo!();
}
fn empty() -> Self {
Self {
gateway_config: None,
keys: None,
}
}
}
+39
View File
@@ -0,0 +1,39 @@
use std::path::PathBuf;
use nym_sdk::mixnet;
#[tokio::main]
async fn main() {
logging::setup_logging();
// Specify some config options
let config_dir = PathBuf::from("/tmp/mixnet-client");
// 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).unwrap();
// Provide key paths for the client to read/write keys to.
let client = mixnet::ClientBuilder::new(None, Some(keys)).unwrap();
// Connect to the mixnet, now we're listening for incoming
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");
if let Some(received) = client.wait_for_messages().await {
for r in received {
println!("Received: {}", String::from_utf8_lossy(&r.message));
}
}
client.disconnect().await;
}
+23
View File
@@ -0,0 +1,23 @@
use nym_sdk::mixnet;
#[tokio::main]
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::connect().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;
}
@@ -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;
}
+25
View File
@@ -0,0 +1,25 @@
use std::path::PathBuf;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("i/o error: {0}")]
IoError(#[from] std::io::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: {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,
#[error("expected to received a directory, received: {0}")]
ExpectedDirectory(PathBuf),
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
+2
View File
@@ -0,0 +1,2 @@
pub mod error;
pub mod mixnet;
+17
View File
@@ -0,0 +1,17 @@
mod client;
mod config;
mod connection_state;
mod keys;
mod paths;
pub use client_core::config::GatewayEndpointConfig;
pub use nymsphinx::{
addressing::clients::{ClientIdentity, Recipient},
receiver::ReconstructedMessage,
};
pub use keys::{Keys, KeysArc};
pub use paths::{GatewayKeyMode, KeyMode, StoragePaths};
pub use client::{Client, ClientBuilder};
pub use config::Config;
+331
View File
@@ -0,0 +1,331 @@
use std::{path::Path, sync::Arc};
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 task::TaskManager;
use super::{connection_state::BuilderState, Config, GatewayKeyMode, Keys, KeysArc, StoragePaths};
use crate::error::{Error, Result};
pub struct ClientBuilder {
/// Keys handled by the client
key_manager: KeyManager,
/// Client configuration
config: Config,
/// Paths for client keys, including identity, encryption, ack and shared gateway keys.
storage_paths: Option<StoragePaths>,
/// The client can be in one of multiple states, depending on how it is created and if it's
/// connected to the mixnet.
state: BuilderState,
}
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<Config>, paths: Option<StoragePaths>) -> Result<Self> {
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 paths) = paths {
let path_finder = ClientKeyPathfinder::from(paths.clone());
// Try load keys
match KeyManager::load_keys_but_gateway_is_optional(&path_finder) {
Ok(key_manager) => key_manager,
Err(err) => {
log::debug!("Not loading keys: {err}");
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!
key_manager.store_keys(&path_finder)?;
key_manager
}
}
} else {
// Ephemeral keys that we only store in memory
client_core::init::new_client_keys()
};
Ok(Self {
key_manager,
config,
storage_paths: paths,
state: BuilderState::New,
})
}
/// 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.
fn has_gateway_key(&self) -> bool {
self.key_manager.is_gateway_key_set()
}
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) -> KeysArc {
KeysArc::from(&self.key_manager)
}
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> {
self.state.gateway_endpoint_config()
}
pub async fn register_with_gateway(&mut self) -> Result<()> {
assert!(
matches!(self.state, BuilderState::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?;
self.state = BuilderState::Registered {
gateway_endpoint_config: gateway_config,
};
Ok(())
}
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(
path_finder.gateway_shared_key().to_path_buf(),
));
};
self.key_manager.store_gateway_key(&path_finder)?;
Ok(())
}
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() {
std::fs::create_dir_all(parent_dir)?;
}
std::fs::write(gateway_endpoint_config_path, gateway_endpoint_config)?;
Ok(())
}
fn read_gateway_endpoint_config(&mut self, gateway_endpoint_config_path: &Path) -> Result<()> {
let gateway_endpoint_config: GatewayEndpointConfig =
std::fs::read_to_string(gateway_endpoint_config_path)
.map(|str| toml::from_str(&str))??;
self.state = BuilderState::Registered {
gateway_endpoint_config,
};
Ok(())
}
/// Connects to the mixnet via the gateway in the client config
pub async fn connect_to_mixnet(mut self) -> Result<Client> {
// For some simple cases we can figure how to setup gateway without it having to have been
// called in advance.
if matches!(self.state, BuilderState::New) {
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
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
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)?;
}
} 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.
let BuilderState::Registered { gateway_endpoint_config } = self.state else {
todo!();
};
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;
// 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 base_builder = BaseClientBuilder::new(
&gateway_endpoint_config,
&self.config.debug_config,
self.key_manager.clone(),
bandwidth_controller,
reply_storage_backend,
CredentialsToggle::Disabled,
self.config.nym_api_endpoints.clone(),
);
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();
Ok(Client {
nym_address,
key_manager: self.key_manager,
client_input,
client_output,
client_state,
reconstructed_receiver,
task_manager: started_client.task_manager,
})
}
}
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<Self> {
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
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<u8>) {
log::debug!("send_bytes");
let lane = TransmissionLane::General;
let recipient = Recipient::try_from_base58_string(address).unwrap();
let input_msg = InputMessage::new_regular(recipient, message, lane);
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<Vec<ReconstructedMessage>> {
self.reconstructed_receiver.next().await
}
pub fn wait_for_messages_split(&mut self) -> Option<ReconstructedMessage> {
todo!();
}
pub async fn on_messages<F>(&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) {
self.task_manager.signal_shutdown().ok();
self.task_manager.wait_for_shutdown().await;
}
}
+36
View File
@@ -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<String>,
/// List of nym-api endpoints
pub nym_api_endpoints: Vec<Url>,
/// 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::NYM_API.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<String>, nym_api_endpoints: Vec<Url>) -> Self {
Self {
user_chosen_gateway,
nym_api_endpoints,
debug_config: DebugConfig::default(),
}
}
}
@@ -0,0 +1,21 @@
use client_core::config::GatewayEndpointConfig;
#[derive(Debug)]
pub(super) enum BuilderState {
New,
Registered {
gateway_endpoint_config: GatewayEndpointConfig,
},
}
impl BuilderState {
pub(super) fn gateway_endpoint_config(&self) -> Option<&GatewayEndpointConfig> {
match self {
BuilderState::New => None,
BuilderState::Registered {
gateway_endpoint_config,
..
} => Some(gateway_endpoint_config),
}
}
}
+53
View File
@@ -0,0 +1,53 @@
use std::sync::Arc;
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,
}
pub struct KeysArc {
pub identity_keypair: Arc<identity::KeyPair>,
pub encryption_keypair: Arc<encryption::KeyPair>,
pub ack_key: Arc<AckKey>,
pub gateway_shared_key: Arc<SharedKeys>,
}
impl From<Keys> for KeyManager {
fn from(keys: Keys) -> Self {
KeyManager::from_keys(
keys.identity_keypair,
keys.encryption_keypair,
keys.gateway_shared_key,
keys.ack_key,
)
}
}
impl From<Keys> 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(),
}
}
}
+96
View File
@@ -0,0 +1,96 @@
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.
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 StoragePaths {
// 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.
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,
}
impl StoragePaths {
pub fn new_from_dir(operating_mode: KeyMode, dir: &Path) -> Result<Self> {
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,
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<StoragePaths> for ClientKeyPathfinder {
fn from(paths: StoragePaths) -> 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,
}
}
}