Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/explicit gateway addressing #239

Merged
merged 18 commits into from
May 19, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clients/desktop/examples/websocket_filesend.rs
Original file line number Diff line number Diff line change
@@ -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};

1 change: 1 addition & 0 deletions clients/desktop/examples/websocket_textsend.rs
Original file line number Diff line number Diff line change
@@ -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();
2 changes: 1 addition & 1 deletion clients/desktop/src/client/cover_traffic_stream.rs
Original file line number Diff line number Diff line change
@@ -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;
85 changes: 1 addition & 84 deletions clients/desktop/src/client/inbound_messages.rs
Original file line number Diff line number Diff line change
@@ -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<InputMessage>;
pub(crate) type InputMessageReceiver = mpsc::UnboundedReceiver<InputMessage>;
@@ -23,83 +20,3 @@ impl InputMessage {
(self.recipient, self.data)
}
}

#[derive(Debug)]
pub struct RecipientFormattingError;

impl From<nymsphinx::Error> 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<Self, RecipientFormattingError> {
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()
)
}
}
6 changes: 1 addition & 5 deletions clients/desktop/src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
3 changes: 2 additions & 1 deletion clients/desktop/src/client/real_traffic_stream.rs
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 2 additions & 1 deletion clients/desktop/src/websocket/handler.rs
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion clients/desktop/src/websocket/types.rs
Original file line number Diff line number Diff line change
@@ -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;
6 changes: 4 additions & 2 deletions clients/webassembly/client.js
Original file line number Diff line number Diff line change
@@ -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 });
}
6 changes: 3 additions & 3 deletions clients/webassembly/js-example/index.html
Original file line number Diff line number Diff line change
@@ -9,14 +9,14 @@

<body>
<p>
<label for="fname">Our address: </label><input disabled="true" size="40" type="text" id="sender" value="">
<label for="fname">Our address: </label><input disabled="true" size="120" type="text" id="sender" value="">
</p>

<p>
<label for="fname">Recipient address: </label><input size="40" type="text" id="recipient" value="">
<label for="fname">Recipient address: </label><input size="120" type="text" id="recipient" value="">
</p>
<label for="fname">Text to send: </label><input type="text" id="sendtext" value="Hello mixnet!">
<button id="send-button">Send</button><button id="refresh-button">Refresh</button>
<button id="send-button">Send</button>


<p>Send messages to the mixnet using the "send" button.</p>
9 changes: 4 additions & 5 deletions clients/webassembly/js-example/index.js
Original file line number Diff line number Diff line change
@@ -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');
3 changes: 0 additions & 3 deletions clients/webassembly/js-example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 41 additions & 19 deletions clients/webassembly/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<u8> {
pub fn create_sphinx_packet(topology_json: &str, msg: &str, recipient: &str) -> Vec<u8> {
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<SphinxNode>) -> Vec<u8> {
///
/// 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<SphinxNode> {
fn sphinx_route_to(topology_json: &str, gateway_address: &NodeAddressBytes) -> Vec<SphinxNode> {
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,15 +172,24 @@ 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]
#[should_panic]
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());
}
38 changes: 16 additions & 22 deletions clients/webassembly/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<SphinxNode>, 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<Vec<SphinxNode>> {
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)]
95 changes: 95 additions & 0 deletions common/nymsphinx/src/addressing/clients.rs
Original file line number Diff line number Diff line change
@@ -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<crate::Error> 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<S: Into<String>>(
full_address: S,
) -> Result<Self, RecipientFormattingError> {
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()
)
}
}
1 change: 1 addition & 0 deletions common/nymsphinx/src/addressing/mod.rs
Original file line number Diff line number Diff line change
@@ -12,4 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.

pub mod clients;
pub mod nodes;