diff --git a/Cargo.lock b/Cargo.lock index cc01296146..d4d5176172 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -941,6 +941,7 @@ dependencies = [ "reqwest 0.9.22 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "sfw-provider-requests 0.1.0", "sphinx 0.1.0", "tokio 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1402,6 +1403,13 @@ dependencies = [ "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sfw-provider-requests" +version = "0.1.0" +dependencies = [ + "sphinx 0.1.0", +] + [[package]] name = "sha2" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 7125852925..b745efc446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ pem = "0.7.0" reqwest = "0.9.22" serde = { version = "1.0", features = ["derive"] } sphinx = { path = "../sphinx" } +sfw-provider-requests = { path = "../nym-sfw-provider/sfw-provider-requests" } + tokio = { version = "0.2", features = ["full"] } [dev-dependencies] diff --git a/src/clients/provider.rs b/src/clients/provider.rs index e69de29bb2..fbf644b58c 100644 --- a/src/clients/provider.rs +++ b/src/clients/provider.rs @@ -0,0 +1,47 @@ +use sphinx::route::Node as MixNode; +use sphinx::SphinxPacket; +use tokio::prelude::*; +use sfw_provider_requests::*; +use std::net::Shutdown; +use std::time::Duration; +use sfw_provider_requests::requests::{PullRequest, ProviderRequest}; +use sfw_provider_requests::responses::{PullResponse, ProviderResponse}; + +pub struct ProviderClient {} + + +impl ProviderClient { + pub fn new() -> Self { + ProviderClient {} + } + + pub async fn retrieve_messages( + &self, +// provider: &MixNode, + ) -> Result<(), Box> { + let address = [42; 32]; + let pull_request = PullRequest::new(address); + let bytes = pull_request.to_bytes(); + + let mut socket = tokio::net::TcpStream::connect("127.0.0.1:9000").await?; + println!("keep alive: {:?}", socket.keepalive()); + socket.set_keepalive(Some(tokio::time::Duration::from_secs(2))); + socket.write_all(&bytes[..]).await?; + if let Err(e) = socket.shutdown(Shutdown::Write) { + eprintln!("failed to close write part of the socket; err = {:?}", e) + } + + let mut response = Vec::new(); + socket.read_to_end(&mut response).await?; + if let Err(e) = socket.shutdown(Shutdown::Read) { + eprintln!("failed to close read part of the socket; err = {:?}", e) + } + + println!("Received the following response: {:?}", response); + let parsed_response = PullResponse::from_bytes(&response).unwrap(); + for message in parsed_response.messages { + println!("Received: {:?}", String::from_utf8(message).unwrap()) + } + Ok(()) + } +} diff --git a/src/commands/init.rs b/src/commands/init.rs index 3552597aae..00af80c1f5 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,60 +1,20 @@ use crate::banner; +use crate::identity::mixnet; +use crate::persistence::pathfinder::Pathfinder; +use crate::persistence::pemstore::PemStore; use clap::ArgMatches; -use dirs; -use pem::{encode, Pem}; -use std::fs::File; -use std::io::prelude::*; pub fn execute(matches: &ArgMatches) { println!("{}", banner()); println!("Initialising client..."); - let id = matches.value_of("id").unwrap(); // required for now + let id = matches.value_of("id").unwrap().to_string(); // required for now + let pathfinder = Pathfinder::new(id); - // don't unwrap it, pass it as it is, if it's None, choose a random - let _provider_id = matches.value_of("provider"); - let _init_local = matches.is_present("local"); - - let os_config_dir = dirs::config_dir().unwrap(); - let nym_client_config_dir = os_config_dir.join("nym").join("clients").join(id); - - println!("Writing keypairs to {:?}...", nym_client_config_dir); - write_pem_files(nym_client_config_dir); + println!("Writing keypairs to {:?}...", pathfinder.config_dir); + let mix_keys = mixnet::KeyPair::new(); + let pem_store = PemStore::new(pathfinder); + pem_store.write(mix_keys); println!("Client configuration completed.\n\n\n") } - -fn write_pem_files(nym_client_config_dir: std::path::PathBuf) { - std::fs::create_dir_all(nym_client_config_dir.clone()).unwrap(); - - let (private, public) = sphinx::crypto::keygen(); - write_pem_file( - nym_client_config_dir.clone(), - String::from("private.pem"), - private.to_bytes().to_vec(), - String::from("SPHINX CURVE25519 PRIVATE KEY"), - ); - write_pem_file( - nym_client_config_dir.clone(), - String::from("public.pem"), - public.to_bytes().to_vec(), - String::from("SPHINX CURVE25519 PUBLIC KEY"), - ); -} - -fn write_pem_file( - nym_client_config_dir: std::path::PathBuf, - filename: String, - data: Vec, - tag: String, -) { - let pem = Pem { - tag, - contents: data, - }; - let key = encode(&pem); - - let full_path = nym_client_config_dir.join(filename); - let mut file = File::create(full_path).unwrap(); - file.write_all(key.as_bytes()).unwrap(); -} diff --git a/src/commands/run.rs b/src/commands/run.rs index 0ce7a25f79..9a72186984 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -1,8 +1,9 @@ use crate::clients::directory; -use crate::clients::directory::presence::{MixNodePresence, Topology}; +use crate::clients::directory::presence::Topology; use crate::clients::directory::requests::presence_topology_get::PresenceTopologyGetRequester; use crate::clients::directory::DirectoryClient; use crate::clients::mix::MixClient; +use crate::clients::provider::ProviderClient; use crate::utils::bytes; use base64; use clap::ArgMatches; @@ -15,10 +16,7 @@ use tokio::time::{interval_at, Instant}; pub fn execute(matches: &ArgMatches) { let custom_cfg = matches.value_of("customCfg"); - println!( - "Going to start client with custom config of: {:?}", - custom_cfg - ); + println!("Starting client with config: {:?}", custom_cfg); // Grab the network topology from the remote directory server let topology = get_topology(); @@ -35,7 +33,7 @@ pub fn execute(matches: &ArgMatches) { interval.tick().await; let message = format!("Hello, Sphinx {}", i).as_bytes().to_vec(); - let route_len = 3; + let route_len = 2; // data needed to generate a new Sphinx packet let route = route_from(&topology, route_len); @@ -51,6 +49,14 @@ pub fn execute(matches: &ArgMatches) { let result = mix_client.send(packet, route.first().unwrap()).await; println!("packet sent: {:?}", i); i += 1; + + // retrieve messages every now and then + if i % 3 == 0 { + interval.tick().await; + println!("going to retrieve messages!"); + let provider_client = ProviderClient::new(); + provider_client.retrieve_messages().await.unwrap(); + } } }) } @@ -76,10 +82,13 @@ fn route_from(topology: &Topology, route_len: usize) -> Vec { let decoded_key_bytes = base64::decode_config(&mix.pub_key, base64::URL_SAFE).unwrap(); let key_bytes = bytes::zero_pad_to_32(decoded_key_bytes); let key = MontgomeryPoint(key_bytes); - let sphinx_node = SphinxNode { + let mut sphinx_node = SphinxNode { address: address_bytes, pub_key: key, }; + + // temporary to make it work locally: + sphinx_node.pub_key = Default::default(); route.push(sphinx_node); } route @@ -88,7 +97,7 @@ fn route_from(topology: &Topology, route_len: usize) -> Vec { // TODO: where do we retrieve this guy from? fn get_destination() -> Destination { Destination { - address: [0u8; 32], - identifier: [0u8; 16], + address: [42u8; 32], + identifier: [1u8; 16], } } diff --git a/src/identity/mixnet.rs b/src/identity/mixnet.rs new file mode 100644 index 0000000000..83507d7d70 --- /dev/null +++ b/src/identity/mixnet.rs @@ -0,0 +1,35 @@ +use curve25519_dalek::montgomery::MontgomeryPoint; +use curve25519_dalek::scalar::Scalar; + +// This keypair serves as the user's identity within the Mixnet +pub struct KeyPair { + pub private: Scalar, + pub public: MontgomeryPoint, +} + +impl KeyPair { + pub fn new() -> KeyPair { + let (private, public) = sphinx::crypto::keygen(); + KeyPair { private, public } + } + + pub fn from_bytes(private_bytes: Vec, public_bytes: Vec) -> KeyPair { + let mut bytes = [0; 32]; + bytes.copy_from_slice(&private_bytes[..]); + let private = Scalar::from_canonical_bytes(bytes).unwrap(); + + let mut bytes = [0; 32]; + bytes.copy_from_slice(&public_bytes[..]); + let public = MontgomeryPoint(bytes); + + KeyPair { private, public } + } + + pub fn private_bytes(&self) -> Vec { + self.private.to_bytes().to_vec() + } + + pub fn public_bytes(&self) -> Vec { + self.public.to_bytes().to_vec() + } +} diff --git a/src/identity/mod.rs b/src/identity/mod.rs new file mode 100644 index 0000000000..ae7f01c0eb --- /dev/null +++ b/src/identity/mod.rs @@ -0,0 +1 @@ +pub mod mixnet; diff --git a/src/identity/validators.rs b/src/identity/validators.rs new file mode 100644 index 0000000000..b18c85b0b2 --- /dev/null +++ b/src/identity/validators.rs @@ -0,0 +1 @@ +// TODO types for Validator keys will go in here once we hook this up. diff --git a/src/main.rs b/src/main.rs index c9eaf03d40..cf053edfac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use std::process; mod clients; mod commands; +mod identity; +mod persistence; mod utils; fn main() { diff --git a/src/persistence/mod.rs b/src/persistence/mod.rs new file mode 100644 index 0000000000..bc1ad593bd --- /dev/null +++ b/src/persistence/mod.rs @@ -0,0 +1,2 @@ +pub mod pathfinder; +pub mod pemstore; diff --git a/src/persistence/pathfinder.rs b/src/persistence/pathfinder.rs new file mode 100644 index 0000000000..08110b9ae8 --- /dev/null +++ b/src/persistence/pathfinder.rs @@ -0,0 +1,21 @@ +use std::path::PathBuf; + +pub struct Pathfinder { + pub config_dir: PathBuf, + pub private_mix_key: PathBuf, + pub public_mix_key: PathBuf, +} + +impl Pathfinder { + pub fn new(id: String) -> Pathfinder { + let os_config_dir = dirs::config_dir().unwrap(); // grabs the OS default config dir + let config_dir = os_config_dir.join("nym").join("clients").join(id); + let private_mix_key = config_dir.join("private.pem"); + let public_mix_key = config_dir.join("public.pem"); + Pathfinder { + config_dir, + private_mix_key, + public_mix_key, + } + } +} diff --git a/src/persistence/pemstore.rs b/src/persistence/pemstore.rs new file mode 100644 index 0000000000..c935fab437 --- /dev/null +++ b/src/persistence/pemstore.rs @@ -0,0 +1,65 @@ +use crate::identity::mixnet::KeyPair; +use crate::persistence::pathfinder::Pathfinder; +use pem::{encode, parse, Pem}; +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; + +pub struct PemStore { + config_dir: PathBuf, + private_mix_key: PathBuf, + public_mix_key: PathBuf, +} + +impl PemStore { + pub fn new(pathfinder: Pathfinder) -> PemStore { + PemStore { + config_dir: pathfinder.config_dir, + private_mix_key: pathfinder.private_mix_key, + public_mix_key: pathfinder.public_mix_key, + } + } + + pub fn read(&self) -> KeyPair { + let private = self.read_file(self.private_mix_key.clone()); + let public = self.read_file(self.public_mix_key.clone()); + + KeyPair::from_bytes(private, public) + } + + fn read_file(&self, filepath: PathBuf) -> Vec { + let mut pem_bytes = File::open(filepath).unwrap(); + let mut buf = Vec::new(); + pem_bytes.read_to_end(&mut buf).unwrap(); + let pem = parse(&buf).unwrap(); + pem.contents + } + // This should be refactored and made more generic for when we have other kinds of + // KeyPairs that we want to persist (e.g. validator keypairs, or keys for + // signing vs encryption). However, for the moment, it does the job. + pub fn write(&self, key_pair: KeyPair) { + std::fs::create_dir_all(self.config_dir.clone()).unwrap(); + + self.write_pem_file( + self.private_mix_key.clone(), + key_pair.private_bytes(), + String::from("SPHINX CURVE25519 PRIVATE KEY"), + ); + self.write_pem_file( + self.public_mix_key.clone(), + key_pair.public_bytes(), + String::from("SPHINX CURVE25519 PUBLIC KEY"), + ); + } + + fn write_pem_file(&self, filepath: PathBuf, data: Vec, tag: String) { + let pem = Pem { + tag, + contents: data, + }; + let key = encode(&pem); + + let mut file = File::create(filepath).unwrap(); + file.write_all(key.as_bytes()).unwrap(); + } +} diff --git a/src/persistence/toml.rs b/src/persistence/toml.rs new file mode 100644 index 0000000000..f4f5578ad0 --- /dev/null +++ b/src/persistence/toml.rs @@ -0,0 +1 @@ +// TODO: we can put all the TOML config templating code in here once we get to that.