Skip to content

Commit

Permalink
Improve Network type to better support private tangles (#360)
Browse files Browse the repository at this point in the history
* Make `Network` not `Copy`

This is in preparation for adding another
non-`Copy` variant later.

* Add `Other` variant in `Network`

* Add node.js DID creation on private tangle example

* Add `from_name` in Wasm and fix absence of `Copy`

* Remove superfluous imports

* Add browser Wasm example

* Remove old variable

* Add low-level Rust API example

* Format Wasm network source code

* Add account Rust API example

* Use same ideas for account & low-level examples

* Rename `Network::as_str` to `name`

* Impl spec compliance check for `Network` names

* Change `IotaDID::network` return type to `Result`

* Change bindings according to new return type

* Improve Wasm examples with resolution step

* Add private tangle test

* Add cypress browser test

* Use `?` instead of `unwrap` where possible

* Add explorer url to `Network::Other` variant

* Add explorer_url tests

* Merge branch 'dev' into feat/private-tangle-network

* Fix `explorer_url` conversion

* Fix `message_url` in new examples

* Fix network names in Rust private tangle examples

* Update bindings/wasm/examples/node/test.js

Co-authored-by: Craig Bester <[email protected]>

* Update bindings/wasm/cypress/integration/browser_test.js

Co-authored-by: Craig Bester <[email protected]>

* Update bindings/wasm/examples/node/private_tangle.js

Co-authored-by: Craig Bester <[email protected]>

* Update bindings/wasm/examples/browser/private_tangle.js

Co-authored-by: Craig Bester <[email protected]>

* Update examples/account/private_tangle.rs

Co-authored-by: Craig Bester <[email protected]>

* Update examples/low-level-api/private_tangle.rs

Co-authored-by: Craig Bester <[email protected]>

Co-authored-by: Craig Bester <[email protected]>
  • Loading branch information
PhilippGackstatter and cycraig authored Aug 24, 2021
1 parent 4bc3313 commit e8f9e77
Show file tree
Hide file tree
Showing 27 changed files with 465 additions and 62 deletions.
14 changes: 14 additions & 0 deletions bindings/wasm/cypress/integration/browser_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { createVP } from "../../examples/browser/create_vp.js";
import { createDiff } from "../../examples/browser/diff_chain.js";
import { revoke } from "../../examples/browser/revoke_vc.js";
import { merkleKey } from "../../examples/browser/merkle_key.js";
import { createIdentityPrivateTangle } from "../../examples/browser/private_tangle";
import { resolveHistory } from "../../examples/browser/resolve_history";

// Test that the browser examples do not throw uncaught exceptions twice, including syntax errors etc.
Expand Down Expand Up @@ -83,13 +84,26 @@ describe(
await merkleKey(defaultClientConfig(), false);
}
});

it("private tangle", async function () {
try {
await createIdentityPrivateTangle(false, false)
throw new Error("Did not throw.")
} catch (err) {
// Example is expected to throw an error because no private Tangle is running
expect(err.name).to.eq("ClientError")
expect(err.message).to.contain("error sending request")
}
});

it("diff chain", async function () {
try {
await createDiff(defaultClientConfig(), false);
} catch (e) {
await createDiff(defaultClientConfig(), false);
}
});

