diff --git a/clients/desktop/examples/websocket_filesend.rs b/clients/desktop/examples/websocket_filesend.rs index dee09244ea0..ba6ffc42ba9 100644 --- a/clients/desktop/examples/websocket_filesend.rs +++ b/clients/desktop/examples/websocket_filesend.rs @@ -1,6 +1,6 @@ use futures::{SinkExt, StreamExt}; -use nym_client::client::Recipient; use nym_client::websocket::{BinaryClientRequest, ClientRequest, ServerResponse}; +use nymsphinx::addressing::clients::Recipient; use std::convert::TryFrom; use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; diff --git a/clients/desktop/examples/websocket_textsend.rs b/clients/desktop/examples/websocket_textsend.rs index 8d9b3a07eb3..4471c501e7b 100644 --- a/clients/desktop/examples/websocket_textsend.rs +++ b/clients/desktop/examples/websocket_textsend.rs @@ -2,6 +2,7 @@ use futures::{SinkExt, StreamExt}; use nym_client::websocket::{ClientRequest, ServerResponse}; use std::convert::TryFrom; use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; + #[tokio::main] async fn main() { let message = "Hello Nym!".to_string(); diff --git a/clients/desktop/src/client/cover_traffic_stream.rs b/clients/desktop/src/client/cover_traffic_stream.rs index aecabc00190..5354e0c9aa2 100644 --- a/clients/desktop/src/client/cover_traffic_stream.rs +++ b/clients/desktop/src/client/cover_traffic_stream.rs @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client::inbound_messages::Recipient; use crate::client::mix_traffic::{MixMessage, MixMessageSender}; use crate::client::topology_control::TopologyAccessor; use futures::task::{Context, Poll}; use futures::{Future, Stream, StreamExt}; use log::*; +use nymsphinx::addressing::clients::Recipient; use nymsphinx::utils::{encapsulation, poisson}; use std::pin::Pin; use std::time::Duration; diff --git a/clients/desktop/src/client/inbound_messages.rs b/clients/desktop/src/client/inbound_messages.rs index 254d27ba380..a2d0128d619 100644 --- a/clients/desktop/src/client/inbound_messages.rs +++ b/clients/desktop/src/client/inbound_messages.rs @@ -1,8 +1,5 @@ use futures::channel::mpsc; -use nymsphinx::{ - DestinationAddressBytes, Error, NodeAddressBytes, DESTINATION_ADDRESS_LENGTH, - NODE_ADDRESS_LENGTH, -}; +use nymsphinx::addressing::clients::Recipient; pub(crate) type InputMessageSender = mpsc::UnboundedSender; pub(crate) type InputMessageReceiver = mpsc::UnboundedReceiver; @@ -23,83 +20,3 @@ impl InputMessage { (self.recipient, self.data) } } - -#[derive(Debug)] -pub struct RecipientFormattingError; - -impl From for RecipientFormattingError { - fn from(_: Error) -> Self { - Self - } -} - -// TODO: this should a different home... somewhere, but where? -#[derive(Clone, Debug)] -pub struct Recipient { - destination: DestinationAddressBytes, - gateway: NodeAddressBytes, -} - -impl Recipient { - pub const LEN: usize = DESTINATION_ADDRESS_LENGTH + NODE_ADDRESS_LENGTH; - - pub fn new(destination: DestinationAddressBytes, gateway: NodeAddressBytes) -> Self { - Recipient { - destination, - gateway, - } - } - - pub fn destination(&self) -> DestinationAddressBytes { - self.destination.clone() - } - - pub fn gateway(&self) -> NodeAddressBytes { - self.gateway.clone() - } - - pub fn into_bytes(self) -> [u8; Self::LEN] { - let mut out = [0u8; Self::LEN]; - out[..DESTINATION_ADDRESS_LENGTH].copy_from_slice(self.destination.as_bytes()); - out[DESTINATION_ADDRESS_LENGTH..].copy_from_slice(self.gateway.as_bytes()); - - out - } - - pub fn from_bytes(bytes: [u8; Self::LEN]) -> Self { - let mut destination_bytes = [0u8; DESTINATION_ADDRESS_LENGTH]; - destination_bytes.copy_from_slice(&bytes[..DESTINATION_ADDRESS_LENGTH]); - - let mut gateway_address_bytes = [0u8; NODE_ADDRESS_LENGTH]; - gateway_address_bytes.copy_from_slice(&bytes[DESTINATION_ADDRESS_LENGTH..]); - - let destination = DestinationAddressBytes::from_bytes(destination_bytes); - let gateway = NodeAddressBytes::from_bytes(gateway_address_bytes); - - Self { - destination, - gateway, - } - } - - pub fn try_from_string(full_address: String) -> Result { - let split: Vec<_> = full_address.split("@").collect(); - if split.len() != 2 { - return Err(RecipientFormattingError); - } - let destination = DestinationAddressBytes::try_from_base58_string(split[0])?; - let gateway = NodeAddressBytes::try_from_base58_string(split[1])?; - Ok(Recipient { - destination, - gateway, - }) - } - - pub fn to_string(&self) -> String { - format!( - "{}@{}", - self.destination.to_base58_string(), - self.gateway.to_base58_string() - ) - } -} diff --git a/clients/desktop/src/client/mod.rs b/clients/desktop/src/client/mod.rs index 4e2dd033fc9..944ec693d73 100644 --- a/clients/desktop/src/client/mod.rs +++ b/clients/desktop/src/client/mod.rs @@ -29,6 +29,7 @@ use futures::channel::mpsc; use gateway_client::{GatewayClient, SphinxPacketReceiver, SphinxPacketSender}; use gateway_requests::auth_token::AuthToken; use log::*; +use nymsphinx::addressing::clients::Recipient; use nymsphinx::chunking::split_and_prepare_payloads; use nymsphinx::NodeAddressBytes; use received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver}; @@ -42,11 +43,6 @@ mod real_traffic_stream; pub(crate) mod received_buffer; pub(crate) mod topology_control; -// I'm not sure if that is the right place for it to live, but I could not find a better one... -// (it definitely can't be in websocket, because it's not websocket specific; neither can it just -// live in common/nymsphinx, as it's sphinx related at all, it's only for client-client communication) -pub use inbound_messages::Recipient; - pub struct NymClient { config: Config, runtime: Runtime, diff --git a/clients/desktop/src/client/real_traffic_stream.rs b/clients/desktop/src/client/real_traffic_stream.rs index 2201c310394..d008631b209 100644 --- a/clients/desktop/src/client/real_traffic_stream.rs +++ b/clients/desktop/src/client/real_traffic_stream.rs @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client::inbound_messages::{InputMessage, Recipient}; +use crate::client::inbound_messages::InputMessage; use crate::client::mix_traffic::MixMessage; use crate::client::topology_control::TopologyAccessor; use futures::channel::mpsc; use futures::task::{Context, Poll}; use futures::{Future, Stream, StreamExt}; use log::{error, info, trace, warn}; +use nymsphinx::addressing::clients::Recipient; use nymsphinx::utils::{encapsulation, poisson}; use std::pin::Pin; use std::time::Duration; diff --git a/clients/desktop/src/websocket/handler.rs b/clients/desktop/src/websocket/handler.rs index 033b54e9f91..8ca9d9b867f 100644 --- a/clients/desktop/src/websocket/handler.rs +++ b/clients/desktop/src/websocket/handler.rs @@ -14,7 +14,7 @@ use super::types::{BinaryClientRequest, ClientRequest, ServerResponse}; use crate::client::{ - inbound_messages::{InputMessage, InputMessageSender, Recipient}, + inbound_messages::{InputMessage, InputMessageSender}, received_buffer::{ ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver, }, @@ -23,6 +23,7 @@ use crate::client::{ use futures::channel::mpsc; use futures::{SinkExt, StreamExt}; use log::*; +use nymsphinx::addressing::clients::Recipient; use nymsphinx::chunking::split_and_prepare_payloads; use std::convert::TryFrom; use tokio::net::TcpStream; diff --git a/clients/desktop/src/websocket/types.rs b/clients/desktop/src/websocket/types.rs index 655e74f146b..e1a72cd33db 100644 --- a/clients/desktop/src/websocket/types.rs +++ b/clients/desktop/src/websocket/types.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client::inbound_messages::Recipient; +use nymsphinx::addressing::clients::Recipient; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use tokio_tungstenite::tungstenite::protocol::Message; diff --git a/clients/webassembly/client.js b/clients/webassembly/client.js index 4c53d01ddf1..8997866155a 100644 --- a/clients/webassembly/client.js +++ b/clients/webassembly/client.js @@ -11,7 +11,6 @@ export class Identity { } export class Client { - // constructor(gateway_url, ownAddress, registeredCallback) { constructor(directoryUrl, identity, authToken) { this.authToken = authToken this.gateway = null; // {socketAddress, mixAddress, conn} @@ -20,6 +19,10 @@ export class Client { this.topologyEndpoint = directoryUrl + "/api/presence/topology"; } + formatAsRecipient() { + return `${this.identity.address}@${this.gateway.mixAddress}` + } + async start() { await this.updateTopology(); this._getInitialGateway(); @@ -206,7 +209,6 @@ export class Client { } } - function makeRegisterRequest(address) { return JSON.stringify({ "type": "register", "address": address }); } diff --git a/clients/webassembly/js-example/index.html b/clients/webassembly/js-example/index.html index bfb8db4247a..658a11609e9 100644 --- a/clients/webassembly/js-example/index.html +++ b/clients/webassembly/js-example/index.html @@ -9,14 +9,14 @@

