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/clean up #238

Merged
merged 14 commits into from
May 19, 2020
Merged
Show file tree
Hide file tree
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 Cargo.lock

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

5 changes: 3 additions & 2 deletions clients/webassembly/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
[package]
name = "nym-client-wasm"
version = "0.1.3"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "0.7.0-pre"
edition = "2018"
repository = "https://github.com/nymtech/nym"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
repository = "https://github.com/nymtech/nym"

[lib]
crate-type = ["cdylib", "rlib"]
Expand Down
37 changes: 27 additions & 10 deletions clients/webassembly/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,42 @@
## Nym Sphinx in WebAssembly

This is a Rust crate which is set up to automatically cross-compile the contents of `lib.rs` to WebAssembly (aka wasm).
## About

Wasm is pretty close to bare metal. Browser-based or server-side JavaScript (or other wasm-using environments) can use the wasm output from this crate to create Sphinx packets at much higher speeds than would be possible using (interpreted) JavaScript. This enables browser-based and mobile applications get stronger privacy, in a way that wasn't previously possible.
This is an npm package which allows JavaScript programmers (or anyone else who can use WebAssembly in their applications) to produce layer-encrypted [Sphinx](http://www0.cs.ucl.ac.uk/staff/G.Danezis/papers/sphinx-eprint.pdf) packets for use with [Nym](https://nymtech.net/docs) mixnets. It's written in Rust and compiled to WebAssembly.

### Compiling
Sphinx packets are designed to ensure the privacy of information in transit, even when the adversary is able to monitor the network in its entirety. When used with a mixnet, both content (what you said) and metadata (who you said it to, when you said it) are protected.

First, make sure you've got all the [Rust wasm toolchain](https://rustwasm.github.io/book/game-of-life/setup.html) installed. Cross-compilation sounds scary but the Rust crew have enabled a remarkably simple setup.
This helps browser-based and mobile applications get stronger privacy, in a way that wasn't previously possible.

## Security Status

From a security point of view, this module is not yet complete. A key missing feature, cover traffic, will be implemented soon. You can build your applications, but don't rely on it for strong anonymity yet if your application needs cover traffic.

### Using it as a JavaScripter
## Using it

See our [docs](https://nymtech.net/docs).
See the [Nym docs](https://nymtech.net/docs).

### Demo

There's a demo web application in the `js-example` folder. To run it, first make sure you've got a recent `npm` installed, then follow the instructions in its README.

### Developing
## Developing

This is a Rust crate which is set up to automatically cross-compile the contents of `src` to WebAssembly (aka wasm). It's published from the main [Nym platform monorepo](https://github.com/nymtech/nym) in the `clients/webassembly` directory.

First, make sure you've got all the [Rust wasm toolchain](https://rustwasm.github.io/book/game-of-life/setup.html) installed. Cross-compilation sounds scary but the Rust crew have enabled a remarkably simple setup.

Whenever you change any Rust in the `src` directory, run `wasm-pack build --scope nymproject` to update the built wasm artefact in the `pkg` directory.

For now, when you compile `nym-client-wasm` using `wasm-pack build --scope nymproject` you will need to manually copy the file `client.js` into the `pkg` and add it to `package.json`. Once [these](https://github.com/rustwasm/wasm-pack/issues/840) [issues](https://github.com/rustwasm/rfcs/pull/8#issuecomment-564725214) get closed, this annoying extra step will go away.

To be clear, this is not something that most JS developers need to worry about, this is only for Nym devs. The packages on NPM have all files in place. Just install and enjoy!

Whenever you change your Rust, run `wasm-pack build` to update the built was artefact in the `pkg` directory.
### Packaging

# IMPORTANT!!
If you're a Nym platform developer who's made changes to the Rust (or JS) files and wants to re-publish the package to NPM, here's how you do it:

You also need to manually copy `helpers.js` file into the `pkg` and add it to `package.json`. I'm 100% sure one of the hundreds JS packagers could do that for us, but I didn't feel like spending 10 hours figuring this one out.
1. `wasm-pack build --scope nymproject` builds the wasm binaries into the `pkg` directory (not in source control)
2. copy `client.js` into the `pkg` folder and add it to the `package.json` manifest
3. bump version numbers as necessary for SemVer
4. `wasm-pack publish --access=public` will publish your changed package to NPM
240 changes: 240 additions & 0 deletions clients/webassembly/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import * as wasm from "nym-client-wasm";

export class Identity {
// in the future this should allow for loading from local storage
constructor() {
const raw_identity = JSON.parse(wasm.keygen());
this.address = raw_identity.address;
this.privateKey = raw_identity.private_key;
this.publicKey = raw_identity.public_key;
}
}

export class Client {
// constructor(gateway_url, ownAddress, registeredCallback) {
constructor(directoryUrl, identity, authToken) {
this.authToken = authToken
this.gateway = null; // {socketAddress, mixAddress, conn}
this.identity = identity;
this.topology = null;
this.topologyEndpoint = directoryUrl + "/api/presence/topology";
}

async start() {
await this.updateTopology();
this._getInitialGateway();
await this.establishGatewayConnection();
// TODO: a way to somehow await for our authenticate response to be processed
}

_isRegistered() {
return this.authToken !== null
}

async updateTopology() {
let response = await http('get', this.topologyEndpoint);
let topology = JSON.parse(response); // make sure it's a valid json
console.log(topology);
this.topology = topology;
this.onUpdatedTopology();
return topology;
}

/* Gets the address of a Nym gateway to send the Sphinx packet to.
At present we choose the first gateway as the network should only be running
one. Later, we will implement multiple gateways. */
_getInitialGateway() {
if (this.gateway !== null) {
console.error("tried to re-initialise gateway data");
return;
}
if (this.topology === null || this.topology.gatewayNodes.length === 0) {
console.error("No gateways available on the network")
}
this.gateway = {
socketAddress: this.topology.gatewayNodes[0].clientListener,
mixAddress: this.topology.gatewayNodes[0].pubKey,
conn: null,
}
}

establishGatewayConnection() {
return new Promise((resolve, reject) => {
const conn = new WebSocket(this.gateway.socketAddress);
conn.onclose = this.onGatewayConnectionClose;
conn.onerror = (event) => {
this.onGatewayConnectionError(event);
reject(event);
};
conn.onmessage = (event) => this.onGatewayMessage(event);
conn.onopen = (event) => {
this.onEstablishedGatewayConnection(event);
if (this._isRegistered()) {
this.sendAuthenticateRequest();
resolve(); // TODO: we should wait for authenticateResponse...
} else {
this.sendRegisterRequest();
resolve(); // TODO: we should wait for registerResponse...
}
}

this.gateway.conn = conn;
})
}

sendRegisterRequest() {
const registerReq = makeRegisterRequest(this.identity.address);
this.gateway.conn.send(registerReq);
this.onRegisterRequestSend();
}

sendAuthenticateRequest(token) {
const authenticateReq = makeAuthenticateRequest(this.identity.address, token);
this.conn.send(authenticateReq);
this.onAuthenticateRequestSend();
}

/*
NOTE: this currently does not implement chunking and messages over ~1KB
will cause a panic. This will be fixed in a future version.
*/
sendMessage(message, recipient) {
if (this.gateway === null || this.gateway.conn === null) {
console.error("Client was not initialised");
return
}
if (message instanceof Blob || message instanceof ArrayBuffer) {
// but it wouldn't be difficult to implement it.
console.error("Binary messages are not yet supported");
return
}
// TODO: CURRENTLY WE ONLY ACCEPT "recipient", not recipient@gateway
// this will be changed in the very next PR
console.log("send", this.topology)
const sphinxPacket = wasm.create_sphinx_packet(JSON.stringify(this.topology), message, recipient);
this.gateway.conn.send(sphinxPacket);
this.onMessageSend();
}

onGatewayMessage(event) {
if (event.data instanceof Blob) {
this.onBlobResponse(event);
} else {
const receivedData = JSON.parse(event.data);
switch (receivedData.type) {
case "send": return this.onSendConfirmation(event);
case "authenticate": return this.onAuthenticateResponse(event);
case "error": return this.onErrorResponse(event);
case "register": return this.onRegisterResponse(event);
default: return this.onUnknownResponse(event);
}
}
}

// all the callbacks that can be overwritten

onUpdatedTopology() {
console.log("Default: Updated topology")
}

onEstablishedGatewayConnection(event) {
console.log("Default: Established gateway connection", event);
}

onGatewayConnectionClose(event) {
console.log("Default: The the connection to gateway was closed", event);
}

onGatewayConnectionError(event) {
console.error("Default: Gateway connection error: ", event);
}

onAuthenticateRequestSend() {
console.log("Default: sent authentication request");
}

onRegisterRequestSend() {
console.log("Default: sent register request");
}

onMessageSend() {
console.log("Default: sent message through gateway to the mixnet");
}

onAuthenticated() {
console.log("Default: we are authenticated");
}

onSendConfirmation(event) {
console.log("Default: received send confirmation", event.data);
}

onRegisterResponse(event) {
console.log("Default: received register response", event.data);
}

onAuthenticateResponse(event) {
console.log("Default: received authentication response", event.data);
}

onErrorResponse(event) {
console.error("Received error response", event.data);
}

onUnknownResponse(event) {
console.error("Received unknown response", event);
}

// Gateway returns any received data from the mix network as a Blob,
// So most likely this is your best bet to override
onBlobResponse(event) {
// note that the actual handling depends on the expected content, however,
// in this example we're just sending a text messages to ourselves and hence
// we can safely read it as text
let reader = new FileReader();

reader.onload = () => {
this.onParsedBlobResponse(reader.result)
};

reader.readAsText(event.data);
}

// Alternatively you may use default implementation and get everything as a text
onParsedBlobResponse(data) {
console.log("Default: parsed the following data", data);
}
}


function makeRegisterRequest(address) {
return JSON.stringify({ "type": "register", "address": address });
}

function makeAuthenticateRequest(address, token) {
return JSON.stringify({ "type": "authenticate", "address": address, "token": token });
}

function http(method, url) {
return new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
} else {
reject({
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.send();
});
}
52 changes: 0 additions & 52 deletions clients/webassembly/helpers.js

This file was deleted.

3 changes: 2 additions & 1 deletion clients/webassembly/js-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ This example application demonstrates how to use WebAssembly to create Sphinx pa
## 🚴 Usage

```
npm run start # fires up a web page at http://localhost:8001
npm install # set up dependencies
npm run start # starts a web server at http://localhost:8001
```

Check your dev console for output.
Expand Down
12 changes: 8 additions & 4 deletions clients/webassembly/js-example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@

<body>
<p>
<label for="fname">Recipient address: </label><input type="text" id="recipient" name="recipient"
value="">
<label for="fname">Our address: </label><input disabled="true" size="40" type="text" id="sender" value="">
</p>
<label for="fname">Text to send: </label><input type="text" id="sendtext" name="sendtext" value="Hello mixnet!">
<button id="send-button">Send</button>

<p>
<label for="fname">Recipient address: </label><input size="40" 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>


<p>Send messages to the mixnet using the "send" button.</p>
<p><span style='color: blue;'>Sent</span> messages show in blue, <span style='color: green;'>received</span>
Expand Down
Loading