From 5a7aaa78f16e22fdd86345753f67ee4049fdea13 Mon Sep 17 00:00:00 2001 From: PhilippGackstatter Date: Wed, 18 Aug 2021 09:10:39 +0200 Subject: [PATCH 01/31] Make `Network` not `Copy` This is in preparation for adding another non-`Copy` variant later. --- identity-iota/src/tangle/client.rs | 6 +++--- identity-iota/src/tangle/client_builder.rs | 2 +- identity-iota/src/tangle/client_map.rs | 6 +++--- identity-iota/src/tangle/network.rs | 14 ++++++++------ identity-iota/src/tangle/receipt.rs | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/identity-iota/src/tangle/client.rs b/identity-iota/src/tangle/client.rs index d891a4afb1..e2a5632f15 100644 --- a/identity-iota/src/tangle/client.rs +++ b/identity-iota/src/tangle/client.rs @@ -60,7 +60,7 @@ impl Client { /// Returns the `Client` Tangle network. pub fn network(&self) -> Network { - self.network + self.network.clone() } /// Publishes an `IotaDocument` to the Tangle. @@ -73,7 +73,7 @@ impl Client { .finish() .await .map_err(Into::into) - .map(|message| Receipt::new(self.network, message)) + .map(|message| Receipt::new(self.network.clone(), message)) } /// Publishes a `DocumentDiff` to the Tangle. @@ -86,7 +86,7 @@ impl Client { .finish() .await .map_err(Into::into) - .map(|message| Receipt::new(self.network, message)) + .map(|message| Receipt::new(self.network.clone(), message)) } pub async fn read_document(&self, did: &IotaDID) -> Result { diff --git a/identity-iota/src/tangle/client_builder.rs b/identity-iota/src/tangle/client_builder.rs index 3731addd2b..bd1a49d8aa 100644 --- a/identity-iota/src/tangle/client_builder.rs +++ b/identity-iota/src/tangle/client_builder.rs @@ -31,7 +31,7 @@ impl ClientBuilder { /// Sets the IOTA Tangle network. pub fn network(mut self, network: Network) -> Self { - self.builder = self.builder.with_network(network.as_str()); + self.builder = self.builder.with_network(&network.as_str()); self.network = network; self } diff --git a/identity-iota/src/tangle/client_map.rs b/identity-iota/src/tangle/client_map.rs index 80bd6f93e8..444d7cfec7 100644 --- a/identity-iota/src/tangle/client_map.rs +++ b/identity-iota/src/tangle/client_map.rs @@ -31,7 +31,7 @@ impl ClientMap { pub fn from_client(client: Client) -> Self { let data: State = State::new(); - data.insert(client.network, Arc::new(client)); + data.insert(client.network.clone(), Arc::new(client)); Self { data } } @@ -49,7 +49,7 @@ impl ClientMap { } pub fn insert(&self, client: Client) { - self.data.insert(client.network, Arc::new(client)); + self.data.insert(client.network.clone(), Arc::new(client)); } pub async fn publish_document(&self, document: &IotaDocument) -> Result { @@ -85,7 +85,7 @@ impl ClientMap { return Ok(Arc::clone(&client)); } - let client: Arc = Client::from_network(network).await.map(Arc::new)?; + let client: Arc = Client::from_network(network.clone()).await.map(Arc::new)?; self.data.insert(network, Arc::clone(&client)); diff --git a/identity-iota/src/tangle/network.rs b/identity-iota/src/tangle/network.rs index 389f79a0a9..06d5b9c2ff 100644 --- a/identity-iota/src/tangle/network.rs +++ b/identity-iota/src/tangle/network.rs @@ -1,6 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::borrow::Cow; + use identity_core::common::Url; use crate::did::IotaDID; @@ -16,7 +18,7 @@ lazy_static! { } /// The Tangle network to use (`Mainnet` or `Testnet`). -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] pub enum Network { #[serde(rename = "main")] Mainnet, @@ -46,7 +48,7 @@ impl Network { } /// Returns the default node URL of the Tangle network. - pub fn default_node_url(self) -> &'static Url { + pub fn default_node_url(&self) -> &'static Url { match self { Self::Mainnet => &*NODE_MAIN, Self::Testnet => &*NODE_TEST, @@ -54,7 +56,7 @@ impl Network { } /// Returns the web explorer URL of the Tangle network. - pub fn explorer_url(self) -> &'static Url { + pub fn explorer_url(&self) -> &'static Url { match self { Self::Mainnet => &*EXPLORER_MAIN, Self::Testnet => &*EXPLORER_TEST, @@ -71,10 +73,10 @@ impl Network { } /// Returns the name of the network as a static `str`. - pub const fn as_str(self) -> &'static str { + pub const fn as_str(&self) -> Cow<'static, str> { match self { - Self::Mainnet => MAIN_NETWORK_NAME, - Self::Testnet => TEST_NETWORK_NAME, + Self::Mainnet => Cow::Borrowed(MAIN_NETWORK_NAME), + Self::Testnet => Cow::Borrowed(TEST_NETWORK_NAME), } } } diff --git a/identity-iota/src/tangle/receipt.rs b/identity-iota/src/tangle/receipt.rs index 6b05fd835e..4ab7ee118e 100644 --- a/identity-iota/src/tangle/receipt.rs +++ b/identity-iota/src/tangle/receipt.rs @@ -29,7 +29,7 @@ impl Receipt { /// Returns the associated IOTA Tangle `Network`. pub fn network(&self) -> Network { - self.network + self.network.clone() } /// Returns the message `id`. From be2c01ef88e8ad58815b9f35a868ea6c2e554409 Mon Sep 17 00:00:00 2001 From: PhilippGackstatter Date: Wed, 18 Aug 2021 11:30:12 +0200 Subject: [PATCH 02/31] Add `Other` variant in `Network` --- examples/low-level-api/create_did.rs | 2 +- examples/low-level-api/manipulate_did.rs | 2 +- identity-account/src/account/builder.rs | 2 +- identity-iota/src/did/url/iota_did.rs | 3 +- identity-iota/src/error.rs | 4 ++ identity-iota/src/tangle/client.rs | 7 +++- identity-iota/src/tangle/network.rs | 52 +++++++++++++++--------- identity-iota/src/tangle/receipt.rs | 3 +- 8 files changed, 50 insertions(+), 25 deletions(-) diff --git a/examples/low-level-api/create_did.rs b/examples/low-level-api/create_did.rs index a350beb7aa..7c2bc26024 100644 --- a/examples/low-level-api/create_did.rs +++ b/examples/low-level-api/create_did.rs @@ -31,7 +31,7 @@ pub async fn run() -> Result<(IotaDocument, KeyPair, Receipt)> { println!("Publish Receipt > {:#?}", receipt); // Display the web explorer url that shows the published message. - println!("DID Document Transaction > {}", receipt.message_url()); + println!("DID Document Transaction > {}", receipt.message_url()?); Ok((document, keypair, receipt)) } diff --git a/examples/low-level-api/manipulate_did.rs b/examples/low-level-api/manipulate_did.rs index 8d23ca30c5..e77f1a2544 100644 --- a/examples/low-level-api/manipulate_did.rs +++ b/examples/low-level-api/manipulate_did.rs @@ -54,7 +54,7 @@ async fn main() -> Result<()> { println!("Publish Receipt > {:#?}", receipt); // Display the web explorer url that shows the published message. - println!("DID Document Transaction > {}", receipt.message_url()); + println!("DID Document Transaction > {}", receipt.message_url()?); Ok(()) } diff --git a/identity-account/src/account/builder.rs b/identity-account/src/account/builder.rs index 70e3d99adc..b9a384e503 100644 --- a/identity-account/src/account/builder.rs +++ b/identity-account/src/account/builder.rs @@ -80,7 +80,7 @@ impl AccountBuilder { self .clients .get_or_insert_with(HashMap::new) - .insert(network, f(ClientBuilder::new().network(network))); + .insert(network.clone(), f(ClientBuilder::new().network(network))); self } diff --git a/identity-iota/src/did/url/iota_did.rs b/identity-iota/src/did/url/iota_did.rs index 109259dc65..816e629eff 100644 --- a/identity-iota/src/did/url/iota_did.rs +++ b/identity-iota/src/did/url/iota_did.rs @@ -248,7 +248,8 @@ impl IotaDID { /// Returns the Tangle `network` of the `DID`. pub fn network(&self) -> Network { - Network::from_name(self.network_str()) + // SAFETY: `network_str` never returns an empty string. + Network::from_name(self.network_str()).unwrap() } /// Returns the Tangle `network` name of the `DID`. diff --git a/identity-iota/src/error.rs b/identity-iota/src/error.rs index df787c29ff..db210a3a35 100644 --- a/identity-iota/src/error.rs +++ b/identity-iota/src/error.rs @@ -51,4 +51,8 @@ pub enum Error { CannotRemoveAuthMethod, #[error("Cannot Revoke Verification Method")] CannotRevokeMethod, + #[error("No Client Nodes Provided")] + NoClientNodesProvided, + #[error("No Explorer for Private Tangles")] + NoExplorerForPrivateTangles, } diff --git a/identity-iota/src/tangle/client.rs b/identity-iota/src/tangle/client.rs index e2a5632f15..7dd4bde93e 100644 --- a/identity-iota/src/tangle/client.rs +++ b/identity-iota/src/tangle/client.rs @@ -12,6 +12,7 @@ use crate::chain::IntegrationChain; use crate::did::DocumentDiff; use crate::did::IotaDID; use crate::did::IotaDocument; +use crate::error::Error; use crate::error::Result; use crate::tangle::ClientBuilder; use crate::tangle::Message; @@ -49,7 +50,11 @@ impl Client { let mut client: iota_client::ClientBuilder = builder.builder; if !builder.nodeset { - client = client.with_node(builder.network.default_node_url().as_str())?; + if let Some(network_url) = builder.network.default_node_url() { + client = client.with_node(network_url.as_str())?; + } else { + return Err(Error::NoClientNodesProvided); + } } Ok(Self { diff --git a/identity-iota/src/tangle/network.rs b/identity-iota/src/tangle/network.rs index 06d5b9c2ff..7be061d6f1 100644 --- a/identity-iota/src/tangle/network.rs +++ b/identity-iota/src/tangle/network.rs @@ -6,6 +6,7 @@ use std::borrow::Cow; use identity_core::common::Url; use crate::did::IotaDID; +use crate::error::{Error, Result}; const MAIN_NETWORK_NAME: &str = "main"; const TEST_NETWORK_NAME: &str = "test"; @@ -24,16 +25,23 @@ pub enum Network { Mainnet, #[serde(rename = "test")] Testnet, + Other(String), } impl Network { - /// Parses the provided string to a `Network`. + /// Parses the provided string to a [`Network`]. /// - /// If the input is `"test"` then `Testnet` is returned, otherwise `Mainnet` is returned. - pub fn from_name(string: &str) -> Self { + /// The inputs `"test"` and `"main"` will be mapped to the well-known [`Testnet`][Network::Testnet] + /// and [`Mainnet`][Network::Mainnet] variants respectively. + /// Other inputs will return an instance of [`Other`][Network::Other]. + /// + /// Note that empty strings are not valid network names. + pub fn from_name(string: &str) -> Result { match string { - TEST_NETWORK_NAME => Self::Testnet, - _ => Self::Mainnet, + "" => Err(Error::InvalidDIDNetwork), + TEST_NETWORK_NAME => Ok(Self::Testnet), + MAIN_NETWORK_NAME => Ok(Self::Mainnet), + other => Ok(Self::Other(other.to_owned())), } } @@ -48,35 +56,37 @@ impl Network { } /// Returns the default node URL of the Tangle network. - pub fn default_node_url(&self) -> &'static Url { + pub fn default_node_url(&self) -> Option<&'static Url> { match self { - Self::Mainnet => &*NODE_MAIN, - Self::Testnet => &*NODE_TEST, + Self::Mainnet => Some(&*NODE_MAIN), + Self::Testnet => Some(&*NODE_TEST), + _ => None, } } /// Returns the web explorer URL of the Tangle network. - pub fn explorer_url(&self) -> &'static Url { + pub fn explorer_url(&self) -> Result<&'static Url> { match self { - Self::Mainnet => &*EXPLORER_MAIN, - Self::Testnet => &*EXPLORER_TEST, + Self::Mainnet => Ok(&*EXPLORER_MAIN), + Self::Testnet => Ok(&*EXPLORER_TEST), + _ => Err(Error::NoExplorerForPrivateTangles), } } /// Returns the web explorer URL of the given `message`. - pub fn message_url(&self, message_id: &str) -> Url { - let mut url: Url = self.explorer_url().clone(); - + pub fn message_url(&self, message_id: &str) -> Result { + let mut url = self.explorer_url()?.clone(); // unwrap is safe, the explorer URL is always a valid base URL url.path_segments_mut().unwrap().push("message").push(message_id); - url + Ok(url) } /// Returns the name of the network as a static `str`. - pub const fn as_str(&self) -> Cow<'static, str> { + pub fn as_str(&self) -> Cow<'static, str> { match self { Self::Mainnet => Cow::Borrowed(MAIN_NETWORK_NAME), Self::Testnet => Cow::Borrowed(TEST_NETWORK_NAME), + Self::Other(network) => Cow::Owned(network.clone()), } } } @@ -94,9 +104,13 @@ mod tests { #[test] fn test_from_name() { - assert_eq!(Network::from_name("test"), Network::Testnet); - assert_eq!(Network::from_name("main"), Network::Mainnet); - assert_eq!(Network::from_name("anything"), Network::Mainnet); + assert_eq!(Network::from_name("test").unwrap(), Network::Testnet); + assert_eq!(Network::from_name("main").unwrap(), Network::Mainnet); + assert_eq!( + Network::from_name("anything").unwrap(), + Network::Other("anything".to_owned()) + ); + assert!(Network::from_name("").is_err()); } #[test] diff --git a/identity-iota/src/tangle/receipt.rs b/identity-iota/src/tangle/receipt.rs index 4ab7ee118e..6b515ee9df 100644 --- a/identity-iota/src/tangle/receipt.rs +++ b/identity-iota/src/tangle/receipt.rs @@ -3,6 +3,7 @@ use identity_core::common::Url; +use crate::error::Result; use crate::tangle::Message; use crate::tangle::MessageId; use crate::tangle::Network; @@ -48,7 +49,7 @@ impl Receipt { } /// Returns the web explorer URL of the associated `message`. - pub fn message_url(&self) -> Url { + pub fn message_url(&self) -> Result { self.network.message_url(&self.message_id.to_string()) } } From 10b48a4a9ff8bd090e794e7be95b87a19b444915 Mon Sep 17 00:00:00 2001 From: PhilippGackstatter Date: Wed, 18 Aug 2021 16:42:51 +0200 Subject: [PATCH 03/31] Add node.js DID creation on private tangle example --- bindings/wasm/examples/README.md | 1 + bindings/wasm/examples/node/node.js | 3 ++ bindings/wasm/examples/node/private_tangle.js | 41 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 bindings/wasm/examples/node/private_tangle.js diff --git a/bindings/wasm/examples/README.md b/bindings/wasm/examples/README.md index 0753da2a99..2010de0c24 100644 --- a/bindings/wasm/examples/README.md +++ b/bindings/wasm/examples/README.md @@ -41,6 +41,7 @@ The following examples are currently available: | 5 | [revocation](node/revocation.js) | Remove a verification method from the Issuers DID Document, making the Verifiable Credential it signed unable to verify, effectively revoking the VC. | | 6 | [create_vp](node/create_vp.js) | Create a Verifiable Presentation, the data model for sharing VCs, out of a Verifiable Credential and verifies it. | | 7 | [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. | +| 8 | [private_tangle](node/private_tangle.js) | Showcases the same procedure as `create_did`, but on a private tangle - a locally running hornet node. | diff --git a/bindings/wasm/examples/node/node.js b/bindings/wasm/examples/node/node.js index 22677851fc..1ac3d8ac30 100644 --- a/bindings/wasm/examples/node/node.js +++ b/bindings/wasm/examples/node/node.js @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 const { createIdentity } = require('./create_did'); +const { createIdentityPrivateTangle } = require('./private_tangle'); const { manipulateIdentity } = require('./manipulate_did'); const { resolution } = require('./resolution'); const { createVC } = require('./create_vc'); @@ -33,6 +34,8 @@ async function main() { return await createVP(CLIENT_CONFIG); case 'merkle_key': return await merkleKey(CLIENT_CONFIG); + case 'private_tangle': + return await createIdentityPrivateTangle(); case 'all': console.log(">>> Run All Examples"); diff --git a/bindings/wasm/examples/node/private_tangle.js b/bindings/wasm/examples/node/private_tangle.js new file mode 100644 index 0000000000..4eacd7b7e3 --- /dev/null +++ b/bindings/wasm/examples/node/private_tangle.js @@ -0,0 +1,41 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +const { createIdentity } = require('./create_did'); +const { Client, Config, Document, KeyType, Network } = require('../../node/identity_wasm') +const { logExplorerUrl } = require('./explorer_util') + +/** + 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 the `networkID` defined in hornet's node configuration. + const network = Network.from_name("private-tangle"); + + // Create a DID Document (an identity). + const { doc, key } = new Document(KeyType.Ed25519, clientConfig.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()); + + // Return the results. + return { key, doc, receipt }; +} + +exports.createIdentityPrivateTangle = createIdentityPrivateTangle; From 881a92e9cbe7496e2659e680db189637752c70c3 Mon Sep 17 00:00:00 2001 From: PhilippGackstatter Date: Wed, 18 Aug 2021 16:47:42 +0200 Subject: [PATCH 04/31] Add `from_name` in Wasm and fix absence of `Copy` --- bindings/wasm/src/tangle/config.rs | 2 +- bindings/wasm/src/tangle/network.rs | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/bindings/wasm/src/tangle/config.rs b/bindings/wasm/src/tangle/config.rs index b6a5561bfd..2e719c21e2 100644 --- a/bindings/wasm/src/tangle/config.rs +++ b/bindings/wasm/src/tangle/config.rs @@ -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)] diff --git a/bindings/wasm/src/tangle/network.rs b/bindings/wasm/src/tangle/network.rs index 417e233a83..7d6e3cc882 100644 --- a/bindings/wasm/src/tangle/network.rs +++ b/bindings/wasm/src/tangle/network.rs @@ -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 { + let network = IotaNetwork::from_name(string).map_err(wasm_error)?; + Ok(Self(network)) + } + #[wasm_bindgen] pub fn mainnet() -> WasmNetwork { Self(IotaNetwork::Mainnet) @@ -23,20 +31,20 @@ 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 { + 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 { + self.0.explorer_url().map(ToString::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 { + self.0.message_url(message_id).map(|url| url.to_string()).map_err(wasm_error) } #[allow(clippy::inherent_to_string, clippy::wrong_self_convention)] From 5ae4edc7b1b09e39cb3d6f8572bfd9f291a74818 Mon Sep 17 00:00:00 2001 From: PhilippGackstatter Date: Wed, 18 Aug 2021 16:51:01 +0200 Subject: [PATCH 05/31] Remove superfluous imports --- bindings/wasm/examples/node/private_tangle.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/bindings/wasm/examples/node/private_tangle.js b/bindings/wasm/examples/node/private_tangle.js index 4eacd7b7e3..9e55360a6f 100644 --- a/bindings/wasm/examples/node/private_tangle.js +++ b/bindings/wasm/examples/node/private_tangle.js @@ -1,9 +1,7 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -const { createIdentity } = require('./create_did'); const { Client, Config, Document, KeyType, Network } = require('../../node/identity_wasm') -const { logExplorerUrl } = require('./explorer_util') /** This example shows how a DID document can be created on a private tangle. From e7f181e297c92f682f6ba093ea3fc26deb2876a8 Mon Sep 17 00:00:00 2001 From: PhilippGackstatter Date: Wed, 18 Aug 2021 17:00:00 +0200 Subject: [PATCH 06/31] Add browser Wasm example --- bindings/wasm/examples/browser/index.html | 2 + bindings/wasm/examples/browser/main.js | 6 +++ .../wasm/examples/browser/private_tangle.js | 48 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 bindings/wasm/examples/browser/private_tangle.js diff --git a/bindings/wasm/examples/browser/index.html b/bindings/wasm/examples/browser/index.html index 41fb7ee1f7..a9fc80a647 100644 --- a/bindings/wasm/examples/browser/index.html +++ b/bindings/wasm/examples/browser/index.html @@ -46,6 +46,8 @@ + + - +
+ + + +
+ @@ -52,9 +53,17 @@ + + + +