- +

- +

- +

Send messages to the mixnet using the "send" button.

diff --git a/clients/webassembly/js-example/index.js b/clients/webassembly/js-example/index.js index e5ca4c896db..7ac63abf86a 100644 --- a/clients/webassembly/js-example/index.js +++ b/clients/webassembly/js-example/index.js @@ -17,17 +17,16 @@ import { Identity } from "nym-client-wasm/client" - async function main() { let directory = "https://qa-directory.nymtech.net"; - // let identity = new Identity(); // or load one from storage if you have one already - // because I'm about to make a new PR tomorrow, just hardcode it to not recreate client every single recompilation - let identity = { address: "7mVwY9uFRBBTW91dAWaDtuusSpzc16ZwANLSXxXVYT7M", privateKey: "H4cp7DFMsPXD4Qm7ZW3TgsJETr2CpJhxmBJohko7HrDE", publicKey: "7mVwY9uFRBBTW91dAWaDtuusSpzc16ZwANLSXxXVYT7M" } + let identity = new Identity(); // or load one from storage if you have one already - document.getElementById("sender").value = identity.address; + document.getElementById("sender").value = "loading..."; let nymClient = new Client(directory, identity, null); // provide your authToken if you've registered before + nymClient.onEstablishedGatewayConnection = (_) => document.getElementById("sender").value = nymClient.formatAsRecipient() // overwrite default behaviour with our implementation nymClient.onParsedBlobResponse = displayReceived // overwrite default behaviour with our implementation + nymClient.onErrorResponse = (event) => alert("Received invalid gateway response", event.data) await nymClient.start(); const sendButton = document.querySelector('#send-button'); diff --git a/clients/webassembly/js-example/package-lock.json b/clients/webassembly/js-example/package-lock.json index 4b5e2e810c0..4eae5465936 100644 --- a/clients/webassembly/js-example/package-lock.json +++ b/clients/webassembly/js-example/package-lock.json @@ -3135,9 +3135,6 @@ "path-key": "2.0.1" } }, - "nym-client-wasm": { - "version": "file:../pkg" - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", diff --git a/clients/webassembly/src/lib.rs b/clients/webassembly/src/lib.rs index b087eb5e068..521e29f57b5 100644 --- a/clients/webassembly/src/lib.rs +++ b/clients/webassembly/src/lib.rs @@ -15,7 +15,7 @@ use crypto::identity::MixIdentityPublicKey; use models::Topology; use nymsphinx::addressing::nodes::NymNodeRoutingAddress; use nymsphinx::Node as SphinxNode; -use nymsphinx::{delays, Destination, DestinationAddressBytes, NodeAddressBytes, SphinxPacket}; +use nymsphinx::{delays, Destination, NodeAddressBytes, SphinxPacket}; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::convert::TryInto; @@ -27,6 +27,7 @@ mod models; mod utils; pub use models::keys::keygen; +use nymsphinx::addressing::clients::Recipient; // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global // allocator. @@ -55,17 +56,14 @@ pub struct NodeData { /// Message chunking is currently not implemented. If the message exceeds the /// capacity of a single Sphinx packet, the extra information will be discarded. #[wasm_bindgen] -pub fn create_sphinx_packet(topology_json: &str, msg: &str, destination: &str) -> Vec { +pub fn create_sphinx_packet(topology_json: &str, msg: &str, recipient: &str) -> Vec { utils::set_panic_hook(); // nicer js errors. - if topology_json.len() == 0 { - panic!("WTF2?"); - } - let route = sphinx_route_to(topology_json, destination); + let recipient = Recipient::try_from_string(recipient).unwrap(); + + let route = sphinx_route_to(topology_json, &recipient.gateway()); let average_delay = Duration::from_secs_f64(0.1); let delays = delays::generate_from_average_duration(route.len(), average_delay); - let dest_bytes = DestinationAddressBytes::try_from_base58_string(destination).unwrap(); - let dest = Destination::new(dest_bytes, Default::default()); // TODO: once we are able to reconstruct split messages use this instead // let split_message = split_and_prepare_payloads(&msg.as_bytes()); @@ -74,7 +72,8 @@ pub fn create_sphinx_packet(topology_json: &str, msg: &str, destination: &str) - let message = msg.as_bytes().to_vec(); - let sphinx_packet = SphinxPacket::new(message, &route, &dest, &delays, None).unwrap(); + let destination = Destination::new(recipient.destination(), Default::default()); + let sphinx_packet = SphinxPacket::new(message, &route, &destination, &delays, None).unwrap(); payload(sphinx_packet, route) } @@ -103,12 +102,11 @@ fn payload(sphinx_packet: SphinxPacket, route: Vec) -> Vec { /// /// This function panics if the supplied `raw_route` json string can't be /// extracted to a `JsonRoute`. -fn sphinx_route_to(topology_json: &str, recipient: &str) -> Vec { +fn sphinx_route_to(topology_json: &str, gateway_address: &NodeAddressBytes) -> Vec { let topology = Topology::new(topology_json); - let recipient_address = DestinationAddressBytes::try_from_base58_string(recipient).unwrap(); let route = topology - .random_route_to_client(recipient_address) - .expect("invalid route produced - perhaps client has never registered?"); + .random_route_to_gateway(gateway_address) + .expect("invalid route produced"); assert_eq!(4, route.len()); route } @@ -155,7 +153,7 @@ mod test_constructing_a_sphinx_packet { let mut payload = create_sphinx_packet( topology_fixture(), "foomp", - "5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq", + "5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq@7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh", ); // you don't really need 32 bytes here, but giving too much won't make it fail let mut address_buffer = [0; 32]; @@ -174,7 +172,13 @@ mod building_a_topology_from_json { #[test] #[should_panic] fn panics_on_empty_string() { - sphinx_route_to("", "5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq"); + sphinx_route_to( + "", + &NodeAddressBytes::try_from_base58_string( + "7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh", + ) + .unwrap(), + ); } #[test] @@ -182,7 +186,10 @@ mod building_a_topology_from_json { fn panics_on_bad_json() { sphinx_route_to( "bad bad bad not json", - "5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq", + &NodeAddressBytes::try_from_base58_string( + "7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh", + ) + .unwrap(), ); } @@ -192,7 +199,13 @@ mod building_a_topology_from_json { let mut topology: Topology = serde_json::from_str(topology_fixture()).unwrap(); topology.mix_nodes = vec![]; let json = serde_json::to_string(&topology).unwrap(); - sphinx_route_to(&json, "5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq"); + sphinx_route_to( + &json, + &NodeAddressBytes::try_from_base58_string( + "7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh", + ) + .unwrap(), + ); } #[test] @@ -202,14 +215,23 @@ mod building_a_topology_from_json { let node = topology.mix_nodes.first().unwrap().clone(); topology.mix_nodes = vec![node]; // 1 mixnode isn't enough. Panic! let json = serde_json::to_string(&topology).unwrap(); - sphinx_route_to(&json, "5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq"); + sphinx_route_to( + &json, + &NodeAddressBytes::try_from_base58_string( + "7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh", + ) + .unwrap(), + ); } #[test] fn test_works_on_happy_json() { let route = sphinx_route_to( topology_fixture(), - "5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq", + &NodeAddressBytes::try_from_base58_string( + "7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh", + ) + .unwrap(), ); assert_eq!(4, route.len()); } diff --git a/clients/webassembly/src/models/mod.rs b/clients/webassembly/src/models/mod.rs index 32c0885321d..177d3a0e085 100644 --- a/clients/webassembly/src/models/mod.rs +++ b/clients/webassembly/src/models/mod.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use nymsphinx::DestinationAddressBytes; use nymsphinx::Node as SphinxNode; +use nymsphinx::NodeAddressBytes; use rand::seq::IteratorRandom; use serde::{Deserialize, Serialize}; use std::cmp::max; @@ -27,6 +27,7 @@ pub mod keys; pub mod mixnodes; pub mod providers; +// JS: can we just get rid of this? It's mostly just copied (and un-updated) code from NymTopology trait // Topology shows us the current state of the overall Nym network #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -39,8 +40,8 @@ pub struct Topology { impl Topology { pub fn new(json: &str) -> Self { - if json.len() == 0 { - panic!("WTF?"); + if json.is_empty() { + panic!("empty json passed"); } serde_json::from_str(json).unwrap() } @@ -92,15 +93,23 @@ impl Topology { Ok(route) } - // Sets up a route to a specific provider - pub fn random_route_to( + // Sets up a route to a specific gateway + pub fn random_route_to_gateway( &self, - gateway_node: SphinxNode, + gateway_address: &NodeAddressBytes, ) -> Result, NymTopologyError> { + let b58_address = gateway_address.to_base58_string(); + let gateway_node = self + .gateways() + .iter() + .cloned() + .find(|gateway| gateway.pub_key == b58_address.clone()) + .ok_or_else(|| NymTopologyError::InvalidMixLayerError)?; + Ok(self .random_mix_route()? .into_iter() - .chain(std::iter::once(gateway_node)) + .chain(std::iter::once(gateway_node.into())) .collect()) } @@ -128,21 +137,6 @@ impl Topology { .map(|x| x.clone().into()) .collect() } - - pub(crate) fn random_route_to_client( - &self, - client_address: DestinationAddressBytes, - ) -> Option> { - let b58_address = client_address.to_base58_string(); - - let gateway = self - .gateways() - .iter() - .cloned() - .find(|gateway| gateway.has_client(b58_address.clone()))?; - - self.random_route_to(gateway.into()).ok() - } } #[derive(Debug)] diff --git a/common/nymsphinx/src/addressing/clients.rs b/common/nymsphinx/src/addressing/clients.rs new file mode 100644 index 00000000000..b64e4a3a477 --- /dev/null +++ b/common/nymsphinx/src/addressing/clients.rs @@ -0,0 +1,95 @@ +// This is still not an ideal home for this struct, because it's not an +// universal nymsphinx addressing method, however, it needs to be +// accessible by both desktop and webassembly client (it's more +// of a helper/utils structure, because before it reaches the gateway +// it's already destructed). + +use crate::{ + DestinationAddressBytes, NodeAddressBytes, DESTINATION_ADDRESS_LENGTH, NODE_ADDRESS_LENGTH, +}; + +#[derive(Debug)] +pub struct RecipientFormattingError; + +impl From for RecipientFormattingError { + fn from(_: crate::Error) -> Self { + Self + } +} + +// TODO: this should a different home... somewhere, but where? +#[derive(Clone, Debug)] +pub struct Recipient { + destination: DestinationAddressBytes, + gateway: NodeAddressBytes, +} + +impl Recipient { + pub const LEN: usize = DESTINATION_ADDRESS_LENGTH + NODE_ADDRESS_LENGTH; + + pub fn new(destination: DestinationAddressBytes, gateway: NodeAddressBytes) -> Self { + Recipient { + destination, + gateway, + } + } + + pub fn destination(&self) -> DestinationAddressBytes { + self.destination.clone() + } + + pub fn gateway(&self) -> NodeAddressBytes { + self.gateway.clone() + } + + pub fn into_bytes(self) -> [u8; Self::LEN] { + let mut out = [0u8; Self::LEN]; + out[..DESTINATION_ADDRESS_LENGTH].copy_from_slice(self.destination.as_bytes()); + out[DESTINATION_ADDRESS_LENGTH..].copy_from_slice(self.gateway.as_bytes()); + + out + } + + pub fn from_bytes(bytes: [u8; Self::LEN]) -> Self { + let mut destination_bytes = [0u8; DESTINATION_ADDRESS_LENGTH]; + destination_bytes.copy_from_slice(&bytes[..DESTINATION_ADDRESS_LENGTH]); + + let mut gateway_address_bytes = [0u8; NODE_ADDRESS_LENGTH]; + gateway_address_bytes.copy_from_slice(&bytes[DESTINATION_ADDRESS_LENGTH..]); + + let destination = DestinationAddressBytes::from_bytes(destination_bytes); + let gateway = NodeAddressBytes::from_bytes(gateway_address_bytes); + + Self { + destination, + gateway, + } + } + + pub fn try_from_string>( + full_address: S, + ) -> Result { + let string_address = full_address.into(); + let split: Vec<_> = string_address.split('@').collect(); + if split.len() != 2 { + return Err(RecipientFormattingError); + } + let destination = DestinationAddressBytes::try_from_base58_string(split[0])?; + let gateway = NodeAddressBytes::try_from_base58_string(split[1])?; + Ok(Recipient { + destination, + gateway, + }) + } +} + +impl std::fmt::Display for Recipient { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}@{}", + self.destination.to_base58_string(), + self.gateway.to_base58_string() + ) + } +} diff --git a/common/nymsphinx/src/addressing/mod.rs b/common/nymsphinx/src/addressing/mod.rs index eef8be84427..dd2c47e8c0d 100644 --- a/common/nymsphinx/src/addressing/mod.rs +++ b/common/nymsphinx/src/addressing/mod.rs @@ -12,4 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod clients; pub mod nodes;