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

Add devnet package information #1471

Merged
merged 6 commits into from
Dec 4, 2024
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 examples/utils/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ where
})
.and_then(|pkg_str| pkg_str.parse().context("invalid package id"))?;

let read_only_client = IdentityClientReadOnly::new(iota_client, package_id).await?;
let read_only_client = IdentityClientReadOnly::new_with_pkg_id(iota_client, package_id).await?;

let signer = StorageSigner::new(storage, generate.key_id, public_key_jwk);

Expand Down
1 change: 1 addition & 0 deletions identity_iota_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ secret-storage = { git = "https://github.com/iotaledger/secret-storage.git", tag
serde-aux = { version = "4.5.0", optional = true }
shared-crypto = { git = "https://github.com/iotaledger/iota.git", package = "shared-crypto", tag = "v0.7.3-rc", optional = true }
tokio = { version = "1.29.0", default-features = false, optional = true, features = ["macros", "sync", "rt", "process"] }
phf = { version = "0.11.2", features = ["macros"] }

[dev-dependencies]
iota-crypto = { version = "0.23", default-features = false, features = ["bip39", "bip39-en"] }
Expand Down
57 changes: 23 additions & 34 deletions identity_iota_core/src/network/network_name.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::borrow::Cow;

use core::convert::TryFrom;
use core::fmt::Display;
use core::fmt::Formatter;
use core::ops::Deref;
use std::fmt::Debug;
use std::str::FromStr;

use serde::Deserialize;
use serde::Serialize;
Expand All @@ -18,21 +17,11 @@ use crate::error::Result;
/// Network name compliant with the [`crate::IotaDID`] method specification.
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
#[repr(transparent)]
pub struct NetworkName(Cow<'static, str>);
pub struct NetworkName(String);

impl NetworkName {
/// The maximum length of a network name.
pub const MAX_LENGTH: usize = 6;

/// Creates a new [`NetworkName`] if the name passes validation.
pub fn try_from<T>(name: T) -> Result<Self>
where
T: Into<Cow<'static, str>>,
{
let name_cow: Cow<'static, str> = name.into();
Self::validate_network_name(&name_cow)?;
Ok(Self(name_cow))
}
pub const MAX_LENGTH: usize = 8;

/// Validates whether a string is a spec-compliant IOTA DID [`NetworkName`].
pub fn validate_network_name(name: &str) -> Result<()> {
Expand All @@ -52,33 +41,34 @@ impl AsRef<str> for NetworkName {
}
}

impl From<NetworkName> for Cow<'static, str> {
fn from(network_name: NetworkName) -> Self {
network_name.0
}
}

impl Deref for NetworkName {
type Target = Cow<'static, str>;
type Target = str;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl TryFrom<&'static str> for NetworkName {
impl TryFrom<String> for NetworkName {
type Error = Error;

fn try_from(name: &'static str) -> Result<Self, Self::Error> {
Self::try_from(Cow::Borrowed(name))
fn try_from(value: String) -> Result<Self> {
Self::validate_network_name(&value)?;
Ok(Self(value))
}
}

impl TryFrom<String> for NetworkName {
impl<'a> TryFrom<&'a str> for NetworkName {
type Error = Error;
fn try_from(value: &'a str) -> Result<Self> {
value.to_string().try_into()
}
}

fn try_from(name: String) -> Result<Self, Self::Error> {
Self::try_from(Cow::Owned(name))
impl FromStr for NetworkName {
type Err = Error;
fn from_str(name: &str) -> Result<Self> {
Self::validate_network_name(name)?;
Ok(Self(name.to_string()))
}
}

Expand All @@ -98,15 +88,14 @@ impl Display for NetworkName {
mod tests {
use super::*;

// Rules are: at least one character, at most six characters and may only contain digits and/or lowercase ascii
// Rules are: at least one character, at most eight characters and may only contain digits and/or lowercase ascii
// characters.
const VALID_NETWORK_NAMES: [&str; 12] = [
"main", "dev", "smr", "rms", "test", "foo", "foobar", "123456", "0", "foo42", "bar123", "42foo",
const VALID_NETWORK_NAMES: &[&str] = &[
"main", "dev", "smr", "rms", "test", "foo", "foobar", "123456", "0", "foo42", "bar123", "42foo", "1234567",
"foobar0",
];

const INVALID_NETWORK_NAMES: [&str; 10] = [
"Main", "fOo", "deV", "féta", "", " ", "foo ", " foo", "1234567", "foobar0",
];
const INVALID_NETWORK_NAMES: &[&str] = &["Main", "fOo", "deV", "féta", "", " ", "foo ", " foo"];

#[test]
fn valid_validate_network_name() {
Expand Down
83 changes: 41 additions & 42 deletions identity_iota_core/src/rebased/client/read_only.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ use std::ops::Deref;
use std::pin::Pin;
use std::str::FromStr;

use crate::rebased::iota;
use crate::IotaDID;
use crate::IotaDocument;
use crate::NetworkName;
use anyhow::anyhow;
use anyhow::Context as _;
use futures::stream::FuturesUnordered;

use futures::StreamExt as _;
use identity_core::common::Url;
use identity_did::DID;
use futures::StreamExt as _;
use iota_sdk::rpc_types::EventFilter;
use iota_sdk::rpc_types::IotaData as _;
use iota_sdk::rpc_types::IotaObjectData;
Expand All @@ -37,8 +38,6 @@ use crate::rebased::migration::lookup;
use crate::rebased::migration::Identity;
use crate::rebased::Error;

const UNKNOWN_NETWORK_HRP: &str = "unknwn";

/// An [`IotaClient`] enriched with identity-related
/// functionalities.
#[derive(Clone)]
Expand Down Expand Up @@ -75,13 +74,40 @@ impl IdentityClientReadOnly {
self.migration_registry_id
}

/// Attempts to create a new [`IdentityClientReadOnly`] from a given [`IotaClient`].

/// # Failures
/// This function fails if the provided `iota_client` is connected to an unrecognized
/// network.
///
/// # Notes
/// When trying to connect to a local or unofficial network prefer using
/// [`IdentityClientReadOnly::new_with_pkg_id`].
pub async fn new(iota_client: IotaClient) -> Result<Self, Error> {
let network = network_id(&iota_client).await?;
let metadata = iota::well_known_networks::network_metadata(&network).ok_or_else(|| {
Error::InvalidConfig(format!(
"unrecognized network \"{network}\". Use `new_with_pkg_id` instead."
))
})?;

let pkg_id = metadata.latest_pkg_id();

Ok(IdentityClientReadOnly {
iota_client,
iota_identity_pkg_id: pkg_id,
migration_registry_id: metadata.migration_registry(),
network,
})
}

/// Attempts to create a new [`IdentityClientReadOnly`] from
/// the given [`IotaClient`].
pub async fn new(iota_client: IotaClient, iota_identity_pkg_id: ObjectID) -> Result<Self, Error> {
pub async fn new_with_pkg_id(iota_client: IotaClient, iota_identity_pkg_id: ObjectID) -> Result<Self, Error> {
let IdentityPkgMetadata {
migration_registry_id, ..
} = identity_pkg_metadata(&iota_client, iota_identity_pkg_id).await?;
let network = get_client_network(&iota_client).await?;
let network = network_id(&iota_client).await?;
Ok(Self {
iota_client,
iota_identity_pkg_id,
Expand All @@ -90,21 +116,6 @@ impl IdentityClientReadOnly {
})
}

/// Same as [`Self::new`], but if the network isn't recognized among IOTA's official networks,
/// the provided `network_name` will be used.
pub async fn new_with_network_name(
iota_client: IotaClient,
iota_identity_pkg_id: ObjectID,
network_name: NetworkName,
) -> Result<Self, Error> {
let mut identity_client = Self::new(iota_client, iota_identity_pkg_id).await?;
if identity_client.network.as_ref() == UNKNOWN_NETWORK_HRP {
identity_client.network = network_name;
}

Ok(identity_client)
}

/// Resolves a _Move_ Object of ID `id` and parses it to a value of type `T`.
pub async fn get_object_by_id<T>(&self, id: ObjectID) -> Result<T, Error>
where
Expand Down Expand Up @@ -188,8 +199,7 @@ impl IdentityClientReadOnly {
/// Resolves an [`Identity`] from its ID `object_id`.
pub async fn get_identity(&self, object_id: ObjectID) -> Result<Identity, Error> {
// spawn all checks
let all_futures =
FuturesUnordered::<Pin<Box<dyn Future<Output = Result<Option<Identity>, Error>> + Send>>>::new();
let all_futures = FuturesUnordered::<Pin<Box<dyn Future<Output = Result<Option<Identity>, Error>> + Send>>>::new();
all_futures.push(Box::pin(resolve_new(self, object_id)));
all_futures.push(Box::pin(resolve_migrated(self, object_id)));
all_futures.push(Box::pin(resolve_unmigrated(self, object_id)));
Expand All @@ -202,9 +212,17 @@ impl IdentityClientReadOnly {
}
}

async fn network_id(iota_client: &IotaClient) -> Result<NetworkName, Error> {
let network_id = iota_client
.read_api()
.get_chain_identifier()
.await
.map_err(|e| Error::RpcError(e.to_string()))?;
Ok(network_id.try_into().expect("chain ID is a valid network name"))
}

#[derive(Debug)]
struct IdentityPkgMetadata {
_package_id: ObjectID,
migration_registry_id: ObjectID,
}

Expand All @@ -214,24 +232,6 @@ struct MigrationRegistryCreatedEvent {
id: ObjectID,
}

async fn get_client_network(client: &IotaClient) -> Result<NetworkName, Error> {
let network_id = client
.read_api()
.get_chain_identifier()
.await
.map_err(|e| Error::RpcError(e.to_string()))?;

// TODO: add entries when iota_identity package is published to well-known networks.
#[allow(clippy::match_single_binding)]
let network_hrp = match &network_id {
// "89c3eeec" => NetworkName::try_from("iota").unwrap(),
// "fe12a865" => NetworkName::try_from("atoi").unwrap(),
_ => NetworkName::try_from(UNKNOWN_NETWORK_HRP).unwrap(), // Unrecognized network
};

Ok(network_hrp)
}

// TODO: remove argument `package_id` and use `EventFilter::MoveEventField` to find the beacon event and thus the
// package id.
// TODO: authenticate the beacon event with though sender's ID.
Expand Down Expand Up @@ -271,7 +271,6 @@ async fn identity_pkg_metadata(iota_client: &IotaClient, package_id: ObjectID) -

Ok(IdentityPkgMetadata {
migration_registry_id: registry_id,
_package_id: package_id,
})
}

Expand Down
1 change: 1 addition & 0 deletions identity_iota_core/src/rebased/iota/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

pub(crate) mod move_calls;
pub(crate) mod types;
pub(crate) mod well_known_networks;
63 changes: 63 additions & 0 deletions identity_iota_core/src/rebased/iota/well_known_networks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use iota_sdk::types::base_types::ObjectID;
use phf::{phf_map, Map};

/// A Mapping `network_id` -> metadata needed by the library.
pub(crate) static IOTA_NETWORKS: Map<&str, IdentityNetworkMetadata> = phf_map! {
// devnet
"e678123a" => IdentityNetworkMetadata::new(
&["0x156dfa0c4d4e576f5675de7d4bbe161c767947ffceefd7498cb39c406bc1cb67"],
"0x0247da7f3b8708fc1d326f70153c01b7caf52a19a6f42dd3b868ac8777486b11",
),
};

/// `iota_identity` package information for a given network.
#[derive(Debug)]
pub(crate) struct IdentityNetworkMetadata {
/// `package[0]` is the current version, `package[1]`
/// is the version before, and so forth.
pub package: &'static [&'static str],
pub migration_registry: &'static str,
}

/// Returns the [`IdentityNetworkMetadata`] for a given network, if any.
pub(crate) fn network_metadata(network_id: &str) -> Option<&'static IdentityNetworkMetadata> {
IOTA_NETWORKS.get(network_id)
}

impl IdentityNetworkMetadata {
const fn new(pkgs: &'static [&'static str], migration_registry: &'static str) -> Self {
assert!(!pkgs.is_empty());
Self {
package: pkgs,
migration_registry,
}
}

/// Returns the latest `IotaIdentity` package ID on this network.
pub(crate) fn latest_pkg_id(&self) -> ObjectID {
self
.package
.first()
.expect("a package was published")
.parse()
.expect("valid package ID")
}

/// Returns the ID for the `MigrationRegistry` on this network.
pub(crate) fn migration_registry(&self) -> ObjectID {
self.migration_registry.parse().expect("valid ObjectID")
}
}

#[cfg(test)]
mod test {
use iota_sdk::IotaClientBuilder;

use crate::rebased::client::IdentityClientReadOnly;

#[tokio::test]
async fn identity_client_connection_to_devnet_works() -> anyhow::Result<()> {
IdentityClientReadOnly::new(IotaClientBuilder::default().build_devnet().await?).await?;
Ok(())
}
}
4 changes: 3 additions & 1 deletion identity_iota_core/src/rebased/migration/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,9 @@ impl Transaction for CreateIdentityTx {
threshold,
controllers,
} = self.0;
let did_doc = StateMetadataDocument::from(did_doc).pack(StateMetadataEncoding::default()).map_err(|e| Error::DidDocSerialization(e.to_string()))?;
let did_doc = StateMetadataDocument::from(did_doc)
.pack(StateMetadataEncoding::default())
.map_err(|e| Error::DidDocSerialization(e.to_string()))?;
let programmable_transaction = if controllers.is_empty() {
move_calls::identity::new(&did_doc, client.package_id())?
} else {
Expand Down
10 changes: 8 additions & 2 deletions identity_iota_core/tests/e2e/client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::ops::Deref;

use crate::common::get_client as get_test_client;
use identity_iota_core::rebased::migration;
use identity_iota_core::rebased::transaction::Transaction;
Expand All @@ -11,11 +13,15 @@ async fn can_create_an_identity() -> anyhow::Result<()> {
let test_client = get_test_client().await?;
let identity_client = test_client.new_user_client().await?;

let _identity = identity_client
let identity = identity_client
.create_identity(IotaDocument::new(identity_client.network()))
.finish()
.execute(&identity_client)
.await?;
.await?
.output;

let did = identity.deref().id();
assert_eq!(did.network_str(), identity_client.network().as_ref());

Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion identity_iota_core/tests/e2e/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub async fn get_client() -> anyhow::Result<TestClient> {
request_funds(&address).await?;

let storage = Arc::new(Storage::new(JwkMemStore::new(), KeyIdMemstore::new()));
let identity_client = IdentityClientReadOnly::new(client, package_id).await?;
let identity_client = IdentityClientReadOnly::new_with_pkg_id(client, package_id).await?;

Ok(TestClient {
client: identity_client,
Expand Down
Loading