it("resolve history", async function () {
try {
await resolveHistory(defaultClientConfig(), false);
Expand Down
2 changes: 1 addition & 1 deletion bindings/wasm/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The following examples are currently available:
| 7 | [revoke_vc](node/revoke_vc.js) | Remove a verification method from the Issuers DID Document, making the Verifiable Credential it signed unable to verify, effectively revoking the VC. |
| 8 | [resolution](node/resolution.js) | Resolves an existing DID to return the latest DID Document. |
| 9 | [merkle_key](node/merkle_key.js) | Adds a MerkleKeyCollection verification method to an Issuers DID Document and signs a Verifiable Credential with the key on index 0. Afterwards the key on index 0 is deactivated, making the Verifiable Credential fail its verification. |

| 10 | [private_tangle](node/private_tangle.js) | Showcases the same procedure as `create_did`, but on a private tangle - a locally running hornet node. |


### Browser Examples
Expand Down
7 changes: 6 additions & 1 deletion bindings/wasm/examples/browser/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@

<button class="ma-md block" id="merkle_key_btn">Merkle Key</button>

<div class="ma-md">
<input placeholder="Private Tangle REST API URL" id="create-private-rest-url" />
<input placeholder="Private Tangle Network Name" id="create-private-network-name" />
<button class="ma-md block" id="private_tangle_btn">Create DID on Private Tangle</button>
</div>

<button class="ma-md block" id="diff_chain_btn">
Diff Chain
</button>
Expand All @@ -55,7 +61,6 @@
Resolve History
</button>


<button
class="ma-md block"
id="clear_output"
Expand Down
6 changes: 6 additions & 0 deletions bindings/wasm/examples/browser/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { resolveIdentity } from "./resolve.js";
import { createVP } from "./create_vp.js";
import { revoke } from "./revoke_vc.js";
import { merkleKey } from "./merkle_key.js";
import { createIdentityPrivateTangle } from "./private_tangle.js";
import { createDiff } from "./diff_chain.js";
import { resolveHistory } from "./resolve_history.js";

Expand Down Expand Up @@ -47,6 +48,11 @@ document
.querySelector("#merkle_key_btn")
.addEventListener("click", () => merkleKey(clientConfig));

//handle private tangle DID creation on click event
document
.querySelector("#private_tangle_btn")
.addEventListener("click", () => createIdentityPrivateTangle());

//handle diff chain on click event
document
.querySelector("#diff_chain_btn")
Expand Down
64 changes: 64 additions & 0 deletions bindings/wasm/examples/browser/private_tangle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { Client, Config, Document, KeyType, Network } from "../../web/identity_wasm.js";
import {
logObjectToScreen,
logToScreen,
} from "./utils.js";

/**
This example shows how a DID document can be created on a private tangle.
It can be run together with a local hornet node.
Refer to https://github.com/iotaledger/one-click-tangle/tree/chrysalis/hornet-private-net
for setup instructions.
**/
export async function createIdentityPrivateTangle(inBrowser = true, log = true) {
if (log) logToScreen("Identity creation started...");
if (log) logToScreen("This might take a few seconds to complete proof of work!");

let restURL
let networkName

if (inBrowser) {
// Get the required parameters from the input fields
restURL = document.querySelector("#create-private-rest-url").value;
networkName = document.querySelector("#create-private-network-name").value;
} else {
restURL = "http://127.0.0.1:14265/";
networkName = "custom";
}

// This is an arbitrarily defined network name
const network = Network.from_name(networkName);

// Create a DID Document (an identity).
const { doc, key } = new Document(KeyType.Ed25519, network.toString());

// Sign the DID Document with the generated key.
doc.sign(key);

// Create a client configuration and set the custom network.
const config = new Config();
config.setNetwork(network);

// This URL should point to the REST API of a node.
config.setNode(restURL);

// Create a client instance from the configuration to publish messages to the Tangle.
const client = Client.fromConfig(config);

// Publish the Identity to the IOTA Network, this may take a few seconds to complete Proof-of-Work.
const receipt = await client.publishDocument(doc.toJSON());

if (log) logToScreen("Identity creation done!");

// Make sure the DID can be resolved on the private tangle
const resolved = await client.resolve(doc.id.toString());

if (log) logToScreen("Resolved DID document:");
if (log) logObjectToScreen(resolved);

// Return the results.
return { key, doc, receipt };
}
3 changes: 3 additions & 0 deletions bindings/wasm/examples/node/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

const {createIdentity} = require("./create_did");
const {manipulateIdentity} = require("./manipulate_did");
const {createIdentityPrivateTangle} = require("./private_tangle");
const {resolution} = require("./resolution");
const {createVC} = require("./create_vc");
const {createVP} = require("./create_vp");
Expand Down Expand Up @@ -35,6 +36,8 @@ async function main() {
return await createVP(CLIENT_CONFIG);
case "merkle_key":
return await merkleKey(CLIENT_CONFIG);
case "private_tangle":
return await createIdentityPrivateTangle();
case "resolve_history":
return await resolveHistory(CLIENT_CONFIG);
case "diff_chain":
Expand Down
42 changes: 42 additions & 0 deletions bindings/wasm/examples/node/private_tangle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

const { Client, Config, Document, KeyType, Network } = require('../../node/identity_wasm')

/**
This example shows how a DID document can be created on a private tangle.
It can be run together with a local hornet node.
Refer to https://github.com/iotaledger/one-click-tangle/tree/chrysalis/hornet-private-net
for setup instructions.
**/
async function createIdentityPrivateTangle() {
// This is an arbitrarily defined network name
const network = Network.from_name("custom");

// Create a DID Document (an identity).
const { doc, key } = new Document(KeyType.Ed25519, network.toString());

// Sign the DID Document with the generated key.
doc.sign(key);

// Create a client configuration and set the custom network.
const config = new Config();
config.setNetwork(network);

// This URL points to the REST API of the locally running hornet node.
config.setNode("http://127.0.0.1:14265/");

// Create a client instance from the configuration to publish messages to the Tangle.
const client = Client.fromConfig(config);

// Publish the Identity to the IOTA Network, this may take a few seconds to complete Proof-of-Work.
const receipt = await client.publishDocument(doc.toJSON());

// Make sure the DID can be resolved on the private tangle
const resolved = await client.resolve(doc.id.toString());

// Return the results.
return { key, resolved, receipt };
}

exports.createIdentityPrivateTangle = createIdentityPrivateTangle;
11 changes: 11 additions & 0 deletions bindings/wasm/examples/node/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { revokeVC } = require("./revoke_vc");
const { merkleKey } = require("./merkle_key");
const { resolveHistory } = require("./resolve_history");
const { CLIENT_CONFIG } = require("./config");
const { createIdentityPrivateTangle } = require("./private_tangle");

jest.setTimeout(180000); // 3 minutes to account for spurious network delays, most tests pass in a few seconds

Expand Down Expand Up @@ -63,6 +64,16 @@ test.concurrent("Merkle Key", async () => {
await merkleKey(CLIENT_CONFIG);
}
});
test.concurrent("Private Tangle", async () => {
try {
await createIdentityPrivateTangle()
throw new Error("Did not throw.")
} catch (err) {
// Example is expected to throw an error because no private Tangle is running
expect(err.name).toEqual("ClientError")
expect(err.message).toContain("error sending request")
}
});
test.concurrent("Diff Chain", async () => {
try {
await createDiff(CLIENT_CONFIG);
Expand Down
4 changes: 2 additions & 2 deletions bindings/wasm/src/did/wasm_did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ impl WasmDID {

/// Returns the IOTA tangle network of the `DID`.
#[wasm_bindgen(getter)]
pub fn network(&self) -> WasmNetwork {
self.0.network().into()
pub fn network(&self) -> Result<WasmNetwork, JsValue> {
self.0.network().map(Into::into).map_err(wasm_error)
}

/// Returns the IOTA tangle network of the `DID`.
Expand Down
2 changes: 1 addition & 1 deletion bindings/wasm/src/tangle/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl Config {

#[wasm_bindgen(js_name = setNetwork)]
pub fn set_network(&mut self, network: &WasmNetwork) -> Result<(), JsValue> {
self.with_mut(|builder| builder.network((*network).into()))
self.with_mut(|builder| builder.network(network.clone().into()))
}

#[wasm_bindgen(js_name = setNode)]
Expand Down
28 changes: 20 additions & 8 deletions bindings/wasm/src/tangle/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ use identity::iota::Network as IotaNetwork;

use wasm_bindgen::prelude::*;

use crate::error::wasm_error;

#[wasm_bindgen(js_name = Network)]
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Debug)]
pub struct WasmNetwork(IotaNetwork);

#[wasm_bindgen(js_class = Network)]
impl WasmNetwork {
#[wasm_bindgen]
pub fn from_name(string: &str) -> Result<WasmNetwork, JsValue> {
let network = IotaNetwork::from_name(string).map_err(wasm_error)?;
Ok(Self(network))
}

#[wasm_bindgen]
pub fn mainnet() -> WasmNetwork {
Self(IotaNetwork::Mainnet)
Expand All @@ -23,26 +31,30 @@ impl WasmNetwork {

/// Returns the node URL of the Tangle network.
#[wasm_bindgen(getter = defaultNodeURL)]
pub fn default_node_url(&self) -> String {
self.0.default_node_url().to_string()
pub fn default_node_url(&self) -> Option<String> {
self.0.default_node_url().map(ToString::to_string)
}

/// Returns the web explorer URL of the Tangle network.
#[wasm_bindgen(getter = explorerURL)]
pub fn explorer_url(&self) -> String {
self.0.explorer_url().to_string()
pub fn explorer_url(&self) -> Result<String, JsValue> {
self.0.explorer_url().map(|url| url.to_string()).map_err(wasm_error)
}

/// Returns the web explorer URL of the given `message`.
#[wasm_bindgen(js_name = messageURL)]
pub fn message_url(&self, message_id: &str) -> String {
self.0.message_url(message_id).to_string()
pub fn message_url(&self, message_id: &str) -> Result<String, JsValue> {
self
.0
.message_url(message_id)
.map(|url| url.to_string())
.map_err(wasm_error)
}

#[allow(clippy::inherent_to_string, clippy::wrong_self_convention)]
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.0.as_str().into()
self.0.name().into()
}
}

Expand Down
8 changes: 8 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ path = "account/signing.rs"
name = "account_stronghold"
path = "account/stronghold.rs"

[[example]]
name = "account_private_tangle"
path = "account/private_tangle.rs"

[[example]]
name = "create_did"
path = "low-level-api/create_did.rs"
Expand Down Expand Up @@ -70,6 +74,10 @@ path = "low-level-api/merkle_key.rs"
name = "resolution"
path = "low-level-api/resolution.rs"

[[example]]
name = "private_tangle"
path = "low-level-api/private_tangle.rs"

[[example]]
name = "revoke_vc"
path = "low-level-api/revoke_vc.rs"
Loading

0 comments on commit e8f9e77

Please sign in to comment.