From 4ce6295a96b79747f9e7cd0676319da1b8ab0c37 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 25 Oct 2021 09:56:00 +0200 Subject: [PATCH] Refactor `Account` to handle just one identity (#436) * Partial impl to use did as storage identifier * Move `CreateIdentity` into a separate type Update Account API to return IdentityState from find_identity and create_identity (#414) * Update Account::create_identity() to return IdentityState. * Update Account::find_identity() to return IdentityState. Update `IdentityUpdater` to only use account ref Remove resolve, find and list Replace `IdentityId` with `IotaDID` in account Use `IotaDID` over `IdentityId` in stronghold More replacement of id with did Remove index from account Fix stream impl Fix stronghold impl, migrate `MemStore` Re-add `resolve_identity` so tests can run Fix Wasm network calls, pin reqwest to 0.11.4 (#439) Rename `Command` -> `Update` Rearrange `process_update` * Fix clippy lints in memstore * Let `RemoteKey` take a `dyn Storage` * Implement `AccountConfig` * Impl create_identity and load_identity in builder * Rename JSON serialization field name to match spec. (#412) * Rename JSON serialization field name to match spec. * Rename notSupported -> representationNotSupported * Reduce WASM build size (#427) * reduce wasm build size * Enable lto for Wasm release Add `build-dev` task for Wasm for debugging. Co-authored-by: Craig Bester * Chore/combine examples (#420) * Merged Stronghold with basic examples in account * Updated explorer to identity resolver * Added explorer URL after basic example * Renamed examples * Completed manipulate did example * Fixed suggestions, removed replaced examples. * Improved example readme * Consistently use Stronghold and Resolver URL * Fix examples * Merged config example with private tangle * low-level-api private-network example runs * cargo fmt * cargo fmt with nightly * Impl suggestions * Refactor config once more * Update examples to use single-id account * Don't pass did into `load_snapshot` * Fix clippy lints * Partially update `update` tests * Fix update tests * Ensure did exists in storage in `load` * Remote `State` in account * Rename `load` -> `load_identity` * Remove `Identity{Id,Index,Key,Name,Tag}` * Let `update_identity` take `&mut self` * Document new and existing types * Update top-level README example * Fix visibility on `CreateIdentity` * Add try_from implementation for ed25519 keypair * Combine account tests * Rename updates > commands for better reviewability * Also rename in mod.rs * Impl `AccountBuilder` without pub async fns * Update README example with latest builder change * Change visibility of setup, config, constructors * Revert to the old `create_did` example * Save to disk when creating identity, fix doc fmt Co-authored-by: Craig Bester * Use `authority` instead of `tag` in stronghold * Rename `Config` -> `AccountConfig` * Prevent two accounts from managing the same did * Impl `IdentityBuilder` * Only impl `IdentityCreate` under `#[cfg(test)]` * Use `IdentityBuilder` in examples * Remove unused `name` field * Run workflow for `epic/*` branches * Relase the lease in account tests * Also run format, clippy and coverage workflows * Revert "Use `IdentityBuilder` in examples" This reverts commit 3d1b716da3f6088069ec2692d187552a71037e44. * Revert "Only impl `IdentityCreate` under `#[cfg(test)]`" This reverts commit 08ada0979f337a7143abf75160fe9e0714066846. * Revert "Impl `IdentityBuilder`" This reverts commit 1a5d6452572f5ce89e3a757141a697c45de56952. * Rename `IdentityCreate` -> `IdentitySetup` * Add multi-identity example * Simplify README example * Rename `identity_setup` module * Implement the `IdentityLease` newtype * Rename `IdentityLease` -> `DIDLease` * Update `resolve_identity` docstring Co-authored-by: Matt Renaud Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Co-authored-by: Craig Bester Co-authored-by: Jelle Femmo Millenaar --- .github/workflows/build-and-test.yml | 1 + .github/workflows/clippy.yml | 1 + .github/workflows/coverage.yml | 1 + .github/workflows/format.yml | 1 + README.md | 27 +- examples/Cargo.toml | 4 + examples/README.md | 1 + examples/account/config.rs | 33 +- examples/account/create_did.rs | 23 +- examples/account/lazy.rs | 31 +- examples/account/manipulate_did.rs | 27 +- examples/account/multiple_identities.rs | 72 +++ examples/account/signing.rs | 29 +- identity-account/src/account/account.rs | 443 ++++++------------ identity-account/src/account/builder.rs | 139 ++++-- identity-account/src/account/config.rs | 152 ++++++ identity-account/src/account/mod.rs | 2 + identity-account/src/crypto/remote.rs | 27 +- identity-account/src/error.rs | 5 + identity-account/src/events/commit.rs | 11 +- identity-account/src/events/context.rs | 11 +- identity-account/src/events/macros.rs | 22 +- identity-account/src/events/mod.rs | 4 +- .../src/events/{command.rs => update.rs} | 178 +++---- identity-account/src/identity/did_lease.rs | 40 ++ identity-account/src/identity/identity_id.rs | 124 ----- .../src/identity/identity_index.rs | 167 ------- identity-account/src/identity/identity_key.rs | 54 --- .../src/identity/identity_name.rs | 36 -- .../{identity_create.rs => identity_setup.rs} | 18 +- .../src/identity/identity_snapshot.rs | 6 - .../src/identity/identity_state.rs | 29 +- identity-account/src/identity/identity_tag.rs | 182 ------- .../src/identity/identity_updater.rs | 15 +- identity-account/src/identity/mod.rs | 16 +- identity-account/src/storage/memstore.rs | 109 +++-- identity-account/src/storage/stronghold.rs | 150 +++--- identity-account/src/storage/traits.rs | 105 ++--- identity-account/src/tests/account.rs | 60 +++ identity-account/src/tests/commands.rs | 288 +++--------- identity-account/src/tests/lazy.rs | 52 +- identity-account/src/tests/mod.rs | 1 + identity-core/src/crypto/key/pair.rs | 21 + identity-core/src/utils/ed25519.rs | 10 + 44 files changed, 1148 insertions(+), 1580 deletions(-) create mode 100644 examples/account/multiple_identities.rs create mode 100644 identity-account/src/account/config.rs rename identity-account/src/events/{command.rs => update.rs} (74%) create mode 100644 identity-account/src/identity/did_lease.rs delete mode 100644 identity-account/src/identity/identity_id.rs delete mode 100644 identity-account/src/identity/identity_index.rs delete mode 100644 identity-account/src/identity/identity_key.rs delete mode 100644 identity-account/src/identity/identity_name.rs rename identity-account/src/identity/{identity_create.rs => identity_setup.rs} (81%) delete mode 100644 identity-account/src/identity/identity_tag.rs create mode 100644 identity-account/src/tests/account.rs diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 48fe814d74..60cd8969c5 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -9,6 +9,7 @@ on: branches: - main - dev + - epic/* paths: - '.github/workflows/build-and-test.yml' - '**.rs' diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 470f8f30ce..48076c6c20 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -9,6 +9,7 @@ on: branches: - main - dev + - epic/* paths: - '.github/workflows/clippy.yml' - '**.rs' diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b836bd0502..a97864a6b7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -9,6 +9,7 @@ on: branches: - main - dev + - epic/* paths: - '.github/workflows/coverage.yml' - '.github/workflows/scripts/coverage.sh' diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index e2c3581d99..9b238753b1 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -9,6 +9,7 @@ on: branches: - main - dev + - epic/* paths: - '.github/workflows/format.yml' - '**.rs' diff --git a/README.md b/README.md index ee763457c6..abde671296 100644 --- a/README.md +++ b/README.md @@ -91,10 +91,8 @@ use std::path::PathBuf; use identity::account::Account; use identity::account::AccountStorage; -use identity::account::IdentityCreate; -use identity::account::IdentityState; +use identity::account::IdentitySetup; use identity::account::Result; -use identity::iota::IotaDID; use identity::iota::IotaDocument; #[tokio::main] @@ -102,28 +100,25 @@ async fn main() -> Result<()> { pretty_env_logger::init(); // The Stronghold settings for the storage. - let snapshot: PathBuf = "./example-strong.hodl".into(); + let stronghold_path: PathBuf = "./example-strong.hodl".into(); let password: String = "my-password".into(); - // Create a new Account with Stronghold as the storage adapter. + // Create a new identity with default settings and + // Stronghold as the storage. let account: Account = Account::builder() - .storage(AccountStorage::Stronghold(snapshot, Some(password))) - .build() + .storage(AccountStorage::Stronghold(stronghold_path, Some(password))) + .create_identity(IdentitySetup::default()) .await?; - // Create a new Identity with default settings. - let identity: IdentityState = account.create_identity(IdentityCreate::default()).await?; - - // Retrieve the DID from the newly created Identity state. - let did: &IotaDID = identity.try_did()?; - - println!("[Example] Local Document = {:#?}", identity.to_document()?); - println!("[Example] Local Document List = {:#?}", account.list_identities().await); + println!( + "[Example] Local Document = {:#?}", + account.state().await?.to_document()? + ); // Fetch the DID Document from the Tangle // // This is an optional step to ensure DID Document consistency. - let resolved: IotaDocument = account.resolve_identity(did).await?; + let resolved: IotaDocument = account.resolve_identity().await?; println!("[Example] Tangle Document = {:#?}", resolved); diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 852dc8c36e..305fece4a8 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -34,6 +34,10 @@ path = "account/lazy.rs" name = "account_signing" path = "account/signing.rs" +[[example]] +name = "account_multiple" +path = "account/multiple_identities.rs" + [[example]] name = "create_did" path = "low-level-api/create_did.rs" diff --git a/examples/README.md b/examples/README.md index 2fca72270f..0d20fa94e4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -28,6 +28,7 @@ The following examples are available for using the basic account (A high-level A | 4 | [account_manipulate](./account/manipulate_did.rs) | How to manipulate a DID Document by adding/removing Verification Methods and Services. | | 5 | [account_lazy](./account/lazy.rs) | How to take control over publishing DID updates manually, instead of the default automated behavior. | | 6 | [account_signing](./account/signing.rs) | Using a DID to sign arbitrary statements and validating them. | +| 7 | [account_multiple](./account/multiple_identities.rs) | How to create multiple identities from a builder and how to load existing identities into an account. | The following examples are available for using the low-level APIs, which provides more flexibility at the cost of complexity: diff --git a/examples/account/config.rs b/examples/account/config.rs index c6549084dc..ad64a53f7c 100644 --- a/examples/account/config.rs +++ b/examples/account/config.rs @@ -4,10 +4,10 @@ //! cargo run --example account_config use identity::account::Account; +use identity::account::AccountBuilder; use identity::account::AccountStorage; use identity::account::AutoSave; -use identity::account::IdentityCreate; -use identity::account::IdentityState; +use identity::account::IdentitySetup; use identity::account::Result; use identity::iota::IotaDID; use identity::iota::Network; @@ -16,24 +16,26 @@ use identity::iota::Network; async fn main() -> Result<()> { pretty_env_logger::init(); - // Set-up for private Tangle + // Set-up for a private Tangle // You can use https://github.com/iotaledger/one-click-tangle for a local setup. // The `network_name` needs to match the id of the network or a part of it. - // As an example we are treating the devnet as a `private-tangle`, so we use `dev`. + // As an example we are treating the devnet as a private tangle, so we use `dev`. // Replace this with `tangle` if you run this against a one-click private tangle. let network_name = "dev"; let network = Network::try_from_name(network_name)?; + // In a locally running one-click tangle, this would often be `http://127.0.0.1:14265/` let private_node_url = "https://api.lb-0.h.chrysalis-devnet.iota.cafe"; // Create a new Account with explicit configuration - let account: Account = Account::builder() + let mut builder: AccountBuilder = Account::builder() .autosave(AutoSave::Never) // never auto-save. rely on the drop save .autosave(AutoSave::Every) // save immediately after every action .autosave(AutoSave::Batch(10)) // save after every 10 actions - .dropsave(false) // save the account state on drop + .autopublish(true) // publish to the tangle automatically on every update + .dropsave(true) // save the account state on drop .milestone(4) // save a snapshot every 4 actions - .storage(AccountStorage::Memory) // use the default in-memory storage adapter + .storage(AccountStorage::Memory) // use the default in-memory storage // configure a mainnet Tangle client with node and permanode .client(Network::Mainnet, |builder| { builder @@ -49,16 +51,13 @@ async fn main() -> Result<()> { .client(network, |builder| { // unwrap is safe, we provided a valid node URL builder.node(private_node_url).unwrap() - }) - .build() - .await?; + }); - // Create an Identity specifically on the devnet by passing `network_name` + // Create an identity specifically on the devnet by passing `network_name` // The same applies if we wanted to create an identity on a private tangle - let id_create = IdentityCreate::new().network(network_name)?; + let id_create = IdentitySetup::new().network(network_name)?; - // Create a new Identity with the network name set. - let identity: IdentityState = match account.create_identity(id_create).await { + let identity: Account = match builder.create_identity(id_create).await { Ok(identity) => identity, Err(err) => { eprintln!("[Example] Error: {:?} {}", err, err.to_string()); @@ -66,9 +65,11 @@ async fn main() -> Result<()> { return Ok(()); } }; - let iota_did: &IotaDID = identity.try_did()?; - // Prints the Identity Resolver Explorer URL, the entire history can be observed on this page by "Loading History". + let iota_did: &IotaDID = identity.did(); + + // Prints the Identity Resolver Explorer URL. + // The entire history can be observed on this page by clicking "Loading History". println!( "[Example] Explore the DID Document = {}/{}", iota_did.network()?.explorer_url().unwrap().to_string(), diff --git a/examples/account/create_did.rs b/examples/account/create_did.rs index bd0ef05985..06108b7de5 100644 --- a/examples/account/create_did.rs +++ b/examples/account/create_did.rs @@ -7,8 +7,7 @@ use std::path::PathBuf; use identity::account::Account; use identity::account::AccountStorage; -use identity::account::IdentityCreate; -use identity::account::IdentityState; +use identity::account::IdentitySetup; use identity::account::Result; use identity::iota::IotaDID; @@ -23,30 +22,32 @@ async fn main() -> Result<()> { let stronghold_path: PathBuf = "./example-strong.hodl".into(); let password: String = "my-password".into(); - // Create a new Account with the default configuration + // Create a new identity using Stronghold as local storage. + // + // The creation step generates a keypair, builds an identity + // and publishes it to the IOTA mainnet. let account: Account = Account::builder() .storage(AccountStorage::Stronghold(stronghold_path, Some(password))) - .build() + .create_identity(IdentitySetup::default()) .await?; - // Create a new Identity with default settings - // - // This step generates a keypair, creates an identity and publishes it to the IOTA mainnet. - let identity: IdentityState = account.create_identity(IdentityCreate::default()).await?; - let iota_did: &IotaDID = identity.try_did()?; + // Retrieve the did of the newly created identity. + let iota_did: &IotaDID = account.did(); // Print the local state of the DID Document println!( "[Example] Local Document from {} = {:#?}", iota_did, - identity.to_document() + account.state().await?.to_document() ); - // Prints the Identity Resolver Explorer URL, the entire history can be observed on this page by "Loading History". + // Prints the Identity Resolver Explorer URL. + // The entire history can be observed on this page by clicking "Loading History". println!( "[Example] Explore the DID Document = {}/{}", iota_did.network()?.explorer_url().unwrap().to_string(), iota_did.to_string() ); + Ok(()) } diff --git a/examples/account/lazy.rs b/examples/account/lazy.rs index 433832e72c..5e50d725c8 100644 --- a/examples/account/lazy.rs +++ b/examples/account/lazy.rs @@ -6,8 +6,7 @@ use std::path::PathBuf; use identity::account::Account; use identity::account::AccountStorage; -use identity::account::IdentityCreate; -use identity::account::IdentityState; +use identity::account::IdentitySetup; use identity::account::Result; use identity::core::Url; use identity::iota::IotaDID; @@ -23,21 +22,15 @@ async fn main() -> Result<()> { // Create a new Account with auto publishing set to false. // This means updates are not pushed to the tangle automatically. // Rather, when we publish, multiple updates are batched together. - let account: Account = Account::builder() + let mut account: Account = Account::builder() .storage(AccountStorage::Stronghold(stronghold_path, Some(password))) .autopublish(false) - .build() + .create_identity(IdentitySetup::default()) .await?; - // Create a new Identity with default settings. - // The identity will only be written to the local storage - not published to the tangle. - let identity: IdentityState = account.create_identity(IdentityCreate::default()).await?; - - // Retrieve the DID from the newly created Identity state. - let iota_did: &IotaDID = identity.try_did()?; - + // Add a new service to the local DID document. account - .update_identity(iota_did) + .update_identity() .create_service() .fragment("example-service") .type_("LinkedDomains") @@ -47,11 +40,11 @@ async fn main() -> Result<()> { // Publish the newly created DID document, // including the new service, to the tangle. - account.publish_updates(iota_did).await?; + account.publish_updates().await?; // Add another service. account - .update_identity(iota_did) + .update_identity() .create_service() .fragment("another-service") .type_("LinkedDomains") @@ -61,16 +54,20 @@ async fn main() -> Result<()> { // Delete the previously added service. account - .update_identity(iota_did) + .update_identity() .delete_service() .fragment("example-service") .apply() .await?; // Publish the updates as one message to the tangle. - account.publish_updates(iota_did).await?; + account.publish_updates().await?; + + // Retrieve the DID from the newly created identity. + let iota_did: &IotaDID = account.did(); - // Prints the Identity Resolver Explorer URL, the entire history can be observed on this page by "Loading History". + // Prints the Identity Resolver Explorer URL. + // The entire history can be observed on this page by clicking "Loading History". println!( "[Example] Explore the DID Document = {}/{}", iota_did.network()?.explorer_url().unwrap().to_string(), diff --git a/examples/account/manipulate_did.rs b/examples/account/manipulate_did.rs index eeae96818f..f0e811124d 100644 --- a/examples/account/manipulate_did.rs +++ b/examples/account/manipulate_did.rs @@ -7,8 +7,7 @@ use std::path::PathBuf; use identity::account::Account; use identity::account::AccountStorage; -use identity::account::IdentityCreate; -use identity::account::IdentityState; +use identity::account::IdentitySetup; use identity::account::Result; use identity::core::Url; use identity::did::MethodScope; @@ -27,24 +26,18 @@ async fn main() -> Result<()> { let password: String = "my-password".into(); // Create a new Account with the default configuration - let account: Account = Account::builder() + let mut account: Account = Account::builder() .storage(AccountStorage::Stronghold(stronghold_path, Some(password))) - .build() + .create_identity(IdentitySetup::default()) .await?; - // Create a new Identity with default settings - // - // This step generates a keypair, creates an identity and publishes it to the IOTA mainnet. - let identity: IdentityState = account.create_identity(IdentityCreate::default()).await?; - let iota_did: &IotaDID = identity.try_did()?; - // =========================================================================== // Identity Manipulation // =========================================================================== // Add another Ed25519 verification method to the identity account - .update_identity(&iota_did) + .update_identity() .create_method() .fragment("my-next-key") .apply() @@ -52,7 +45,7 @@ async fn main() -> Result<()> { // Associate the newly created method with additional verification relationships account - .update_identity(&iota_did) + .update_identity() .attach_method() .fragment("my-next-key") .scope(MethodScope::CapabilityDelegation) @@ -62,7 +55,7 @@ async fn main() -> Result<()> { // Add a new service to the identity. account - .update_identity(&iota_did) + .update_identity() .create_service() .fragment("my-service-1") .type_("MyCustomService") @@ -72,13 +65,17 @@ async fn main() -> Result<()> { // Remove the Ed25519 verification method account - .update_identity(&iota_did) + .update_identity() .delete_method() .fragment("my-next-key") .apply() .await?; - // Prints the Identity Resolver Explorer URL, the entire history can be observed on this page by "Loading History". + // Retrieve the DID from the newly created identity. + let iota_did: &IotaDID = account.did(); + + // Prints the Identity Resolver Explorer URL. + // The entire history can be observed on this page by clicking "Loading History". println!( "[Example] Explore the DID Document = {}/{}", iota_did.network()?.explorer_url().unwrap().to_string(), diff --git a/examples/account/multiple_identities.rs b/examples/account/multiple_identities.rs new file mode 100644 index 0000000000..d76b062e05 --- /dev/null +++ b/examples/account/multiple_identities.rs @@ -0,0 +1,72 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! cargo run --example account_multiple + +use std::path::PathBuf; + +use identity::account::Account; +use identity::account::AccountBuilder; +use identity::account::AccountStorage; +use identity::account::IdentitySetup; +use identity::account::Result; +use identity::iota::IotaDID; + +#[tokio::main] +async fn main() -> Result<()> { + pretty_env_logger::init(); + + // Sets the location and password for the Stronghold + // + // Stronghold is an encrypted file that manages private keys. + // It implements best practices for security and is the recommended way of handling private keys. + let stronghold_path: PathBuf = "./example-strong.hodl".into(); + let password: String = "my-password".into(); + + // Create an AccountBuilder to make it easier to create multiple identities. + // Every account created from the builder will use the same storage - stronghold in this case. + let mut builder: AccountBuilder = + Account::builder().storage(AccountStorage::Stronghold(stronghold_path, Some(password))); + + // The creation step generates a keypair, builds an identity + // and publishes it to the IOTA mainnet. + let account1: Account = builder.create_identity(IdentitySetup::default()).await?; + + // Create a second identity. + let account2: Account = builder.create_identity(IdentitySetup::default()).await?; + + // Retrieve the did of the identity that account1 manages. + let iota_did1: IotaDID = account1.did().to_owned(); + + // Suppose we're done with account1 and drop it. + std::mem::drop(account1); + + // Now we want to modify the iota_did1 identity - how do we do that? + // We can load the identity from storage into an account using the builder. + let mut account1 = builder.load_identity(iota_did1).await?; + + // Now we can modify the identity. + account1 + .update_identity() + .create_method() + .fragment("my-key") + .apply() + .await?; + + // Note that there can only ever be one account that manages the same did. + // If we attempt to create another account that manages the same did as account2, we get an error. + assert!(matches!( + builder.load_identity(account2.did().to_owned()).await.unwrap_err(), + identity::account::Error::IdentityInUse + )); + + // Prints the Identity Resolver Explorer URL. + // The entire history can be observed on this page by clicking "Loading History". + println!( + "[Example] Explore the DID Document = {}/{}", + account1.did().network()?.explorer_url().unwrap().to_string(), + account1.did().to_string() + ); + + Ok(()) +} diff --git a/examples/account/signing.rs b/examples/account/signing.rs index 7b5f659cbc..f0f0a42b55 100644 --- a/examples/account/signing.rs +++ b/examples/account/signing.rs @@ -7,8 +7,7 @@ use std::path::PathBuf; use identity::account::Account; use identity::account::AccountStorage; -use identity::account::IdentityCreate; -use identity::account::IdentityState; +use identity::account::IdentitySetup; use identity::account::Result; use identity::core::json; use identity::core::FromJson; @@ -31,25 +30,19 @@ async fn main() -> Result<()> { let stronghold_path: PathBuf = "./example-strong.hodl".into(); let password: String = "my-password".into(); - // Create a new Account with the default configuration - let account: Account = Account::builder() + // Create a new Account with stronghold storage. + let mut account: Account = Account::builder() .storage(AccountStorage::Stronghold(stronghold_path, Some(password))) - .build() + .create_identity(IdentitySetup::default()) .await?; - // Create a new Identity with default settings - // - // This step generates a keypair, creates an identity and publishes it to the IOTA mainnet. - let identity: IdentityState = account.create_identity(IdentityCreate::default()).await?; - let iota_did: &IotaDID = identity.try_did()?; - // =========================================================================== // Signing Example // =========================================================================== // Add a new Ed25519 Verification Method to the identity account - .update_identity(&iota_did) + .update_identity() .create_method() .fragment("key-1") .apply() @@ -70,22 +63,26 @@ async fn main() -> Result<()> { // Issue an unsigned Credential... let mut credential: Credential = Credential::builder(Default::default()) - .issuer(Url::parse(&iota_did.as_str())?) + .issuer(Url::parse(account.did().as_str())?) .type_("UniversityDegreeCredential") .subject(subject) .build()?; // ...and sign the Credential with the previously created Verification Method - account.sign(&iota_did, "key-1", &mut credential).await?; + account.sign("key-1", &mut credential).await?; println!("[Example] Local Credential = {:#}", credential); // Fetch the DID Document from the Tangle // // This is an optional step to ensure DID Document consistency. - let resolved: IotaDocument = account.resolve_identity(&iota_did).await?; + let resolved: IotaDocument = account.resolve_identity().await?; + + // Retrieve the DID from the newly created identity. + let iota_did: &IotaDID = account.did(); - // Prints the Identity Resolver Explorer URL, the entire history can be observed on this page by "Loading History". + // Prints the Identity Resolver Explorer URL. + // The entire history can be observed on this page by clicking "Loading History". println!( "[Example] Explore the DID Document = {}/{}", iota_did.network()?.explorer_url().unwrap().to_string(), diff --git a/identity-account/src/account/account.rs b/identity-account/src/account/account.rs index c0bb483ebb..2230ed96a4 100644 --- a/identity-account/src/account/account.rs +++ b/identity-account/src/account/account.rs @@ -19,39 +19,44 @@ use serde::Serialize; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::Arc; -use tokio::sync::RwLock; -use tokio::sync::RwLockWriteGuard; use crate::account::AccountBuilder; -use crate::error::Error; use crate::error::Result; -use crate::events::Command; use crate::events::Commit; use crate::events::Context; +use crate::events::CreateIdentity; use crate::events::Event; use crate::events::EventData; -use crate::identity::IdentityCreate; -use crate::identity::IdentityId; -use crate::identity::IdentityIndex; -use crate::identity::IdentityKey; -use crate::identity::IdentityLock; +use crate::events::Update; +use crate::identity::DIDLease; +use crate::identity::IdentitySetup; use crate::identity::IdentitySnapshot; use crate::identity::IdentityState; -use crate::identity::IdentityTag; use crate::identity::IdentityUpdater; use crate::identity::TinyMethod; use crate::storage::Storage; use crate::types::Generation; use crate::types::KeyLocation; +use crate::Error; + +use super::config::AccountSetup; +use super::config::AutoSave; +use super::AccountConfig; const OSC: Ordering = Ordering::SeqCst; +/// An account manages one identity. +/// +/// It handles private keys, writing to storage and +/// publishing to the Tangle. #[derive(Debug)] pub struct Account { - config: Arc, - state: State, - store: Box, - index: RwLock, + config: AccountConfig, + storage: Arc, + client_map: Arc, + actions: AtomicUsize, + did: IotaDID, + did_lease: DIDLease, } impl Account { @@ -60,26 +65,36 @@ impl Account { AccountBuilder::new() } - /// Creates a new `Account` instance. - pub async fn new(store: impl Storage) -> Result { - Self::with_config(store, Config::new()).await + /// Creates an [`Account`] for an existing identity, if it exists in the [`Storage`]. + pub(crate) async fn load_identity(setup: AccountSetup, did: IotaDID) -> Result { + // Ensure the did exists in storage + setup.storage.snapshot(&did).await?.ok_or(Error::IdentityNotFound)?; + + let did_lease = setup.storage.lease_did(&did).await?; + + Self::with_setup(setup, did, did_lease).await } /// Creates a new `Account` instance with the given `config`. - pub async fn with_config(store: impl Storage, config: Config) -> Result { - let index: IdentityIndex = store.index().await?; - + async fn with_setup(setup: AccountSetup, did: IotaDID, did_lease: DIDLease) -> Result { Ok(Self { - store: Box::new(store), - state: State::new(), - config: Arc::new(config), - index: RwLock::new(index), + config: setup.config, + storage: setup.storage, + client_map: setup.client_map, + actions: AtomicUsize::new(0), + did, + did_lease, }) } /// Returns a reference to the [Storage] implementation. - pub fn store(&self) -> &dyn Storage { - &*self.store + pub fn storage(&self) -> &dyn Storage { + self.storage.as_ref() + } + + /// Returns whether auto-publish is enabled. + pub fn autopublish(&self) -> bool { + self.config.autopublish } /// Returns the auto-save configuration value. @@ -87,106 +102,85 @@ impl Account { self.config.autosave } - /// Returns the save-on-drop configuration value. + /// Returns whether save-on-drop is enabled. pub fn dropsave(&self) -> bool { self.config.dropsave } /// Returns the total number of actions executed by this instance. pub fn actions(&self) -> usize { - self.state.actions.load(OSC) + self.actions.load(OSC) } /// Adds a pre-configured `Client` for Tangle interactions. pub fn set_client(&self, client: Client) { - self.state.clients.insert(client); + self.client_map.insert(client); + } + + /// Returns the did of the managed identity. + pub fn did(&self) -> &IotaDID { + &self.did } // =========================================================================== // Identity // =========================================================================== - /// Returns a list of tags identifying the identities in the account. - pub async fn list_identities(&self) -> Vec { - self.index.read().await.tags() - } - - /// Finds and returns the identity state for the identity specified by given `key`. - pub async fn find_identity(&self, key: K) -> Result> { - match self.resolve_id(&key).await { - Some(identity) => self - .load_snapshot(identity) - .await - .map(IdentitySnapshot::into_identity) - .map(Some), - None => Ok(None), - } + /// Return a copy of the latest state of the identity + pub async fn state(&self) -> Result { + Ok(self.load_snapshot().await?.into_identity()) } - /// Create an identity from the specified configuration options. - pub async fn create_identity(&self, input: IdentityCreate) -> Result { - // Acquire write access to the index. - let mut index: RwLockWriteGuard<'_, _> = self.index.write().await; + /// Resolves the DID Document associated with this `Account` from the Tangle. + pub async fn resolve_identity(&self) -> Result { + let snapshot: IdentitySnapshot = self.load_snapshot().await?; + let did: &IotaDID = snapshot.identity().try_did()?; - let identity: IdentityId = index.try_next_id()?; + // Fetch the DID Document from the Tangle + self.client_map.resolve(did).await.map_err(Into::into) + } - // Create the initialization command - let command: Command = Command::CreateIdentity { + /// Creates a new identity and returns an [`Account`] instance to manage it. + /// The identity is stored locally in the [`Storage`] given in [`AccountSetup`], and published + /// using the [`ClientMap`]. + /// + /// See [`IdentityCreate`] to customize the identity creation. + pub(crate) async fn create_identity(setup: AccountSetup, input: IdentitySetup) -> Result { + let command = CreateIdentity { network: input.network, method_secret: input.method_secret, authentication: Self::key_to_method(input.key_type), }; - // Process the command - self.process(identity, command, false).await?; + let snapshot = IdentitySnapshot::new(IdentityState::new()); - // Read the latest snapshot - let snapshot: IdentitySnapshot = self.load_snapshot(identity).await?; - let document: &IotaDID = snapshot.identity().try_did()?; + let (did, did_lease, events): (IotaDID, DIDLease, Vec) = command + .process(snapshot.identity().integration_generation(), setup.storage.as_ref()) + .await?; - // Add the identity to the index - if let Some(name) = input.name { - index.set_named(identity, document, name)?; - } else { - index.set(identity, document)?; - } + let commits = Self::commit_events(&did, &setup.config, setup.storage.as_ref(), &snapshot, &events).await?; - // Store the updated identity index - self.store.set_index(&index).await?; + let account = Self::with_setup(setup, did, did_lease).await?; - // Write the changes to disk - self.save(false).await?; + account.publish_commits(snapshot, commits, true).await?; - // Return the identity state - Ok(snapshot.into_identity()) + Ok(account) } - /// Returns the `IdentityUpdater` for the given `key`. + /// Returns the [`IdentityUpdater`] for this identity. /// /// On this type, various operations can be executed /// that modify an identity, such as creating services or methods. - pub fn update_identity<'account, 'key, K: IdentityKey>( - &'account self, - key: &'key K, - ) -> IdentityUpdater<'account, 'key, K> { - IdentityUpdater::new(self, key) + pub fn update_identity(&mut self) -> IdentityUpdater<'_> { + IdentityUpdater::new(self) } - /// Removes the identity specified by the given `key`. + /// Removes the identity from the local storage entirely. /// - /// Note: This will remove all associated events and key material - recovery is NOT POSSIBLE! - pub async fn delete_identity(&self, key: K) -> Result<()> { - // Acquire write access to the index. - let mut index: RwLockWriteGuard<'_, _> = self.index.write().await; - - // Remove the identity from the index - let identity: IdentityId = index.del(key)?.1; - + /// Note: This will remove all associated document updates and key material - recovery is NOT POSSIBLE! + pub async fn delete_identity(self) -> Result<()> { // Remove all associated keys and events - self.store.purge(identity).await?; - - // Store the updated identity index - self.store.set_index(&index).await?; + self.storage().purge(self.did()).await?; // Write the changes to disk self.save(false).await?; @@ -194,85 +188,59 @@ impl Account { Ok(()) } - /// Resolves the DID Document associated with the specified `key`. - pub async fn resolve_identity(&self, key: K) -> Result { - let identity: IdentityId = self.try_resolve_id(&key).await?; - let snapshot: IdentitySnapshot = self.load_snapshot(identity).await?; - let document: &IotaDID = snapshot.identity().try_did()?; - - // Fetch the DID Document from the Tangle - self.state.clients.resolve(document).await.map_err(Into::into) - } - /// Signs `data` with the key specified by `fragment`. - pub async fn sign(&self, key: K, fragment: &str, target: &mut U) -> Result<()> + pub async fn sign(&self, fragment: &str, target: &mut U) -> Result<()> where - K: IdentityKey, U: Serialize + SetSignature, { - let identity: IdentityId = self.try_resolve_id(&key).await?; - let snapshot: IdentitySnapshot = self.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = self.load_snapshot().await?; let state: &IdentityState = snapshot.identity(); let fragment: Fragment = Fragment::new(fragment); let method: &TinyMethod = state.methods().fetch(fragment.name())?; let location: &KeyLocation = method.location(); - state.sign_data(&self.store, location, target).await?; + state.sign_data(self.did(), self.storage(), location, target).await?; Ok(()) } - async fn resolve_id(&self, key: &K) -> Option { - self.index.read().await.get(key) - } - - async fn try_resolve_id(&self, key: &K) -> Result { - self.resolve_id(key).await.ok_or(Error::IdentityNotFound) - } - - async fn try_resolve_id_lock(&self, key: &K) -> Result { - self.index.write().await.get_lock(key).ok_or(Error::IdentityNotFound) - } - // =========================================================================== // Misc. Private // =========================================================================== - - /// Updates the identity specified by the given `key` with the given `command`. - pub(crate) async fn apply_command(&self, key: &K, command: Command) -> Result<()> { - // Hold on to an `IdentityId`s individual lock until we've finished processing the update. - let identity_lock = self.try_resolve_id_lock(key).await?; - let identity: RwLockWriteGuard<'_, IdentityId> = identity_lock.write().await; - - self.process(*identity, command, true).await?; - - Ok(()) - } - - pub(crate) async fn process(&self, id: IdentityId, command: Command, persist: bool) -> Result<()> { + pub(crate) async fn process_update(&self, command: Update, persist: bool) -> Result<()> { // Load the latest state snapshot from storage - let root: IdentitySnapshot = self.load_snapshot(id).await?; + let root: IdentitySnapshot = self.load_snapshot().await?; debug!("[Account::process] Root = {:#?}", root); // Process the command with a read-only view of the state - let context: Context<'_> = Context::new(root.identity(), self.store()); + let context: Context<'_> = Context::new(self.did(), root.identity(), self.storage()); let events: Option> = command.process(context).await?; debug!("[Account::process] Events = {:#?}", events); if let Some(events) = events { - // Commit all events returned by the command - let commits: Vec = self.commit_events(&root, &events).await?; + let commits: Vec = Self::commit_events(self.did(), &self.config, self.storage(), &root, &events).await?; + self.publish_commits(root, commits, persist).await?; + } - debug!("[Account::process] Commits = {:#?}", commits); + Ok(()) + } - self.publish(root, commits, false).await?; - } + /// Publishes the given commits to the tangle. + pub(crate) async fn publish_commits( + &self, + root: IdentitySnapshot, + commits: Vec, + persist: bool, + ) -> Result<()> { + debug!("[Account::process] Commits = {:#?}", commits); + + self.publish(root, commits, false).await?; // Update the total number of executions - self.state.actions.fetch_add(1, OSC); + self.actions.fetch_add(1, OSC); if persist { self.save(false).await?; @@ -292,20 +260,24 @@ impl Account { let location: &KeyLocation = method.location(); // Sign the DID Document with the current authentication method - new_state.sign_data(&self.store, location, document).await?; + new_state + .sign_data(self.did(), self.storage(), location, document) + .await?; } else { let method: &TinyMethod = old_state.authentication()?; let location: &KeyLocation = method.location(); // Sign the DID Document with the previous authentication method - old_state.sign_data(&self.store, location, document).await?; + old_state + .sign_data(self.did(), self.storage(), location, document) + .await?; } Ok(()) } async fn process_integration_change(&self, old_root: IdentitySnapshot) -> Result<()> { - let new_root: IdentitySnapshot = self.load_snapshot(old_root.id()).await?; + let new_root: IdentitySnapshot = self.load_snapshot().await?; let old_state: &IdentityState = old_root.identity(); let new_state: &IdentityState = new_root.identity(); @@ -317,18 +289,18 @@ impl Account { let message: MessageId = if self.config.testmode { MessageId::null() } else { - self.state.clients.publish_document(&new_doc).await?.into() + self.client_map.publish_document(&new_doc).await?.into() }; let events: [Event; 1] = [Event::new(EventData::IntegrationMessage(message))]; - self.commit_events(&new_root, &events).await?; + Self::commit_events(self.did(), &self.config, self.storage(), &new_root, &events).await?; Ok(()) } async fn process_diff_change(&self, old_root: IdentitySnapshot) -> Result<()> { - let new_root: IdentitySnapshot = self.load_snapshot(old_root.id()).await?; + let new_root: IdentitySnapshot = self.load_snapshot().await?; let old_state: &IdentityState = old_root.identity(); let new_state: &IdentityState = new_root.identity(); @@ -343,14 +315,15 @@ impl Account { let method: &TinyMethod = old_state.authentication()?; let location: &KeyLocation = method.location(); - old_state.sign_data(&self.store, location, &mut diff).await?; + old_state + .sign_data(self.did(), self.storage(), location, &mut diff) + .await?; let message: MessageId = if self.config.testmode { MessageId::null() } else { self - .state - .clients + .client_map .publish_diff(old_state.this_message_id(), &diff) .await? .into() @@ -358,7 +331,7 @@ impl Account { let events: [Event; 1] = [Event::new(EventData::DiffMessage(message))]; - self.commit_events(&new_root, &events).await?; + Self::commit_events(self.did(), &self.config, self.storage(), &new_root, &events).await?; Ok(()) } @@ -371,37 +344,43 @@ impl Account { } #[doc(hidden)] - pub async fn load_snapshot(&self, id: IdentityId) -> Result { + pub async fn load_snapshot(&self) -> Result { // Retrieve the state snapshot from storage or create a new one. let initial: IdentitySnapshot = self - .store - .snapshot(id) + .storage() + .snapshot(self.did()) .await? - .unwrap_or_else(|| IdentitySnapshot::new(IdentityState::new(id))); + .unwrap_or_else(|| IdentitySnapshot::new(IdentityState::new())); // Apply all recent events to the state and create a new snapshot self - .store - .stream(id, initial.sequence()) + .storage() + .stream(self.did(), initial.sequence()) .await? .try_fold(initial, Self::fold_snapshot) .await } - async fn load_snapshot_at(&self, id: IdentityId, generation: Generation) -> Result { - let initial: IdentitySnapshot = IdentitySnapshot::new(IdentityState::new(id)); + async fn load_snapshot_at(&self, generation: Generation) -> Result { + let initial: IdentitySnapshot = IdentitySnapshot::new(IdentityState::new()); // Apply all events up to `generation` self - .store - .stream(id, Generation::new()) + .storage() + .stream(self.did(), Generation::new()) .await? .take(generation.to_u32() as usize) .try_fold(initial, Self::fold_snapshot) .await } - async fn commit_events(&self, state: &IdentitySnapshot, events: &[Event]) -> Result> { + async fn commit_events( + did: &IotaDID, + config: &AccountConfig, + storage: &dyn Storage, + state: &IdentitySnapshot, + events: &[Event], + ) -> Result> { // Bail early if there are no new events if events.is_empty() { return Ok(Vec::new()); @@ -414,14 +393,14 @@ impl Account { // Iterate over the events and create a new commit with the correct sequence for event in events { sequence = sequence.try_increment()?; - commits.push(Commit::new(state.id(), sequence, event.clone())); + commits.push(Commit::new(did.clone(), sequence, event.clone())); } // Append the list of commits to the store - self.store.append(state.id(), &commits).await?; + storage.append(did, &commits).await?; // Store a snapshot every N events - if sequence.to_u32() % self.config.milestone == 0 { + if sequence.to_u32() % config.milestone == 0 { let mut state: IdentitySnapshot = state.clone(); // Fold the new commits into the snapshot @@ -430,7 +409,7 @@ impl Account { } // Store the new snapshot - self.store.set_snapshot(state.id(), &state).await?; + storage.set_snapshot(did, &state).await?; } // Return the list of stored events @@ -440,10 +419,10 @@ impl Account { async fn save(&self, force: bool) -> Result<()> { match self.config.autosave { AutoSave::Every => { - self.store.flush_changes().await?; + self.storage().flush_changes().await?; } AutoSave::Batch(step) if force || (step != 0 && self.actions() % step == 0) => { - self.store.flush_changes().await?; + self.storage().flush_changes().await?; } AutoSave::Batch(_) | AutoSave::Never => {} } @@ -451,23 +430,24 @@ impl Account { Ok(()) } - /// Push all unpublished changes for the given identity to the tangle in a single message. - pub async fn publish_updates(&self, key: K) -> Result<()> { - let identity_lock: IdentityLock = self.try_resolve_id_lock(&key).await?; - let identity: RwLockWriteGuard<'_, IdentityId> = identity_lock.write().await; - + /// Push all unpublished changes to the tangle in a single message. + pub async fn publish_updates(&mut self) -> Result<()> { // Get the last commit generation that was published to the tangle. - let last_published: Generation = self.store.published_generation(*identity).await?.unwrap_or_default(); + let last_published: Generation = self + .storage() + .published_generation(self.did()) + .await? + .unwrap_or_default(); // Get the commits that need to be published. - let commits: Vec = self.store.collect(*identity, last_published).await?; + let commits: Vec = self.storage().collect(self.did(), last_published).await?; if commits.is_empty() { return Ok(()); } // Load the snapshot that represents the state on the tangle. - let snapshot: IdentitySnapshot = self.load_snapshot_at(*identity, last_published).await?; + let snapshot: IdentitySnapshot = self.load_snapshot_at(last_published).await?; self.publish(snapshot, commits, true).await?; @@ -480,8 +460,6 @@ impl Account { return Ok(()); } - let id: IdentityId = snapshot.id(); - match Publish::new(&commits) { Publish::Integration => self.process_integration_change(snapshot).await?, Publish::Diff => self.process_diff_change(snapshot).await?, @@ -494,7 +472,7 @@ impl Account { // which is required to be set for subsequent updates. // The next snapshot that loads the tangle state will require this message id to be set. let generation: Generation = Generation::from_u32(last_commit_generation.to_u32() + 1); - self.store.set_published_generation(id, generation).await?; + self.storage().set_published_generation(self.did(), generation).await?; } Ok(()) @@ -511,124 +489,7 @@ impl Drop for Account { fn drop(&mut self) { if self.config.dropsave && self.actions() != 0 { // TODO: Handle Result (?) - let _ = executor::block_on(self.store.flush_changes()); - } - } -} - -// ============================================================================= -// Config -// ============================================================================= - -/// Top-level configuration for Identity [Account]s -#[derive(Clone, Debug)] -pub struct Config { - autosave: AutoSave, - autopublish: bool, - dropsave: bool, - testmode: bool, - milestone: u32, -} - -impl Config { - const MILESTONE: u32 = 1; - - /// Creates a new default `Config`. - pub fn new() -> Self { - Self { - autosave: AutoSave::Every, - autopublish: true, - dropsave: true, - testmode: false, - milestone: Self::MILESTONE, - } - } - - /// Sets the account auto-save behaviour. - /// - [`Every`][AutoSave::Every] => Save to storage on every update - /// - [`Never`][AutoSave::Never] => Never save to storage when updating - /// - [`Batch(n)`][AutoSave::Batch] => Save to storage after every `n` updates. - /// - /// Note that when [`Never`][AutoSave::Never] is selected, you will most - /// likely want to set [`dropsave`][Self::dropsave] to `true`. - /// - /// Default: [`Every`][AutoSave::Every] - pub fn autosave(mut self, value: AutoSave) -> Self { - self.autosave = value; - self - } - - /// Sets the account auto-publish behaviour. - /// - `true` => publish to the Tangle on every DID document change - /// - `false` => never publish automatically - /// - /// Default: `true` - pub fn autopublish(mut self, value: bool) -> Self { - self.autopublish = value; - self - } - - /// Save the account state on drop. - /// If set to `false`, set [`autosave`][Self::autosave] to - /// either [`Every`][AutoSave::Every] or [`Batch(n)`][AutoSave::Batch]. - /// - /// Default: `true` - pub fn dropsave(mut self, value: bool) -> Self { - self.dropsave = value; - self - } - - /// Save a state snapshot every N actions. - pub fn milestone(mut self, value: u32) -> Self { - self.milestone = value; - self - } - - #[doc(hidden)] - pub fn testmode(mut self, value: bool) -> Self { - self.testmode = value; - self - } -} - -impl Default for Config { - fn default() -> Self { - Self::new() - } -} - -// ============================================================================= -// AutoSave -// ============================================================================= - -/// Available auto-save behaviours. -#[derive(Clone, Copy, Debug)] -pub enum AutoSave { - /// Never save - Never, - /// Save after every action - Every, - /// Save after every N actions - Batch(usize), -} - -// ============================================================================= -// State -// ============================================================================= - -// Internal account state -#[derive(Debug)] -struct State { - actions: AtomicUsize, - clients: ClientMap, -} - -impl State { - /// Creates a new `State` instance. - fn new() -> Self { - Self { - actions: AtomicUsize::new(0), - clients: ClientMap::new(), + let _ = executor::block_on(self.storage().flush_changes()); } } } diff --git a/identity-account/src/account/builder.rs b/identity-account/src/account/builder.rs index c6d1675efd..5f4758bae8 100644 --- a/identity-account/src/account/builder.rs +++ b/identity-account/src/account/builder.rs @@ -1,24 +1,30 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use hashbrown::HashMap; +use identity_iota::did::IotaDID; use identity_iota::tangle::ClientBuilder; +use identity_iota::tangle::ClientMap; use identity_iota::tangle::Network; use identity_iota::tangle::NetworkName; +use std::collections::HashMap; #[cfg(feature = "stronghold")] use std::path::PathBuf; +use std::sync::Arc; #[cfg(feature = "stronghold")] use zeroize::Zeroize; use crate::account::Account; -use crate::account::AutoSave; -use crate::account::Config; use crate::error::Result; +use crate::identity::IdentitySetup; use crate::storage::MemStore; use crate::storage::Storage; #[cfg(feature = "stronghold")] use crate::storage::Stronghold; +use super::config::AccountConfig; +use super::config::AccountSetup; +use super::config::AutoSave; + /// The storage adapter used by an [Account]. /// /// Note that [AccountStorage::Stronghold] is only available if the `stronghold` feature is activated, which it is by @@ -28,24 +34,35 @@ pub enum AccountStorage { Memory, #[cfg(feature = "stronghold")] Stronghold(PathBuf, Option), - Custom(Box), + Custom(Arc), } -/// An [Account] builder for easier account configuration. +/// An [`Account`] builder for easier account configuration. +/// +/// Accounts created from the same builder share the [`Storage`], +/// used to store identities, and the [`ClientMap`], used to +/// publish identities to the Tangle. +/// +/// The [`Config`] on the other hand is cloned for each account. +/// This means a builder can be reconfigured in-between account creations. #[derive(Debug)] pub struct AccountBuilder { - config: Config, - storage: AccountStorage, - clients: Option>, + config: AccountConfig, + storage_template: Option, + storage: Option>, + client_builders: Option>, + client_map: Arc, } impl AccountBuilder { /// Creates a new `AccountBuilder`. pub fn new() -> Self { Self { - config: Config::new(), - storage: AccountStorage::Memory, - clients: None, + config: AccountConfig::new(), + storage_template: Some(AccountStorage::Memory), + storage: Some(Arc::new(MemStore::new())), + client_builders: None, + client_map: Arc::new(ClientMap::new()), } } @@ -79,30 +96,28 @@ impl AccountBuilder { self } - /// Sets the account storage adapter. - pub fn storage(mut self, value: AccountStorage) -> Self { - self.storage = value; + #[cfg(test)] + /// Set whether the account is in testmode or not. + /// In testmode, the account skips publishing to the tangle. + pub(crate) fn testmode(mut self, value: bool) -> Self { + self.config = self.config.testmode(value); self } - /// Apply configuration to the IOTA Tangle client for the given `Network`. - pub fn client(mut self, network: Network, f: F) -> Self - where - F: FnOnce(ClientBuilder) -> ClientBuilder, - { - self - .clients - .get_or_insert_with(HashMap::new) - .insert(network.name(), f(ClientBuilder::new().network(network))); + /// Sets the account storage adapter. + pub fn storage(mut self, value: AccountStorage) -> Self { + self.storage_template = Some(value); self } - /// Creates a new [Account] based on the builder configuration. - pub async fn build(mut self) -> Result { - let account: Account = match self.storage { - AccountStorage::Memory => Account::with_config(MemStore::new(), self.config).await?, + async fn get_storage(&mut self) -> Result> { + match self.storage_template.take() { + Some(AccountStorage::Memory) => { + let storage = Arc::new(MemStore::new()); + self.storage = Some(storage); + } #[cfg(feature = "stronghold")] - AccountStorage::Stronghold(snapshot, password) => { + Some(AccountStorage::Stronghold(snapshot, password)) => { let passref: Option<&str> = password.as_deref(); let adapter: Stronghold = Stronghold::new(&snapshot, passref).await?; @@ -110,17 +125,75 @@ impl AccountBuilder { password.zeroize(); } - Account::with_config(adapter, self.config).await? + let storage = Arc::new(adapter); + self.storage = Some(storage); } - AccountStorage::Custom(adapter) => Account::with_config(adapter, self.config).await?, + Some(AccountStorage::Custom(storage)) => { + self.storage = Some(storage); + } + None => (), }; - if let Some(clients) = self.clients.take() { - for (_, client) in clients.into_iter() { - account.set_client(client.build().await?); + // unwrap is fine, since by default, storage_template is `Some`, + // which results in storage being `Some`. + // Overwriting storage_template always produces `Some` storage. + Ok(Arc::clone(self.storage.as_ref().unwrap())) + } + + /// Apply configuration to the IOTA Tangle client for the given [`Network`]. + pub fn client(mut self, network: Network, f: F) -> Self + where + F: FnOnce(ClientBuilder) -> ClientBuilder, + { + self + .client_builders + .get_or_insert_with(HashMap::new) + .insert(network.name(), f(ClientBuilder::new().network(network))); + self + } + + async fn build_clients(&mut self) -> Result<()> { + if let Some(hmap) = self.client_builders.take() { + for builder in hmap.into_iter() { + self.client_map.insert(builder.1.build().await?) } } + Ok(()) + } + + /// Creates a new identity based on the builder configuration and returns + /// an [`Account`] instance to manage it. + /// The identity is stored locally in the [`Storage`]. + /// + /// See [`IdentityCreate`] to customize the identity creation. + pub async fn create_identity(&mut self, input: IdentitySetup) -> Result { + self.build_clients().await?; + + let setup = AccountSetup::new_with_options( + self.get_storage().await?, + Some(self.config.clone()), + Some(Arc::clone(&self.client_map)), + ); + + let account = Account::create_identity(setup, input).await?; + + Ok(account) + } + + /// Loads an existing identity with the specified `did` using the current builder configuration. + /// The identity must exist in the configured [`Storage`]. + pub async fn load_identity(&mut self, did: IotaDID) -> Result { + self.build_clients().await?; + + let setup = AccountSetup::new_with_options( + self.get_storage().await?, + Some(self.config.clone()), + Some(Arc::clone(&self.client_map)), + ); + + let account = Account::load_identity(setup, did).await?; + Ok(account) } } diff --git a/identity-account/src/account/config.rs b/identity-account/src/account/config.rs new file mode 100644 index 0000000000..7f79e8c2d8 --- /dev/null +++ b/identity-account/src/account/config.rs @@ -0,0 +1,152 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::Arc; + +use identity_iota::tangle::ClientMap; + +use crate::storage::{MemStore, Storage}; + +/// A wrapper that holds configuration for an [`Account`] instantiation. +/// +/// The setup implements `Clone` so multiple [`Account`]s can be created +/// from the same setup. [`Storage`] and [`ClientMap`] are shared among +/// those accounts, while the [`Config`] is unique to each account. +/// +/// [`Account`]([crate::account::Account]) +#[derive(Clone, Debug)] +pub(crate) struct AccountSetup { + pub(crate) config: AccountConfig, + pub(crate) storage: Arc, + pub(crate) client_map: Arc, +} + +impl Default for AccountSetup { + fn default() -> Self { + Self::new(Arc::new(MemStore::new())) + } +} + +impl AccountSetup { + /// Create a new setup from the given [`Storage`] implementation + /// and with defaults for [`Config`] and [`ClientMap`]. + pub(crate) fn new(storage: Arc) -> Self { + Self { + config: AccountConfig::new(), + storage, + client_map: Arc::new(ClientMap::new()), + } + } + + /// Create a new setup from the given [`Storage`] implementation, + /// as well as optional [`Config`] and [`ClientMap`]. + /// If `None` is passed, the defaults will be used. + pub(crate) fn new_with_options( + storage: Arc, + config: Option, + client_map: Option>, + ) -> Self { + Self { + config: config.unwrap_or_default(), + storage, + client_map: client_map.unwrap_or_else(|| Arc::new(ClientMap::new())), + } + } + + #[cfg(test)] + /// Set the [`Config`] for this setup. + pub(crate) fn config(mut self, value: AccountConfig) -> Self { + self.config = value; + self + } +} + +/// Configuration for [`Account`][crate::account::Account]s. +#[derive(Clone, Debug)] +pub(crate) struct AccountConfig { + pub(crate) autosave: AutoSave, + pub(crate) autopublish: bool, + pub(crate) dropsave: bool, + pub(crate) testmode: bool, + pub(crate) milestone: u32, +} + +impl AccountConfig { + const MILESTONE: u32 = 1; + + /// Creates a new default [`Config`]. + pub(crate) fn new() -> Self { + Self { + autosave: AutoSave::Every, + autopublish: true, + dropsave: true, + testmode: false, + milestone: Self::MILESTONE, + } + } + + /// Sets the account auto-save behaviour. + /// - [`Every`][AutoSave::Every] => Save to storage on every update + /// - [`Never`][AutoSave::Never] => Never save to storage when updating + /// - [`Batch(n)`][AutoSave::Batch] => Save to storage after every `n` updates. + /// + /// Note that when [`Never`][AutoSave::Never] is selected, you will most + /// likely want to set [`dropsave`][Self::dropsave] to `true`. + /// + /// Default: [`Every`][AutoSave::Every] + pub(crate) fn autosave(mut self, value: AutoSave) -> Self { + self.autosave = value; + self + } + + /// Sets the account auto-publish behaviour. + /// - `true` => publish to the Tangle on every DID document change + /// - `false` => never publish automatically + /// + /// Default: `true` + pub(crate) fn autopublish(mut self, value: bool) -> Self { + self.autopublish = value; + self + } + + /// Save the account state on drop. + /// If set to `false`, set [`autosave`][Self::autosave] to + /// either [`Every`][AutoSave::Every] or [`Batch(n)`][AutoSave::Batch]. + /// + /// Default: `true` + pub(crate) fn dropsave(mut self, value: bool) -> Self { + self.dropsave = value; + self + } + + /// Save a state snapshot every N actions. + pub(crate) fn milestone(mut self, value: u32) -> Self { + self.milestone = value; + self + } + + #[cfg(test)] + /// Set whether the account is in testmode or not. + /// In testmode, the account skips publishing to the tangle. + pub(crate) fn testmode(mut self, value: bool) -> Self { + self.testmode = value; + self + } +} + +impl Default for AccountConfig { + fn default() -> Self { + Self::new() + } +} + +/// Available auto-save behaviours. +#[derive(Clone, Copy, Debug)] +pub enum AutoSave { + /// Never save + Never, + /// Save after every action + Every, + /// Save after every N actions + Batch(usize), +} diff --git a/identity-account/src/account/mod.rs b/identity-account/src/account/mod.rs index 5e5ef250cb..e46fbe52ab 100644 --- a/identity-account/src/account/mod.rs +++ b/identity-account/src/account/mod.rs @@ -5,6 +5,8 @@ mod account; mod builder; +mod config; pub use self::account::*; pub use self::builder::*; +pub use self::config::*; diff --git a/identity-account/src/crypto/remote.rs b/identity-account/src/crypto/remote.rs index 034c68f1dc..c03abf569b 100644 --- a/identity-account/src/crypto/remote.rs +++ b/identity-account/src/crypto/remote.rs @@ -6,23 +6,23 @@ use futures::executor; use identity_core::crypto::Sign; use identity_core::error::Error; use identity_core::error::Result; +use identity_iota::did::IotaDID; -use crate::identity::IdentityId; use crate::storage::Storage; use crate::types::KeyLocation; /// A reference to a storage instance and identity key location. #[derive(Debug)] -pub struct RemoteKey<'a, T> { - id: IdentityId, +pub struct RemoteKey<'a> { + did: &'a IotaDID, location: &'a KeyLocation, - store: &'a T, + store: &'a dyn Storage, } -impl<'a, T> RemoteKey<'a, T> { +impl<'a> RemoteKey<'a> { /// Creates a new `RemoteKey` instance. - pub fn new(id: IdentityId, location: &'a KeyLocation, store: &'a T) -> Self { - Self { id, location, store } + pub fn new(did: &'a IotaDID, location: &'a KeyLocation, store: &'a dyn Storage) -> Self { + Self { did, location, store } } } @@ -34,19 +34,16 @@ impl<'a, T> RemoteKey<'a, T> { /// /// Note: The signature implementation is specified by the associated `RemoteKey`. #[derive(Clone, Copy, Debug)] -pub struct RemoteSign<'a, T> { - marker: PhantomData>, +pub struct RemoteSign<'a> { + marker: PhantomData>, } -impl<'a, T> Sign for RemoteSign<'a, T> -where - T: Storage, -{ - type Private = RemoteKey<'a, T>; +impl<'a> Sign for RemoteSign<'a> { + type Private = RemoteKey<'a>; type Output = Vec; fn sign(message: &[u8], key: &Self::Private) -> Result { - let future: _ = key.store.key_sign(key.id, key.location, message.to_vec()); + let future: _ = key.store.key_sign(key.did, key.location, message.to_vec()); executor::block_on(future) .map_err(|_| Error::InvalidProofValue("remote sign")) diff --git a/identity-account/src/error.rs b/identity-account/src/error.rs index fb48374a6d..dbf80897a0 100644 --- a/identity-account/src/error.rs +++ b/identity-account/src/error.rs @@ -91,8 +91,13 @@ pub enum Error { /// Caused by attempting to perform an upate in an invalid context. #[error("Update Error: {0}")] UpdateError(#[from] crate::events::UpdateError), + /// Caused by providing bytes that cannot be used as a private key of the + /// [`KeyType`][identity_core::crypto::KeyType]. #[error("Invalid Private Key: {0}")] InvalidPrivateKey(String), + /// Caused by attempting to create an account for an identity that is already managed by another account. + #[error("Identity Is In-use")] + IdentityInUse, } #[doc(hidden)] diff --git a/identity-account/src/events/commit.rs b/identity-account/src/events/commit.rs index 744310fe75..1bba5778cd 100644 --- a/identity-account/src/events/commit.rs +++ b/identity-account/src/events/commit.rs @@ -1,21 +1,22 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use identity_iota::did::IotaDID; + use crate::events::Event; -use crate::identity::IdentityId; use crate::types::Generation; /// An [event][Event] and position in an identity event sequence. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Commit { - identity: IdentityId, + identity: IotaDID, sequence: Generation, event: Event, } impl Commit { /// Creates a new `Commit`. - pub const fn new(identity: IdentityId, sequence: Generation, event: Event) -> Self { + pub const fn new(identity: IotaDID, sequence: Generation, event: Event) -> Self { Self { identity, sequence, @@ -24,8 +25,8 @@ impl Commit { } /// Returns the identifier of the associated identity. - pub const fn identity(&self) -> IdentityId { - self.identity + pub const fn identity(&self) -> &IotaDID { + &self.identity } /// Returns the sequence index of the event. diff --git a/identity-account/src/events/context.rs b/identity-account/src/events/context.rs index 6f0d4afc2e..f147d23310 100644 --- a/identity-account/src/events/context.rs +++ b/identity-account/src/events/context.rs @@ -1,20 +1,27 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use identity_iota::did::IotaDID; + use crate::identity::IdentityState; use crate::storage::Storage; /// A read-only view of an identity state with a read-write storage instance. #[derive(Debug)] pub struct Context<'a> { + did: &'a IotaDID, state: &'a IdentityState, store: &'a dyn Storage, } impl<'a> Context<'a> { /// Creates a new `Context`. - pub fn new(state: &'a IdentityState, store: &'a dyn Storage) -> Self { - Self { state, store } + pub fn new(did: &'a IotaDID, state: &'a IdentityState, store: &'a dyn Storage) -> Self { + Self { did, state, store } + } + + pub fn did(&self) -> &IotaDID { + self.did } /// Returns the context `state`. diff --git a/identity-account/src/events/macros.rs b/identity-account/src/events/macros.rs index b47621aeef..c538273e4e 100644 --- a/identity-account/src/events/macros.rs +++ b/identity-account/src/events/macros.rs @@ -33,16 +33,15 @@ macro_rules! impl_command_builder { ($(#[$doc:meta])* $ident:ident { $(@ $requirement:ident $field:ident $ty:ty $(= $value:expr)?),* $(,)* }) => { paste::paste! { $(#[$doc])* - #[derive(Clone, Debug)] - pub struct [<$ident Builder>]<'account, 'key, K: $crate::identity::IdentityKey> { - account: &'account Account, - key: &'key K, + #[derive(Debug)] + pub struct [<$ident Builder>]<'account> { + account: &'account mut Account, $( $field: Option<$ty>, )* } - impl<'account, 'key, K: $crate::identity::IdentityKey> [<$ident Builder>]<'account, 'key, K> { + impl<'account> [<$ident Builder>]<'account> { $( pub fn $field>(mut self, value: VALUE) -> Self { self.$field = Some(value.into()); @@ -50,10 +49,9 @@ macro_rules! impl_command_builder { } )* - pub fn new(account: &'account Account, key: &'key K) -> [<$ident Builder>]<'account, 'key, K> { + pub fn new(account: &'account mut Account) -> [<$ident Builder>]<'account> { [<$ident Builder>] { account, - key, $( $field: None, )* @@ -61,20 +59,20 @@ macro_rules! impl_command_builder { } pub async fn apply(self) -> $crate::Result<()> { - let update = $crate::events::Command::$ident { + let update = $crate::events::Update::$ident { $( $field: impl_command_builder!(@finish self $requirement $field $ty $(= $value)?), )* }; - self.account.apply_command(self.key, update).await + self.account.process_update(update, false).await } } - impl<'account, 'key, K: $crate::identity::IdentityKey> $crate::identity::IdentityUpdater<'account, 'key, K> { + impl<'account> $crate::identity::IdentityUpdater<'account> { /// Creates a new builder to modify the identity. See the documentation of the return type for details. - pub fn [<$ident:snake>](&self) -> [<$ident Builder>]<'account, 'key, K> { - [<$ident Builder>]::new(self.account, self.key) + pub fn [<$ident:snake>](&'account mut self) -> [<$ident Builder>]<'account> { + [<$ident Builder>]::new(self.account) } } } diff --git a/identity-account/src/events/mod.rs b/identity-account/src/events/mod.rs index 494f6919a1..f5555618cf 100644 --- a/identity-account/src/events/mod.rs +++ b/identity-account/src/events/mod.rs @@ -4,14 +4,14 @@ #[macro_use] mod macros; -mod command; mod commit; mod context; mod error; mod event; +mod update; -pub use self::command::*; pub use self::commit::*; pub use self::context::*; pub use self::error::*; pub use self::event::*; +pub use self::update::*; diff --git a/identity-account/src/events/command.rs b/identity-account/src/events/update.rs similarity index 74% rename from identity-account/src/events/command.rs rename to identity-account/src/events/update.rs index c4909b31dc..bca6dafdf5 100644 --- a/identity-account/src/events/command.rs +++ b/identity-account/src/events/update.rs @@ -6,6 +6,7 @@ use crypto::signatures::ed25519; use identity_core::common::Fragment; use identity_core::common::Object; use identity_core::common::Url; +use identity_core::crypto::KeyPair; use identity_core::crypto::PublicKey; use identity_did::verification::MethodData; use identity_did::verification::MethodScope; @@ -19,8 +20,7 @@ use crate::events::Context; use crate::events::Event; use crate::events::EventData; use crate::events::UpdateError; -use crate::identity::IdentityId; -use crate::identity::IdentityKey; +use crate::identity::DIDLease; use crate::identity::IdentityState; use crate::identity::TinyMethod; use crate::identity::TinyService; @@ -32,13 +32,89 @@ use crate::types::MethodSecret; // Supported authentication method types. const AUTH_TYPES: &[MethodType] = &[MethodType::Ed25519VerificationKey2018]; +pub(crate) struct CreateIdentity { + pub(crate) network: Option, + pub(crate) method_secret: Option, + pub(crate) authentication: MethodType, +} + +impl CreateIdentity { + pub(crate) async fn process( + &self, + integration_generation: Generation, + store: &dyn Storage, + ) -> Result<(IotaDID, DIDLease, Vec)> { + // The authentication method type must be valid + ensure!( + AUTH_TYPES.contains(&self.authentication), + UpdateError::InvalidMethodType(self.authentication) + ); + + let location: KeyLocation = KeyLocation::new_authentication(self.authentication, integration_generation); + + let keypair: KeyPair = if let Some(MethodSecret::Ed25519(private_key)) = &self.method_secret { + ensure!( + private_key.as_ref().len() == ed25519::SECRET_KEY_LENGTH, + UpdateError::InvalidMethodSecret(format!( + "an ed25519 private key requires {} bytes, found {}", + ed25519::SECRET_KEY_LENGTH, + private_key.as_ref().len() + )) + ); + + KeyPair::try_from_ed25519_bytes(private_key.as_ref())? + } else { + KeyPair::new_ed25519()? + }; + + // Generate a new DID URL from the public key + let did: IotaDID = if let Some(network) = &self.network { + IotaDID::new_with_network(keypair.public().as_ref(), network.clone())? + } else { + IotaDID::new(keypair.public().as_ref())? + }; + + ensure!( + !store.key_exists(&did, &location).await?, + UpdateError::DocumentAlreadyExists + ); + + let did_lease = store.lease_did(&did).await?; + + let private_key = keypair.private().to_owned(); + std::mem::drop(keypair); + + let public: PublicKey = insert_method_secret( + store, + &did, + &location, + self.authentication, + MethodSecret::Ed25519(private_key), + ) + .await?; + + let data: MethodData = MethodData::new_b58(public.as_ref()); + let method: TinyMethod = TinyMethod::new(location, data, None); + + let method_fragment = Fragment::new(method.location().fragment()); + + Ok(( + did.clone(), + did_lease, + vec![ + Event::new(EventData::IdentityCreated(did)), + Event::new(EventData::MethodCreated(MethodScope::VerificationMethod, method)), + Event::new(EventData::MethodAttached( + method_fragment, + vec![MethodScope::Authentication], + )), + ], + )) + } +} + #[derive(Clone, Debug)] -pub(crate) enum Command { - CreateIdentity { - network: Option, - method_secret: Option, - authentication: MethodType, - }, +pub(crate) enum Update { CreateMethod { scope: MethodScope, type_: MethodType, @@ -67,8 +143,9 @@ pub(crate) enum Command { }, } -impl Command { +impl Update { pub(crate) async fn process(self, context: Context<'_>) -> Result>> { + let did: &IotaDID = context.did(); let state: &IdentityState = context.state(); let store: &dyn Storage = context.store(); @@ -77,66 +154,12 @@ impl Command { trace!("[Command::process] Store = {:?}", store); match self { - Self::CreateIdentity { - network, - method_secret, - authentication, - } => { - // The state must not be initialized - ensure!(state.did().is_none(), UpdateError::DocumentAlreadyExists); - - // The authentication method type must be valid - ensure!( - AUTH_TYPES.contains(&authentication), - UpdateError::InvalidMethodType(authentication) - ); - - let generation: Generation = state.integration_generation(); - let location: KeyLocation = KeyLocation::new_authentication(authentication, generation); - - // The key location must be available - // TODO: config: strict - ensure!( - !store.key_exists(state.id(), &location).await?, - UpdateError::DuplicateKeyLocation(location) - ); - - let public: PublicKey = if let Some(method_private_key) = method_secret { - insert_method_secret(store, state.id(), &location, authentication, method_private_key).await - } else { - store.key_new(state.id(), &location).await - }?; - - let data: MethodData = MethodData::new_b58(public.as_ref()); - let method: TinyMethod = TinyMethod::new(location, data, None); - - // Generate a new DID URL from the public key - let document: IotaDID = if let Some(network) = network { - IotaDID::new_with_network(public.as_ref(), network)? - } else { - IotaDID::new(public.as_ref())? - }; - - let method_fragment = Fragment::new(method.location().fragment()); - - Ok(Some(vec![ - Event::new(EventData::IdentityCreated(document)), - Event::new(EventData::MethodCreated(MethodScope::VerificationMethod, method)), - Event::new(EventData::MethodAttached( - method_fragment, - vec![MethodScope::Authentication], - )), - ])) - } Self::CreateMethod { type_, scope, fragment, method_secret, } => { - // The state must be initialized - ensure!(state.did().is_some(), UpdateError::DocumentNotFound); - let location: KeyLocation = state.key_location(type_, fragment)?; // The key location must not be an authentication location @@ -148,7 +171,7 @@ impl Command { // The key location must be available // TODO: config: strict ensure!( - !store.key_exists(state.id(), &location).await?, + !store.key_exists(did, &location).await?, UpdateError::DuplicateKeyLocation(location) ); @@ -159,9 +182,9 @@ impl Command { ); let public: PublicKey = if let Some(method_private_key) = method_secret { - insert_method_secret(store, state.id(), &location, type_, method_private_key).await + insert_method_secret(store, did, &location, type_, method_private_key).await } else { - store.key_new(state.id(), &location).await + store.key_new(did, &location).await }?; let data: MethodData = MethodData::new_b58(public.as_ref()); @@ -170,9 +193,6 @@ impl Command { Ok(Some(vec![Event::new(EventData::MethodCreated(scope, method))])) } Self::DeleteMethod { fragment } => { - // The state must be initialized - ensure!(state.did().is_some(), UpdateError::DocumentNotFound); - let fragment: Fragment = Fragment::new(fragment); // The fragment must not be an authentication location @@ -187,9 +207,6 @@ impl Command { Ok(Some(vec![Event::new(EventData::MethodDeleted(fragment))])) } Self::AttachMethod { fragment, scopes } => { - // The state must be initialized - ensure!(state.did().is_some(), UpdateError::DocumentNotFound); - let fragment: Fragment = Fragment::new(fragment); // The fragment must not be an authentication location @@ -204,9 +221,6 @@ impl Command { Ok(Some(vec![Event::new(EventData::MethodAttached(fragment, scopes))])) } Self::DetachMethod { fragment, scopes } => { - // The state must be initialized - ensure!(state.did().is_some(), UpdateError::DocumentNotFound); - let fragment: Fragment = Fragment::new(fragment); // The fragment must not be an authentication location @@ -226,9 +240,6 @@ impl Command { endpoint, properties, } => { - // The state must be initialized - ensure!(state.did().is_some(), UpdateError::DocumentNotFound); - // The service must not exist ensure!( !state.services().contains(&fragment), @@ -240,9 +251,6 @@ impl Command { Ok(Some(vec![Event::new(EventData::ServiceCreated(service))])) } Self::DeleteService { fragment } => { - // The state must be initialized - ensure!(state.did().is_some(), UpdateError::DocumentNotFound); - let fragment: Fragment = Fragment::new(fragment); // The service must exist @@ -256,7 +264,7 @@ impl Command { async fn insert_method_secret( store: &dyn Storage, - identity_id: IdentityId, + did: &IotaDID, location: &KeyLocation, method_type: MethodType, method_secret: MethodSecret, @@ -279,7 +287,7 @@ async fn insert_method_secret( ) ); - store.key_insert(identity_id, location, private_key).await + store.key_insert(did, location, private_key).await } MethodSecret::MerkleKeyCollection(_) => { ensure!( @@ -333,7 +341,7 @@ AttachMethod { @default scopes Vec, }); -impl<'account, 'key, K: IdentityKey> AttachMethodBuilder<'account, 'key, K> { +impl<'account> AttachMethodBuilder<'account> { pub fn scope(mut self, value: MethodScope) -> Self { self.scopes.get_or_insert_with(Default::default).push(value); self @@ -351,7 +359,7 @@ DetachMethod { @default scopes Vec, }); -impl<'account, 'key, K: IdentityKey> DetachMethodBuilder<'account, 'key, K> { +impl<'account> DetachMethodBuilder<'account> { pub fn scope(mut self, value: MethodScope) -> Self { self.scopes.get_or_insert_with(Default::default).push(value); self diff --git a/identity-account/src/identity/did_lease.rs b/identity-account/src/identity/did_lease.rs new file mode 100644 index 0000000000..81d8ad2399 --- /dev/null +++ b/identity-account/src/identity/did_lease.rs @@ -0,0 +1,40 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +/// A type that represents the permission to modify an identity. +/// +/// Holds an `AtomicBool` that is set to `false` on drop, signifying +/// the release of the lease. +#[derive(Debug, Clone)] +pub struct DIDLease(Arc); + +impl DIDLease { + pub fn new() -> Self { + Self(Arc::new(AtomicBool::new(true))) + } + + pub fn store(&self, value: bool) { + self.0.store(value, Ordering::SeqCst); + } + + pub fn load(&self) -> bool { + self.0.load(Ordering::SeqCst) + } +} + +impl Drop for DIDLease { + fn drop(&mut self) { + self.store(false); + } +} + +impl Default for DIDLease { + fn default() -> Self { + Self::new() + } +} diff --git a/identity-account/src/identity/identity_id.rs b/identity-account/src/identity/identity_id.rs deleted file mode 100644 index 54a855e6a0..0000000000 --- a/identity-account/src/identity/identity_id.rs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use core::convert::TryInto; -use core::fmt::Debug; -use core::fmt::Display; -use core::fmt::Formatter; -use core::fmt::Result as FmtResult; - -use crate::error::Error; -use crate::error::Result; - -/// A 32-bit identifier for stored Identities. -#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] -#[serde(from = "u32", into = "u32")] -#[repr(transparent)] -pub struct IdentityId([u8; 4]); - -impl IdentityId { - const ZERO: Self = Self::from_u32(0); - - /// Creates a new identity id from a slice of bytes. - /// - /// # Errors - /// - /// Fails if the given bytes are not a valid id. - pub fn from_slice(slice: &[u8]) -> Result { - slice - .try_into() - .map_err(|_| Error::IdentityIdInvalid) - .map(Self::from_bytes) - } - - /// Creates a new identity id from an array of bytes. - pub const fn from_bytes(bytes: [u8; 4]) -> Self { - Self(bytes) - } - - /// Creates a new identity id from a 32-bit integer. - pub const fn from_u32(value: u32) -> Self { - Self(value.to_be_bytes()) - } - - /// Returns the identity id as a slice of bytes. - pub const fn as_bytes(&self) -> &[u8] { - &self.0 - } - - /// Returns the identity id as an array of bytes. - pub const fn to_bytes(self) -> [u8; 4] { - self.0 - } - - /// Returns the identity id as a 32-bit integer. - pub const fn to_u32(self) -> u32 { - u32::from_be_bytes(self.0) - } - - /// Returns the next identity id in the sequence. - /// - /// # Errors - /// - /// Fails if the current id is the maximum supported value. - pub fn try_next(self) -> Result { - self - .to_u32() - .checked_add(1) - .map(Self::from_u32) - .ok_or(Error::IdentityIdOverflow) - } -} - -impl Debug for IdentityId { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - f.write_fmt(format_args!("IdentityId({:#010x})", self.to_u32())) - } -} - -impl Display for IdentityId { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - f.write_fmt(format_args!("{:#010x}", self.to_u32())) - } -} - -impl Default for IdentityId { - fn default() -> Self { - Self::ZERO - } -} - -impl From for IdentityId { - fn from(other: u32) -> Self { - Self::from_u32(other) - } -} - -impl From for u32 { - fn from(other: IdentityId) -> Self { - other.to_u32() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_roundtrip() { - let id: IdentityId = IdentityId::from_u32(123); - - assert_eq!(id.to_u32(), 123); - assert_eq!(IdentityId::from_bytes(id.to_bytes()), id); - assert_eq!(IdentityId::from_slice(id.as_bytes()).unwrap(), id); - } - - #[test] - fn test_from_slice() { - assert!(IdentityId::from_slice(&[]).is_err()); - assert!(IdentityId::from_slice(&[0x0]).is_err()); - assert!(IdentityId::from_slice(&[0x0; 3]).is_err()); - assert!(IdentityId::from_slice(&[0x0; 5]).is_err()); - assert!(IdentityId::from_slice(&[0x0; 4]).is_ok()); - } -} diff --git a/identity-account/src/identity/identity_index.rs b/identity-account/src/identity/identity_index.rs deleted file mode 100644 index bcc5299a9a..0000000000 --- a/identity-account/src/identity/identity_index.rs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::sync::Arc; - -use hashbrown::hash_map::Entry; -use hashbrown::HashMap; -use identity_iota::did::IotaDID; -use tokio::sync::RwLock; - -use crate::error::Error; -use crate::error::Result; -use crate::identity::IdentityId; -use crate::identity::IdentityKey; -use crate::identity::IdentityTag; - -pub(crate) type IdentityLock = Arc>; - -/// An mapping between [IdentityTag]s and [IdentityId]s. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct IdentityIndex { - data: HashMap, - #[serde(skip)] - locks: HashMap, -} - -impl IdentityIndex { - /// Creates a new `IdentityIndex`. - pub fn new() -> Self { - Self { - data: HashMap::new(), - locks: HashMap::new(), - } - } - - /// Returns the next IdentityId in the sequence. - /// - /// # Errors - /// - /// Fails if the current id is the maximum supported value. - pub fn try_next_id(&self) -> Result { - self.data.values().max().copied().unwrap_or_default().try_next() - } - - /// Returns a list of all tags in the index. - pub fn tags(&self) -> Vec { - self.data.keys().cloned().collect() - } - - /// Returns the id of the identity matching the given `key`. - pub fn get(&self, key: &K) -> Option { - key.scan(self.data.iter()) - } - - /// Returns the id of the identity matching the given `key` wrapped in a lock. - /// - /// Should be used to synchronize write access to the given `key`. - pub fn get_lock(&mut self, key: K) -> Option { - if let Some(identity_id) = key.scan(self.data.iter()) { - match self.locks.entry(identity_id) { - Entry::Occupied(lock) => Some(Arc::clone(lock.get())), - Entry::Vacant(entry) => { - let lock = entry.insert(Arc::new(RwLock::new(identity_id))); - Some(Arc::clone(lock)) - } - } - } else { - None - } - } - - /// Adds a new unnamed identity to the index. - pub fn set(&mut self, id: IdentityId, did: &IotaDID) -> Result<()> { - self.insert(id, IdentityTag::new(did.method_id().into())) - } - - /// Adds a new named identity to the index. - pub fn set_named(&mut self, id: IdentityId, did: &IotaDID, name: String) -> Result<()> { - self.insert(id, IdentityTag::named(did.method_id().into(), name)) - } - - /// Removes the identity specified by `key` from the index. - pub fn del(&mut self, key: K) -> Result<(IdentityTag, IdentityId)> { - let removed_id = self - .data - .drain_filter(|tag, id| key.equals(tag, *id)) - .next() - .ok_or(Error::IdentityNotFound)?; - - self.locks.remove(&removed_id.1); - - Ok(removed_id) - } - - fn insert(&mut self, id: IdentityId, tag: IdentityTag) -> Result<()> { - match self.data.entry(tag) { - Entry::Occupied(_) => Err(Error::IdentityAlreadyExists), - Entry::Vacant(entry) => { - entry.insert(id); - Ok(()) - } - } - } -} - -impl Default for IdentityIndex { - fn default() -> Self { - Self::new() - } -} - -impl PartialEq for IdentityIndex { - fn eq(&self, other: &Self) -> bool { - self.data == other.data - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_basics() { - let mut index: IdentityIndex = IdentityIndex::new(); - assert!(index.tags().is_empty()); - - let target1: IotaDID = format!("did:iota:{}", IotaDID::encode_key(b"123")).parse().unwrap(); - let target2: IotaDID = format!("did:iota:{}", IotaDID::encode_key(b"456")).parse().unwrap(); - let target3: IotaDID = format!("did:iota:{}", IotaDID::encode_key(b"789")).parse().unwrap(); - - index.set(1.into(), &target1).unwrap(); - index.set(2.into(), &target2).unwrap(); - index.set(3.into(), &target3).unwrap(); - - assert_eq!(index.tags().len(), 3); - - assert_eq!(index.get(&target1).unwrap().to_u32(), 1); - assert_eq!(index.get(&target2).unwrap().to_u32(), 2); - assert_eq!(index.get(&target3).unwrap().to_u32(), 3); - - assert_eq!(index.del(&target1).unwrap().1.to_u32(), 1); - assert_eq!(index.del(&target2).unwrap().1.to_u32(), 2); - - assert_eq!(index.tags().len(), 1); - } - - #[test] - fn test_next_id() { - let mut index: IdentityIndex = IdentityIndex::new(); - assert_eq!(index.try_next_id().unwrap().to_u32(), 1); - - index.insert(1.into(), IdentityTag::new("foo-1".into())).unwrap(); - assert_eq!(index.try_next_id().unwrap().to_u32(), 2); - - index.insert(2.into(), IdentityTag::new("foo-2".into())).unwrap(); - assert_eq!(index.try_next_id().unwrap().to_u32(), 3); - - let target: IdentityId = IdentityId::from(1); - let (tag, id): (IdentityTag, IdentityId) = index.del(target).unwrap(); - assert_eq!(tag.name(), None); - assert_eq!(tag.method_id(), "foo-1"); - assert_eq!(id, target); - - assert_eq!(index.try_next_id().unwrap().to_u32(), 3); - } -} diff --git a/identity-account/src/identity/identity_key.rs b/identity-account/src/identity/identity_key.rs deleted file mode 100644 index dfbfe1a1d2..0000000000 --- a/identity-account/src/identity/identity_key.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::did::IotaDID; - -use crate::identity::IdentityId; -use crate::identity::IdentityTag; - -type Item<'a> = (&'a IdentityTag, &'a IdentityId); - -pub trait IdentityKey { - fn equals(&self, tag: &IdentityTag, id: IdentityId) -> bool; - - fn scan<'a, I: Iterator>>(&self, mut iter: I) -> Option { - iter.find(|(tag, id)| self.equals(tag, **id)).map(|(_, id)| *id) - } -} - -impl<'a, T> IdentityKey for &'a T -where - T: IdentityKey + ?Sized, -{ - fn equals(&self, tag: &IdentityTag, id: IdentityId) -> bool { - (**self).equals(tag, id) - } -} - -impl IdentityKey for IotaDID { - fn equals(&self, tag: &IdentityTag, _: IdentityId) -> bool { - tag.method_id() == self.method_id() - } -} - -impl IdentityKey for str { - fn equals(&self, tag: &IdentityTag, id: IdentityId) -> bool { - tag.fullname(id).as_ref() == self - } -} - -impl IdentityKey for String { - fn equals(&self, tag: &IdentityTag, id: IdentityId) -> bool { - self[..].equals(tag, id) - } -} - -impl IdentityKey for IdentityId { - fn equals(&self, _: &IdentityTag, id: IdentityId) -> bool { - id == *self - } - - fn scan<'a, I: Iterator>>(&self, _: I) -> Option { - Some(*self) - } -} diff --git a/identity-account/src/identity/identity_name.rs b/identity-account/src/identity/identity_name.rs deleted file mode 100644 index 064359f1fb..0000000000 --- a/identity-account/src/identity/identity_name.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::borrow::Cow; - -use crate::identity::IdentityId; - -/// Represents an Identity name, whether explicitly set or default. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(untagged)] -pub enum IdentityName { - Default, - Literal(String), -} - -impl IdentityName { - /// Returns the user-assigned name, if any. - pub fn as_opt(&self) -> Option<&str> { - match self { - Self::Default => None, - Self::Literal(ref inner) => Some(inner), - } - } - - /// Returns the name of the identity, whether user-assigned or default. - pub fn as_str(&self, id: IdentityId) -> Cow<'_, str> { - match self { - Self::Default => Cow::Owned(default_identifier(id)), - Self::Literal(ref inner) => Cow::Borrowed(inner), - } - } -} - -fn default_identifier(id: IdentityId) -> String { - format!("Identity {}", id.to_u32()) -} diff --git a/identity-account/src/identity/identity_create.rs b/identity-account/src/identity/identity_setup.rs similarity index 81% rename from identity-account/src/identity/identity_create.rs rename to identity-account/src/identity/identity_setup.rs index 54920a59aa..94a7c0ab8b 100644 --- a/identity-account/src/identity/identity_create.rs +++ b/identity-account/src/identity/identity_setup.rs @@ -11,19 +11,17 @@ use crate::Result; /// Configuration used to create a new Identity. #[derive(Clone, Debug)] -pub struct IdentityCreate { +pub struct IdentitySetup { pub(crate) key_type: KeyType, - pub(crate) name: Option, pub(crate) network: Option, pub(crate) method_secret: Option, } -impl IdentityCreate { +impl IdentitySetup { /// Creates a new `IdentityCreate` instance. pub const fn new() -> Self { Self { key_type: KeyType::Ed25519, - name: None, network: None, method_secret: None, } @@ -36,16 +34,6 @@ impl IdentityCreate { self } - /// Sets the name of the Identity. - #[must_use] - pub fn name(mut self, value: T) -> Self - where - T: Into, - { - self.name = Some(value.into()); - self - } - /// Sets the IOTA Tangle network of the Identity DID. #[allow(clippy::double_must_use)] #[must_use] @@ -67,7 +55,7 @@ impl IdentityCreate { } } -impl Default for IdentityCreate { +impl Default for IdentitySetup { fn default() -> Self { Self::new() } diff --git a/identity-account/src/identity/identity_snapshot.rs b/identity-account/src/identity/identity_snapshot.rs index 99f525f4c5..dcc55a14c4 100644 --- a/identity-account/src/identity/identity_snapshot.rs +++ b/identity-account/src/identity/identity_snapshot.rs @@ -1,7 +1,6 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::identity::IdentityId; use crate::identity::IdentityState; use crate::types::Generation; @@ -21,11 +20,6 @@ impl IdentitySnapshot { } } - /// Returns the identifier for this identity. - pub fn id(&self) -> IdentityId { - self.identity.id() - } - /// Returns the sequence index of the snapshot. pub fn sequence(&self) -> Generation { self.sequence diff --git a/identity-account/src/identity/identity_state.rs b/identity-account/src/identity/identity_state.rs index 3523c00ae3..441b68a8c1 100644 --- a/identity-account/src/identity/identity_state.rs +++ b/identity-account/src/identity/identity_state.rs @@ -31,7 +31,6 @@ use crate::crypto::RemoteKey; use crate::crypto::RemoteSign; use crate::error::Error; use crate::error::Result; -use crate::identity::IdentityId; use crate::storage::Storage; use crate::types::Generation; use crate::types::KeyLocation; @@ -39,14 +38,13 @@ use crate::types::KeyLocation; type Properties = VerifiableProperties; type BaseDocument = CoreDocument; -pub type RemoteEd25519<'a, T> = JcsEd25519>; +pub type RemoteEd25519<'a> = JcsEd25519>; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct IdentityState { // =========== // // Chain State // // =========== // - id: IdentityId, integration_generation: Generation, diff_generation: Generation, #[serde(default = "MessageId::null", skip_serializing_if = "MessageId::is_null")] @@ -76,9 +74,8 @@ pub struct IdentityState { } impl IdentityState { - pub fn new(id: IdentityId) -> Self { + pub fn new() -> Self { Self { - id, integration_generation: Generation::new(), diff_generation: Generation::new(), this_message_id: MessageId::null(), @@ -98,11 +95,6 @@ impl IdentityState { // Internal State // =========================================================================== - /// Returns the identifier for this identity. - pub fn id(&self) -> IdentityId { - self.id - } - /// Returns the current generation of the identity integration chain. pub fn integration_generation(&self) -> Generation { self.integration_generation @@ -322,13 +314,18 @@ impl IdentityState { Ok(document) } - pub async fn sign_data(&self, store: &T, location: &KeyLocation, target: &mut U) -> Result<()> + pub async fn sign_data( + &self, + did: &IotaDID, + store: &dyn Storage, + location: &KeyLocation, + target: &mut U, + ) -> Result<()> where - T: Storage, U: Serialize + SetSignature, { // Create a private key suitable for identity_core::crypto - let private: RemoteKey<'_, T> = RemoteKey::new(self.id, location, store); + let private: RemoteKey<'_> = RemoteKey::new(did, location, store); // Create the Verification Method identifier let fragment: &str = location.fragment.identifier(); @@ -347,6 +344,12 @@ impl IdentityState { } } +impl Default for IdentityState { + fn default() -> Self { + Self::new() + } +} + // ============================================================================= // TinyMethodRef // ============================================================================= diff --git a/identity-account/src/identity/identity_tag.rs b/identity-account/src/identity/identity_tag.rs deleted file mode 100644 index a2106eb742..0000000000 --- a/identity-account/src/identity/identity_tag.rs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use core::fmt::Debug; -use core::fmt::Formatter; -use core::fmt::Result as FmtResult; -use core::hash::Hash; -use core::hash::Hasher; -use identity_core::convert::FromJson; -use identity_core::convert::ToJson; -use identity_core::utils::decode_b64; -use identity_core::utils::encode_b64; -use serde::de; -use serde::ser; -use serde::Deserialize; -use serde::Serialize; -use std::borrow::Cow; - -use crate::error::Result; -use crate::identity::IdentityId; -use crate::identity::IdentityName; - -/// Information used to identify an identity. -#[derive(Clone)] -pub struct IdentityTag { - name: IdentityName, - method_id: String, -} - -impl IdentityTag { - /// Creates a new IdentityTag with a default name. - pub fn new(method_id: String) -> Self { - Self { - name: IdentityName::Default, - method_id, - } - } - - /// Creates a new IdentityTag with an explicit name. - pub fn named(method_id: String, name: String) -> Self { - Self { - name: IdentityName::Literal(name), - method_id, - } - } - - /// Returns the user-assigned name of the identity. - pub fn name(&self) -> Option<&str> { - self.name.as_opt() - } - - /// Returns the name of the identity, whether user-assigned or default. - pub fn fullname(&self, id: IdentityId) -> Cow<'_, str> { - self.name.as_str(id) - } - - /// Returns the method id of the Identity DID Document. - pub fn method_id(&self) -> &str { - &self.method_id - } - - // Returns the identity tag as a base64-encoded JSON string. - fn encode(&self) -> Result { - let data: ProxySerialize<'_> = ProxySerialize { - method_id: &self.method_id, - name: &self.name, - }; - - let json: Vec = data.to_json_vec()?; - let base: String = encode_b64(&json); - - Ok(base) - } - - // Decodes an identity tag from a base64-encoded JSON string. - fn decode(string: &str) -> Result { - let json: Vec = decode_b64(string)?; - let data: ProxyDeserialize = ProxyDeserialize::from_json_slice(&json)?; - - Ok(Self { - method_id: data.method_id, - name: data.name, - }) - } -} - -impl Debug for IdentityTag { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - f.write_fmt(format_args!("IdentityTag({}, {:?})", self.method_id, self.name)) - } -} - -impl PartialEq for IdentityTag { - fn eq(&self, other: &Self) -> bool { - self.method_id.eq(&other.method_id) - } -} - -impl Eq for IdentityTag {} - -impl Hash for IdentityTag { - fn hash(&self, hasher: &mut H) { - self.method_id.hash(hasher); - } -} - -// ============================================================================= -// Serde -// ============================================================================= - -#[derive(Serialize)] -struct ProxySerialize<'a> { - #[serde(rename = "1")] - method_id: &'a str, - #[serde(rename = "2")] - name: &'a IdentityName, -} - -#[derive(Deserialize)] -struct ProxyDeserialize { - #[serde(rename = "1")] - method_id: String, - #[serde(rename = "2")] - name: IdentityName, -} - -impl Serialize for IdentityTag { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - match self.encode() { - Ok(data) => serializer.serialize_str(&data), - Err(error) => Err(ser::Error::custom(error)), - } - } -} - -impl<'de> Deserialize<'de> for IdentityTag { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - - impl<'de> de::Visitor<'de> for Visitor { - type Value = IdentityTag; - - fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult { - f.write_str("a base64-encoded string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - IdentityTag::decode(value).map_err(E::custom) - } - } - - deserializer.deserialize_str(Visitor) - } -} - -// ============================================================================= -// Tests -// ============================================================================= - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_compare() { - let a: IdentityTag = IdentityTag::new("abcde".into()); - let b: IdentityTag = IdentityTag::named("abcde".into(), "Foo".into()); - - assert_eq!(a, b); - assert_eq!(a.method_id(), b.method_id()); - assert_ne!(a.name(), b.name()); - } -} diff --git a/identity-account/src/identity/identity_updater.rs b/identity-account/src/identity/identity_updater.rs index e5e9f9f6fb..5c70ac18ac 100644 --- a/identity-account/src/identity/identity_updater.rs +++ b/identity-account/src/identity/identity_updater.rs @@ -3,18 +3,15 @@ use crate::account::Account; -use super::IdentityKey; - /// A struct created by the [`Account::update_identity`] method, that /// allows executing various updates on the identity it was created on. -#[derive(Debug, Clone)] -pub struct IdentityUpdater<'account, 'key, K: IdentityKey> { - pub(crate) account: &'account Account, - pub(crate) key: &'key K, +#[derive(Debug)] +pub struct IdentityUpdater<'account> { + pub(crate) account: &'account mut Account, } -impl<'account, 'key, K: IdentityKey> IdentityUpdater<'account, 'key, K> { - pub(crate) fn new(account: &'account Account, key: &'key K) -> Self { - Self { account, key } +impl<'account> IdentityUpdater<'account> { + pub(crate) fn new(account: &'account mut Account) -> Self { + Self { account } } } diff --git a/identity-account/src/identity/mod.rs b/identity-account/src/identity/mod.rs index 246933ce9e..3ed0a6556b 100644 --- a/identity-account/src/identity/mod.rs +++ b/identity-account/src/identity/mod.rs @@ -1,22 +1,14 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -mod identity_create; -mod identity_id; -mod identity_index; -mod identity_key; -mod identity_name; +mod did_lease; +mod identity_setup; mod identity_snapshot; mod identity_state; -mod identity_tag; mod identity_updater; -pub use self::identity_create::*; -pub use self::identity_id::*; -pub use self::identity_index::*; -pub use self::identity_key::*; -pub use self::identity_name::*; +pub use self::did_lease::*; +pub use self::identity_setup::*; pub use self::identity_snapshot::*; pub use self::identity_state::*; -pub use self::identity_tag::*; pub use self::identity_updater::*; diff --git a/identity-account/src/storage/memstore.rs b/identity-account/src/storage/memstore.rs index aacb09c9de..f0f292cfb0 100644 --- a/identity-account/src/storage/memstore.rs +++ b/identity-account/src/storage/memstore.rs @@ -8,6 +8,7 @@ use crypto::signatures::ed25519; use futures::stream; use futures::stream::BoxStream; use futures::StreamExt; +use hashbrown::hash_map::Entry; use hashbrown::HashMap; use identity_core::crypto::Ed25519; use identity_core::crypto::KeyPair; @@ -16,16 +17,17 @@ use identity_core::crypto::PrivateKey; use identity_core::crypto::PublicKey; use identity_core::crypto::Sign; use identity_did::verification::MethodType; +use identity_iota::did::IotaDID; use std::convert::TryFrom; use std::sync::RwLockReadGuard; use std::sync::RwLockWriteGuard; +use tokio::sync::Mutex; use zeroize::Zeroize; use crate::error::Error; use crate::error::Result; use crate::events::Commit; -use crate::identity::IdentityId; -use crate::identity::IdentityIndex; +use crate::identity::DIDLease; use crate::identity::IdentitySnapshot; use crate::storage::Storage; use crate::types::Generation; @@ -36,15 +38,15 @@ use crate::utils::Shared; type MemVault = HashMap; -type Events = HashMap>; -type States = HashMap; -type Vaults = HashMap; -type PublishedGenerations = HashMap; +type Events = HashMap>; +type States = HashMap; +type Vaults = HashMap; +type PublishedGenerations = HashMap; pub struct MemStore { expand: bool, - index: Shared, published_generations: Shared, + did_leases: Mutex>, events: Shared, states: Shared, vaults: Shared, @@ -54,8 +56,8 @@ impl MemStore { pub fn new() -> Self { Self { expand: false, - index: Shared::new(IdentityIndex::new()), published_generations: Shared::new(HashMap::new()), + did_leases: Mutex::new(HashMap::new()), events: Shared::new(HashMap::new()), states: Shared::new(HashMap::new()), vaults: Shared::new(HashMap::new()), @@ -70,10 +72,6 @@ impl MemStore { self.expand = value; } - pub fn index(&self) -> Result { - self.index.read().map(|data| data.clone()) - } - pub fn events(&self) -> Result { self.events.read().map(|data| data.clone()) } @@ -97,9 +95,29 @@ impl Storage for MemStore { Ok(()) } - async fn key_new(&self, id: IdentityId, location: &KeyLocation) -> Result { + async fn lease_did(&self, did: &IotaDID) -> Result { + let mut hmap = self.did_leases.lock().await; + + match hmap.entry(did.clone()) { + Entry::Occupied(entry) => { + if entry.get().load() { + Err(Error::IdentityInUse) + } else { + entry.get().store(true); + Ok(entry.get().clone()) + } + } + Entry::Vacant(entry) => { + let did_lease = DIDLease::new(); + entry.insert(did_lease.clone()); + Ok(did_lease) + } + } + } + + async fn key_new(&self, did: &IotaDID, location: &KeyLocation) -> Result { let mut vaults: RwLockWriteGuard<'_, _> = self.vaults.write()?; - let vault: &mut MemVault = vaults.entry(id).or_default(); + let vault: &mut MemVault = vaults.entry(did.clone()).or_default(); match location.method() { MethodType::Ed25519VerificationKey2018 => { @@ -116,9 +134,9 @@ impl Storage for MemStore { } } - async fn key_insert(&self, id: IdentityId, location: &KeyLocation, private_key: PrivateKey) -> Result { + async fn key_insert(&self, did: &IotaDID, location: &KeyLocation, private_key: PrivateKey) -> Result { let mut vaults: RwLockWriteGuard<'_, _> = self.vaults.write()?; - let vault: &mut MemVault = vaults.entry(id).or_default(); + let vault: &mut MemVault = vaults.entry(did.clone()).or_default(); match location.method() { MethodType::Ed25519VerificationKey2018 => { @@ -144,36 +162,36 @@ impl Storage for MemStore { } } - async fn key_exists(&self, id: IdentityId, location: &KeyLocation) -> Result { + async fn key_exists(&self, did: &IotaDID, location: &KeyLocation) -> Result { let vaults: RwLockReadGuard<'_, _> = self.vaults.read()?; - if let Some(vault) = vaults.get(&id) { + if let Some(vault) = vaults.get(did) { return Ok(vault.contains_key(location)); } Ok(false) } - async fn key_get(&self, id: IdentityId, location: &KeyLocation) -> Result { + async fn key_get(&self, did: &IotaDID, location: &KeyLocation) -> Result { let vaults: RwLockReadGuard<'_, _> = self.vaults.read()?; - let vault: &MemVault = vaults.get(&id).ok_or(Error::KeyVaultNotFound)?; + let vault: &MemVault = vaults.get(did).ok_or(Error::KeyVaultNotFound)?; let keypair: &KeyPair = vault.get(location).ok_or(Error::KeyPairNotFound)?; Ok(keypair.public().clone()) } - async fn key_del(&self, id: IdentityId, location: &KeyLocation) -> Result<()> { + async fn key_del(&self, did: &IotaDID, location: &KeyLocation) -> Result<()> { let mut vaults: RwLockWriteGuard<'_, _> = self.vaults.write()?; - let vault: &mut MemVault = vaults.get_mut(&id).ok_or(Error::KeyVaultNotFound)?; + let vault: &mut MemVault = vaults.get_mut(did).ok_or(Error::KeyVaultNotFound)?; vault.remove(location); Ok(()) } - async fn key_sign(&self, id: IdentityId, location: &KeyLocation, data: Vec) -> Result { + async fn key_sign(&self, did: &IotaDID, location: &KeyLocation, data: Vec) -> Result { let vaults: RwLockReadGuard<'_, _> = self.vaults.read()?; - let vault: &MemVault = vaults.get(&id).ok_or(Error::KeyVaultNotFound)?; + let vault: &MemVault = vaults.get(did).ok_or(Error::KeyVaultNotFound)?; let keypair: &KeyPair = vault.get(location).ok_or(Error::KeyPairNotFound)?; match location.method() { @@ -192,29 +210,19 @@ impl Storage for MemStore { } } - async fn index(&self) -> Result { - self.index.read().map(|index| index.clone()) - } - - async fn set_index(&self, index: &IdentityIndex) -> Result<()> { - *self.index.write()? = index.clone(); - - Ok(()) - } - - async fn snapshot(&self, id: IdentityId) -> Result> { - self.states.read().map(|states| states.get(&id).cloned()) + async fn snapshot(&self, did: &IotaDID) -> Result> { + self.states.read().map(|states| states.get(did).cloned()) } - async fn set_snapshot(&self, id: IdentityId, snapshot: &IdentitySnapshot) -> Result<()> { - self.states.write()?.insert(id, snapshot.clone()); + async fn set_snapshot(&self, did: &IotaDID, snapshot: &IdentitySnapshot) -> Result<()> { + self.states.write()?.insert(did.clone(), snapshot.clone()); Ok(()) } - async fn append(&self, id: IdentityId, commits: &[Commit]) -> Result<()> { + async fn append(&self, did: &IotaDID, commits: &[Commit]) -> Result<()> { let mut state: RwLockWriteGuard<'_, _> = self.events.write()?; - let queue: &mut Vec = state.entry(id).or_default(); + let queue: &mut Vec = state.entry(did.clone()).or_default(); for commit in commits { queue.push(commit.clone()); @@ -223,28 +231,28 @@ impl Storage for MemStore { Ok(()) } - async fn stream(&self, id: IdentityId, index: Generation) -> Result>> { + async fn stream(&self, did: &IotaDID, index: Generation) -> Result>> { let state: RwLockReadGuard<'_, _> = self.events.read()?; - let queue: Vec = state.get(&id).cloned().unwrap_or_default(); + let queue: Vec = state.get(did).cloned().unwrap_or_default(); let index: usize = index.to_u32() as usize; Ok(stream::iter(queue.into_iter().skip(index)).map(Ok).boxed()) } - async fn purge(&self, id: IdentityId) -> Result<()> { - let _ = self.events.write()?.remove(&id); - let _ = self.states.write()?.remove(&id); - let _ = self.vaults.write()?.remove(&id); + async fn purge(&self, did: &IotaDID) -> Result<()> { + let _ = self.events.write()?.remove(did); + let _ = self.states.write()?.remove(did); + let _ = self.vaults.write()?.remove(did); Ok(()) } - async fn published_generation(&self, id: IdentityId) -> Result> { - Ok(self.published_generations.read()?.get(&id).copied()) + async fn published_generation(&self, did: &IotaDID) -> Result> { + Ok(self.published_generations.read()?.get(did).copied()) } - async fn set_published_generation(&self, id: IdentityId, index: Generation) -> Result<()> { - self.published_generations.write()?.insert(id, index); + async fn set_published_generation(&self, did: &IotaDID, index: Generation) -> Result<()> { + self.published_generations.write()?.insert(did.clone(), index); Ok(()) } } @@ -253,7 +261,6 @@ impl Debug for MemStore { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { if self.expand { f.debug_struct("MemStore") - .field("index", &self.index) .field("events", &self.events) .field("states", &self.states) .field("vaults", &self.vaults) diff --git a/identity-account/src/storage/stronghold.rs b/identity-account/src/storage/stronghold.rs index 7f2a720a52..2148bb6f06 100644 --- a/identity-account/src/storage/stronghold.rs +++ b/identity-account/src/storage/stronghold.rs @@ -8,26 +8,29 @@ use futures::stream; use futures::stream::BoxStream; use futures::StreamExt; use futures::TryStreamExt; +use hashbrown::hash_map::Entry; +use hashbrown::HashMap; use hashbrown::HashSet; use identity_core::convert::FromJson; use identity_core::convert::ToJson; use identity_core::crypto::PrivateKey; use identity_core::crypto::PublicKey; use identity_did::verification::MethodType; +use identity_iota::did::IotaDID; use iota_stronghold::Location; use iota_stronghold::SLIP10DeriveInput; use std::convert::TryFrom; use std::io; use std::path::Path; use std::sync::Arc; +use tokio::sync::Mutex; use crate::error::Error; use crate::error::Result; use crate::events::Commit; use crate::events::Event; use crate::events::EventData; -use crate::identity::IdentityId; -use crate::identity::IdentityIndex; +use crate::identity::DIDLease; use crate::identity::IdentitySnapshot; use crate::storage::Storage; use crate::stronghold::default_hint; @@ -40,9 +43,6 @@ use crate::types::Signature; use crate::utils::derive_encryption_key; use crate::utils::EncryptionKey; -// name of the metadata store -const META: &str = "$meta"; - // event concurrency limit const ECL: usize = 8; @@ -51,6 +51,7 @@ const ECL: usize = 8; #[derive(Debug)] pub struct Stronghold { + did_leases: Mutex>, snapshot: Arc, } @@ -67,6 +68,7 @@ impl Stronghold { } Ok(Self { + did_leases: Mutex::new(HashMap::new()), snapshot: Arc::new(snapshot), }) } @@ -75,8 +77,8 @@ impl Stronghold { self.snapshot.store(name, &[]) } - fn vault(&self, id: IdentityId) -> Vault<'_> { - self.snapshot.vault(&fmt_id(id), &[]) + fn vault(&self, id: &IotaDID) -> Vault<'_> { + self.snapshot.vault(&fmt_did(id), &[]) } } @@ -90,8 +92,28 @@ impl Storage for Stronghold { self.snapshot.save().await } - async fn key_new(&self, id: IdentityId, location: &KeyLocation) -> Result { - let vault: Vault<'_> = self.vault(id); + async fn lease_did(&self, did: &IotaDID) -> Result { + let mut hmap = self.did_leases.lock().await; + + match hmap.entry(did.clone()) { + Entry::Occupied(entry) => { + if entry.get().load() { + Err(Error::IdentityInUse) + } else { + entry.get().store(true); + Ok(entry.get().clone()) + } + } + Entry::Vacant(entry) => { + let did_lease = DIDLease::new(); + entry.insert(did_lease.clone()); + Ok(did_lease) + } + } + } + + async fn key_new(&self, did: &IotaDID, location: &KeyLocation) -> Result { + let vault: Vault<'_> = self.vault(did); let public: PublicKey = match location.method() { MethodType::Ed25519VerificationKey2018 => generate_ed25519(&vault, location).await?, @@ -101,8 +123,8 @@ impl Storage for Stronghold { Ok(public) } - async fn key_insert(&self, id: IdentityId, location: &KeyLocation, private_key: PrivateKey) -> Result { - let vault = self.vault(id); + async fn key_insert(&self, did: &IotaDID, location: &KeyLocation, private_key: PrivateKey) -> Result { + let vault = self.vault(did); vault .insert(location_skey(location), private_key.as_ref(), default_hint(), &[]) @@ -114,8 +136,8 @@ impl Storage for Stronghold { } } - async fn key_get(&self, id: IdentityId, location: &KeyLocation) -> Result { - let vault: Vault<'_> = self.vault(id); + async fn key_get(&self, did: &IotaDID, location: &KeyLocation) -> Result { + let vault: Vault<'_> = self.vault(did); match location.method() { MethodType::Ed25519VerificationKey2018 => retrieve_ed25519(&vault, location).await, @@ -123,8 +145,8 @@ impl Storage for Stronghold { } } - async fn key_del(&self, id: IdentityId, location: &KeyLocation) -> Result<()> { - let vault: Vault<'_> = self.vault(id); + async fn key_del(&self, did: &IotaDID, location: &KeyLocation) -> Result<()> { + let vault: Vault<'_> = self.vault(did); match location.method() { MethodType::Ed25519VerificationKey2018 => { @@ -139,8 +161,8 @@ impl Storage for Stronghold { Ok(()) } - async fn key_sign(&self, id: IdentityId, location: &KeyLocation, data: Vec) -> Result { - let vault: Vault<'_> = self.vault(id); + async fn key_sign(&self, did: &IotaDID, location: &KeyLocation, data: Vec) -> Result { + let vault: Vault<'_> = self.vault(did); match location.method() { MethodType::Ed25519VerificationKey2018 => sign_ed25519(&vault, data, location).await, @@ -148,8 +170,8 @@ impl Storage for Stronghold { } } - async fn key_exists(&self, id: IdentityId, location: &KeyLocation) -> Result { - let vault: Vault<'_> = self.vault(id); + async fn key_exists(&self, did: &IotaDID, location: &KeyLocation) -> Result { + let vault: Vault<'_> = self.vault(did); match location.method() { MethodType::Ed25519VerificationKey2018 => vault.exists(location_skey(location)).await, @@ -157,38 +179,9 @@ impl Storage for Stronghold { } } - async fn index(&self) -> Result { - // Load the metadata actor - let store: Store<'_> = self.store(META); - - // Read the index from the snapshot - let data: Vec = store.get(location_index()).await?; - - // No index data (new snapshot) - if data.is_empty() { - return Ok(IdentityIndex::new()); - } - - // Deserialize and return - Ok(IdentityIndex::from_json_slice(&data)?) - } - - async fn set_index(&self, index: &IdentityIndex) -> Result<()> { - // Load the metadata actor - let store: Store<'_> = self.store(META); - - // Serialize the index - let json: Vec = index.to_json_vec()?; - - // Write the index to the snapshot - store.set(location_index(), json, None).await?; - - Ok(()) - } - - async fn snapshot(&self, id: IdentityId) -> Result> { + async fn snapshot(&self, did: &IotaDID) -> Result> { // Load the chain-specific store - let store: Store<'_> = self.store(&fmt_id(id)); + let store: Store<'_> = self.store(&fmt_did(did)); // Read the event snapshot from the stronghold snapshot let data: Vec = store.get(location_snapshot()).await?; @@ -202,9 +195,9 @@ impl Storage for Stronghold { Ok(Some(IdentitySnapshot::from_json_slice(&data)?)) } - async fn set_snapshot(&self, id: IdentityId, snapshot: &IdentitySnapshot) -> Result<()> { + async fn set_snapshot(&self, did: &IotaDID, snapshot: &IdentitySnapshot) -> Result<()> { // Load the chain-specific store - let store: Store<'_> = self.store(&fmt_id(id)); + let store: Store<'_> = self.store(&fmt_did(did)); // Serialize the state snapshot let json: Vec = snapshot.to_json_vec()?; @@ -215,12 +208,12 @@ impl Storage for Stronghold { Ok(()) } - async fn append(&self, id: IdentityId, commits: &[Commit]) -> Result<()> { + async fn append(&self, did: &IotaDID, commits: &[Commit]) -> Result<()> { fn encode(commit: &Commit) -> Result<(Generation, Vec)> { Ok((commit.sequence(), commit.event().to_json_vec()?)) } - let store: Store<'_> = self.store(&fmt_id(id)); + let store: Store<'_> = self.store(&fmt_did(did)); let future: _ = stream::iter(commits.iter().map(encode)) .into_stream() @@ -233,10 +226,12 @@ impl Storage for Stronghold { Ok(()) } - async fn stream(&self, id: IdentityId, index: Generation) -> Result>> { - let name: String = fmt_id(id); + async fn stream(&self, did: &IotaDID, index: Generation) -> Result>> { + let name: String = fmt_did(did); let range: RangeFrom = (index.to_u32() + 1)..; + let did = did.clone(); + let stream: BoxStream<'_, Result> = stream::iter(range) .map(Generation::from) .map(Ok) @@ -258,14 +253,17 @@ impl Storage for Stronghold { // ================================ // Parse the event // ================================ - .try_filter_map(move |(index, json)| async move { - if json.is_empty() { - Err(Error::EventNotFound) - } else { - let event: Event = Event::from_json_slice(&json)?; - let commit: Commit = Commit::new(id, index, event); - - Ok(Some(commit)) + .try_filter_map(move |(index, json)| { + let did = did.clone(); + async move { + if json.is_empty() { + Err(Error::EventNotFound) + } else { + let event: Event = Event::from_json_slice(&json)?; + let commit: Commit = Commit::new(did, index, event); + + Ok(Some(commit)) + } } }) // ================================ @@ -284,7 +282,7 @@ impl Storage for Stronghold { Ok(stream) } - async fn purge(&self, id: IdentityId) -> Result<()> { + async fn purge(&self, did: &IotaDID) -> Result<()> { type PurgeSet = (Generation, HashSet); async fn fold(mut output: PurgeSet, commit: Commit) -> Result { @@ -299,12 +297,12 @@ impl Storage for Stronghold { } // Load the chain-specific store/vault - let store: Store<'_> = self.store(&fmt_id(id)); - let vault: Vault<'_> = self.vault(id); + let store: Store<'_> = self.store(&fmt_did(did)); + let vault: Vault<'_> = self.vault(did); // Scan the event stream and collect a set of all key locations let output: (Generation, HashSet) = self - .stream(id, Generation::new()) + .stream(did, Generation::new()) .await? .try_fold((Generation::new(), HashSet::new()), fold) .await?; @@ -333,8 +331,8 @@ impl Storage for Stronghold { Ok(()) } - async fn published_generation(&self, id: IdentityId) -> Result> { - let store: Store<'_> = self.store(&fmt_id(id)); + async fn published_generation(&self, did: &IotaDID) -> Result> { + let store: Store<'_> = self.store(&fmt_did(did)); let bytes = store.get(location_published_generation()).await?; @@ -357,8 +355,8 @@ impl Storage for Stronghold { Ok(Some(gen)) } - async fn set_published_generation(&self, id: IdentityId, index: Generation) -> Result<()> { - let store: Store<'_> = self.store(&fmt_id(id)); + async fn set_published_generation(&self, did: &IotaDID, index: Generation) -> Result<()> { + let store: Store<'_> = self.store(&fmt_did(did)); store .set(location_published_generation(), index.to_u32().to_le_bytes(), None) @@ -400,10 +398,6 @@ async fn sign_ed25519(vault: &Vault<'_>, payload: Vec, location: &KeyLocatio Ok(Signature::new(public_key, signature.into())) } -fn location_index() -> Location { - Location::generic("$index", Vec::new()) -} - fn location_snapshot() -> Location { Location::generic("$snapshot", Vec::new()) } @@ -435,6 +429,6 @@ fn fmt_key(prefix: &str, location: &KeyLocation) -> Vec { .into_bytes() } -fn fmt_id(id: IdentityId) -> String { - format!("$identity:{}", id) +fn fmt_did(did: &IotaDID) -> String { + format!("$identity:{}", did.authority()) } diff --git a/identity-account/src/storage/traits.rs b/identity-account/src/storage/traits.rs index ffceb76340..abb828dbe2 100644 --- a/identity-account/src/storage/traits.rs +++ b/identity-account/src/storage/traits.rs @@ -6,11 +6,11 @@ use futures::stream::BoxStream; use futures::TryStreamExt; use identity_core::crypto::PrivateKey; use identity_core::crypto::PublicKey; +use identity_iota::did::IotaDID; use crate::error::Result; use crate::events::Commit; -use crate::identity::IdentityId; -use crate::identity::IdentityIndex; +use crate::identity::DIDLease; use crate::identity::IdentitySnapshot; use crate::types::Generation; use crate::types::KeyLocation; @@ -28,59 +28,58 @@ pub trait Storage: Debug + Send + Sync + 'static { /// Write any unsaved changes to disk. async fn flush_changes(&self) -> Result<()>; + /// Attempt to obtain the exclusive permission to modify the given did. + /// The caller is expected to make no more modifications after the lease has been dropped. + /// Returns an [`IdentityInUse`][crate::Error::IdentityInUse] error if already leased. + async fn lease_did(&self, did: &IotaDID) -> Result; + /// Creates a new keypair at the specified `location` - async fn key_new(&self, id: IdentityId, location: &KeyLocation) -> Result; + async fn key_new(&self, did: &IotaDID, location: &KeyLocation) -> Result; /// Inserts a private key at the specified `location`. - async fn key_insert(&self, id: IdentityId, location: &KeyLocation, private_key: PrivateKey) -> Result; + async fn key_insert(&self, did: &IotaDID, location: &KeyLocation, private_key: PrivateKey) -> Result; /// Retrieves the public key at the specified `location`. - async fn key_get(&self, id: IdentityId, location: &KeyLocation) -> Result; + async fn key_get(&self, did: &IotaDID, location: &KeyLocation) -> Result; /// Deletes the keypair specified by `location`. - async fn key_del(&self, id: IdentityId, location: &KeyLocation) -> Result<()>; + async fn key_del(&self, did: &IotaDID, location: &KeyLocation) -> Result<()>; /// Signs `data` with the private key at the specified `location`. - async fn key_sign(&self, id: IdentityId, location: &KeyLocation, data: Vec) -> Result; + async fn key_sign(&self, did: &IotaDID, location: &KeyLocation, data: Vec) -> Result; /// Returns `true` if a keypair exists at the specified `location`. - async fn key_exists(&self, id: IdentityId, location: &KeyLocation) -> Result; - - /// Returns the account identity index. - async fn index(&self) -> Result; + async fn key_exists(&self, did: &IotaDID, location: &KeyLocation) -> Result; /// Returns the last generation that has been published to the tangle for the given `id`. - async fn published_generation(&self, id: IdentityId) -> Result>; + async fn published_generation(&self, did: &IotaDID) -> Result>; /// Sets the last generation that has been published to the tangle for the given `id`. - async fn set_published_generation(&self, id: IdentityId, index: Generation) -> Result<()>; - - /// Sets a new account identity index. - async fn set_index(&self, index: &IdentityIndex) -> Result<()>; + async fn set_published_generation(&self, did: &IotaDID, index: Generation) -> Result<()>; /// Returns the state snapshot of the identity specified by `id`. - async fn snapshot(&self, id: IdentityId) -> Result>; + async fn snapshot(&self, did: &IotaDID) -> Result>; /// Sets a new state snapshot for the identity specified by `id`. - async fn set_snapshot(&self, id: IdentityId, snapshot: &IdentitySnapshot) -> Result<()>; + async fn set_snapshot(&self, did: &IotaDID, snapshot: &IdentitySnapshot) -> Result<()>; /// Appends a set of commits to the event stream for the identity specified by `id`. - async fn append(&self, id: IdentityId, commits: &[Commit]) -> Result<()>; + async fn append(&self, did: &IotaDID, commits: &[Commit]) -> Result<()>; /// Returns a stream of commits for the identity specified by `id`. /// /// The stream may be offset by `index`. - async fn stream(&self, id: IdentityId, index: Generation) -> Result>>; + async fn stream(&self, did: &IotaDID, index: Generation) -> Result>>; /// Returns a list of all commits for the identity specified by `id`. /// /// The list may be offset by `index`. - async fn collect(&self, id: IdentityId, index: Generation) -> Result> { - self.stream(id, index).await?.try_collect().await + async fn collect(&self, did: &IotaDID, index: Generation) -> Result> { + self.stream(did, index).await?.try_collect().await } /// Removes the event stream and state snapshot for the identity specified by `id`. - async fn purge(&self, id: IdentityId) -> Result<()>; + async fn purge(&self, did: &IotaDID) -> Result<()>; } #[async_trait::async_trait] @@ -93,63 +92,59 @@ impl Storage for Box { (**self).flush_changes().await } - async fn key_new(&self, id: IdentityId, location: &KeyLocation) -> Result { - (**self).key_new(id, location).await - } - - async fn key_insert(&self, id: IdentityId, location: &KeyLocation, private_key: PrivateKey) -> Result { - (**self).key_insert(id, location, private_key).await + async fn lease_did(&self, did: &IotaDID) -> Result { + (**self).lease_did(did).await } - async fn key_get(&self, id: IdentityId, location: &KeyLocation) -> Result { - (**self).key_get(id, location).await + async fn key_new(&self, did: &IotaDID, location: &KeyLocation) -> Result { + (**self).key_new(did, location).await } - async fn key_del(&self, id: IdentityId, location: &KeyLocation) -> Result<()> { - (**self).key_del(id, location).await + async fn key_insert(&self, did: &IotaDID, location: &KeyLocation, private_key: PrivateKey) -> Result { + (**self).key_insert(did, location, private_key).await } - async fn key_sign(&self, id: IdentityId, location: &KeyLocation, data: Vec) -> Result { - (**self).key_sign(id, location, data).await + async fn key_get(&self, did: &IotaDID, location: &KeyLocation) -> Result { + (**self).key_get(did, location).await } - async fn key_exists(&self, id: IdentityId, location: &KeyLocation) -> Result { - (**self).key_exists(id, location).await + async fn key_del(&self, did: &IotaDID, location: &KeyLocation) -> Result<()> { + (**self).key_del(did, location).await } - async fn index(&self) -> Result { - (**self).index().await + async fn key_sign(&self, did: &IotaDID, location: &KeyLocation, data: Vec) -> Result { + (**self).key_sign(did, location, data).await } - async fn set_index(&self, index: &IdentityIndex) -> Result<()> { - (**self).set_index(index).await + async fn key_exists(&self, did: &IotaDID, location: &KeyLocation) -> Result { + (**self).key_exists(did, location).await } - async fn snapshot(&self, id: IdentityId) -> Result> { - (**self).snapshot(id).await + async fn snapshot(&self, did: &IotaDID) -> Result> { + (**self).snapshot(did).await } - async fn set_snapshot(&self, id: IdentityId, snapshot: &IdentitySnapshot) -> Result<()> { - (**self).set_snapshot(id, snapshot).await + async fn set_snapshot(&self, did: &IotaDID, snapshot: &IdentitySnapshot) -> Result<()> { + (**self).set_snapshot(did, snapshot).await } - async fn append(&self, id: IdentityId, commits: &[Commit]) -> Result<()> { - (**self).append(id, commits).await + async fn append(&self, did: &IotaDID, commits: &[Commit]) -> Result<()> { + (**self).append(did, commits).await } - async fn stream(&self, id: IdentityId, index: Generation) -> Result>> { - (**self).stream(id, index).await + async fn stream(&self, did: &IotaDID, index: Generation) -> Result>> { + (**self).stream(did, index).await } - async fn purge(&self, id: IdentityId) -> Result<()> { - (**self).purge(id).await + async fn purge(&self, did: &IotaDID) -> Result<()> { + (**self).purge(did).await } - async fn published_generation(&self, id: IdentityId) -> Result> { - (**self).published_generation(id).await + async fn published_generation(&self, did: &IotaDID) -> Result> { + (**self).published_generation(did).await } - async fn set_published_generation(&self, id: IdentityId, index: Generation) -> Result<()> { - (**self).set_published_generation(id, index).await + async fn set_published_generation(&self, did: &IotaDID, index: Generation) -> Result<()> { + (**self).set_published_generation(did, index).await } } diff --git a/identity-account/src/tests/account.rs b/identity-account/src/tests/account.rs new file mode 100644 index 0000000000..f56f6ffe46 --- /dev/null +++ b/identity-account/src/tests/account.rs @@ -0,0 +1,60 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::did::IotaDID; + +use crate::account::Account; +use crate::account::AccountBuilder; +use crate::error::Result; +use crate::identity::IdentitySetup; + +#[tokio::test] +async fn test_account_high_level() -> Result<()> { + let mut builder: AccountBuilder = AccountBuilder::default().testmode(true); + + let account1: Account = builder.create_identity(IdentitySetup::default()).await?; + + builder = builder.autopublish(false); + + let account2: Account = builder.create_identity(IdentitySetup::default()).await?; + + assert!(account1.autopublish()); + assert!(!account2.autopublish()); + + let did1 = account1.did().to_owned(); + let did2 = account2.did().to_owned(); + account2.delete_identity().await?; + + assert!(matches!( + builder.load_identity(did2).await.unwrap_err(), + crate::Error::IdentityNotFound + )); + + // Relase the lease on did1. + std::mem::drop(account1); + + assert!(builder.load_identity(did1).await.is_ok()); + + Ok(()) +} + +#[tokio::test] +async fn test_account_did_lease() -> Result<()> { + let mut builder: AccountBuilder = AccountBuilder::default().testmode(true); + + let did: IotaDID = { + let account: Account = builder.create_identity(IdentitySetup::default()).await?; + account.did().to_owned() + }; // <-- Lease released here. + + // Lease is not in-use + let _account = builder.load_identity(did.clone()).await.unwrap(); + + // Lease is in-use + assert!(matches!( + builder.load_identity(did).await.unwrap_err(), + crate::Error::IdentityInUse + )); + + Ok(()) +} diff --git a/identity-account/src/tests/commands.rs b/identity-account/src/tests/commands.rs index 078a6739e3..911bcfbddc 100644 --- a/identity-account/src/tests/commands.rs +++ b/identity-account/src/tests/commands.rs @@ -1,16 +1,17 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::sync::Arc; + use crate::account::Account; -use crate::account::Config; +use crate::account::AccountConfig; +use crate::account::AccountSetup; use crate::error::Error; use crate::error::Result; -use crate::events::Command; +use crate::events::Update; use crate::events::UpdateError; -use crate::identity::IdentityCreate; -use crate::identity::IdentityId; +use crate::identity::IdentitySetup; use crate::identity::IdentitySnapshot; -use crate::identity::IdentityState; use crate::identity::TinyMethod; use crate::storage::MemStore; use crate::types::Generation; @@ -23,37 +24,21 @@ use identity_core::crypto::PrivateKey; use identity_did::verification::MethodScope; use identity_did::verification::MethodType; -async fn new_account() -> Result { - let store: MemStore = MemStore::new(); - let config: Config = Config::new().testmode(true); - - Account::with_config(store, config).await +fn account_setup() -> AccountSetup { + AccountSetup::new_with_options( + Arc::new(MemStore::new()), + Some(AccountConfig::new().testmode(true)), + None, + ) } #[tokio::test] async fn test_create_identity() -> Result<()> { - let account: Account = new_account().await?; - let identity: IdentityId = IdentityId::from_u32(1); - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; - - assert_eq!(snapshot.sequence(), Generation::new()); - assert_eq!(snapshot.id(), identity); - assert!(snapshot.identity().did().is_none()); - assert_eq!(snapshot.identity().created(), UnixTimestamp::EPOCH); - assert_eq!(snapshot.identity().updated(), UnixTimestamp::EPOCH); - - let command: Command = Command::CreateIdentity { - network: None, - method_secret: None, - authentication: MethodType::Ed25519VerificationKey2018, - }; - - account.process(identity, command, false).await?; + let account = Account::create_identity(account_setup(), IdentitySetup::default()).await?; - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; assert_eq!(snapshot.sequence(), Generation::from_u32(4)); - assert_eq!(snapshot.id(), identity); assert!(snapshot.identity().did().is_some()); assert_ne!(snapshot.identity().created(), UnixTimestamp::EPOCH); assert_ne!(snapshot.identity().updated(), UnixTimestamp::EPOCH); @@ -61,51 +46,14 @@ async fn test_create_identity() -> Result<()> { Ok(()) } -#[tokio::test] -async fn test_create_identity_invalid_method() -> Result<()> { - const TYPES: &[MethodType] = &[MethodType::MerkleKeyCollection2021]; - - let account: Account = new_account().await?; - let identity: IdentityId = IdentityId::from_u32(1); - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; - - // initial snapshot version = 0 - assert_eq!(snapshot.sequence(), Generation::new()); - - for type_ in TYPES.iter().copied() { - let command: Command = Command::CreateIdentity { - network: None, - method_secret: None, - authentication: type_, - }; - - let output: Result<()> = account.process(identity, command, false).await; - - assert!(matches!( - output.unwrap_err(), - Error::UpdateError(UpdateError::InvalidMethodType(_)) - )); - - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; - - // version is still 0, no events have been committed - assert_eq!(snapshot.sequence(), Generation::new()); - } - - Ok(()) -} - #[tokio::test] async fn test_create_identity_network() -> Result<()> { - let account: Account = new_account().await?; - // Create an identity with a valid network string - let create_identity: IdentityCreate = IdentityCreate::new().network("dev")?.key_type(KeyType::Ed25519); - let identity: IdentityState = account.create_identity(create_identity).await?; + let create_identity: IdentitySetup = IdentitySetup::new().network("dev")?.key_type(KeyType::Ed25519); + let account = Account::create_identity(account_setup(), create_identity).await?; // Ensure the identity creation was successful - assert!(identity.did().is_some()); - assert!(identity.authentication().is_ok()); + assert!(account.state().await?.authentication().is_ok()); Ok(()) } @@ -113,7 +61,7 @@ async fn test_create_identity_network() -> Result<()> { #[tokio::test] async fn test_create_identity_invalid_network() -> Result<()> { // Attempt to create an identity with an invalid network string - let result: Result = IdentityCreate::new().network("Invalid=Network!"); + let result: Result = IdentitySetup::new().network("Invalid=Network!"); // Ensure an `InvalidNetworkName` error is thrown assert!(matches!( @@ -126,79 +74,39 @@ async fn test_create_identity_invalid_network() -> Result<()> { #[tokio::test] async fn test_create_identity_already_exists() -> Result<()> { - let account: Account = new_account().await?; - let identity: IdentityId = IdentityId::from_u32(1); - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; - - // initial snapshot version = 0 - assert_eq!(snapshot.sequence(), Generation::new()); - - let command: Command = Command::CreateIdentity { - network: None, - method_secret: None, - authentication: MethodType::Ed25519VerificationKey2018, - }; - - account.process(identity, command.clone(), false).await?; + let keypair = KeyPair::new_ed25519()?; + let identity_create = IdentitySetup::default() + .key_type(KeyType::Ed25519) + .method_secret(MethodSecret::Ed25519(keypair.private().clone())); + let account_setup = account_setup(); - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let account = Account::create_identity(account_setup.clone(), identity_create.clone()).await?; - // version is now 4 - assert_eq!(snapshot.sequence(), Generation::from(4)); + assert_eq!(account.load_snapshot().await?.sequence(), Generation::from(4)); - let output: Result<()> = account.process(identity, command, false).await; + let output = Account::create_identity(account_setup, identity_create).await; assert!(matches!( output.unwrap_err(), Error::UpdateError(UpdateError::DocumentAlreadyExists), )); - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; - // version is still 4, no events have been committed - assert_eq!(snapshot.sequence(), Generation::from(4)); - - Ok(()) -} - -#[tokio::test] -async fn test_create_identity_from_private_key() -> Result<()> { - let account: Account = new_account().await?; - let account2: Account = new_account().await?; - - let identity: IdentityId = IdentityId::from_u32(1); - - let private_key = KeyPair::new_ed25519()?.private().clone(); - - let id_create = IdentityCreate::new() - .key_type(KeyType::Ed25519) - .method_secret(MethodSecret::Ed25519(private_key)); - - account.create_identity(id_create.clone()).await?; - account2.create_identity(id_create).await?; - - let ident = account.find_identity(identity).await.unwrap().unwrap(); - let ident2 = account.find_identity(identity).await.unwrap().unwrap(); - - // The same private key should result in the same did - assert_eq!(ident.did(), ident2.did()); - assert_eq!(ident.authentication()?, ident2.authentication()?); + assert_eq!(account.load_snapshot().await?.sequence(), Generation::from(4)); Ok(()) } #[tokio::test] async fn test_create_identity_from_invalid_private_key() -> Result<()> { - let account: Account = new_account().await?; - let private_bytes: Box<[u8]> = Box::new([0; 33]); let private_key: PrivateKey = PrivateKey::from(private_bytes); - let id_create = IdentityCreate::new() + let id_create = IdentitySetup::new() .key_type(KeyType::Ed25519) .method_secret(MethodSecret::Ed25519(private_key)); - let err = account.create_identity(id_create).await.unwrap_err(); + let err = Account::create_identity(account_setup(), id_create).await.unwrap_err(); assert!(matches!(err, Error::UpdateError(UpdateError::InvalidMethodSecret(_)))); @@ -207,30 +115,20 @@ async fn test_create_identity_from_invalid_private_key() -> Result<()> { #[tokio::test] async fn test_create_method() -> Result<()> { - let account: Account = new_account().await?; - let identity: IdentityId = IdentityId::from_u32(1); - - let command: Command = Command::CreateIdentity { - network: None, - method_secret: None, - authentication: MethodType::Ed25519VerificationKey2018, - }; + let account = Account::create_identity(account_setup(), IdentitySetup::default()).await?; - account.process(identity, command, false).await?; - - let command: Command = Command::CreateMethod { + let update: Update = Update::CreateMethod { scope: MethodScope::default(), method_secret: None, type_: MethodType::Ed25519VerificationKey2018, fragment: "key-1".to_owned(), }; - account.process(identity, command, false).await?; + account.process_update(update, false).await?; - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; assert_eq!(snapshot.sequence(), Generation::from_u32(6)); - assert_eq!(snapshot.id(), identity); assert!(snapshot.identity().did().is_some()); assert_ne!(snapshot.identity().created(), UnixTimestamp::EPOCH); assert_ne!(snapshot.identity().updated(), UnixTimestamp::EPOCH); @@ -246,37 +144,28 @@ async fn test_create_method() -> Result<()> { #[tokio::test] async fn test_create_method_reserved_fragment() -> Result<()> { - let account: Account = new_account().await?; - let identity: IdentityId = IdentityId::from_u32(1); - - let command: Command = Command::CreateIdentity { - network: None, - method_secret: None, - authentication: MethodType::Ed25519VerificationKey2018, - }; + let account = Account::create_identity(account_setup(), IdentitySetup::default()).await?; - account.process(identity, command, false).await?; - - let command: Command = Command::CreateMethod { + let update: Update = Update::CreateMethod { scope: MethodScope::default(), method_secret: None, type_: MethodType::Ed25519VerificationKey2018, fragment: "_sign-123".to_owned(), }; - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; // version is now 4 assert_eq!(snapshot.sequence(), Generation::from_u32(4)); - let output: _ = account.process(identity, command, false).await; + let output: _ = account.process_update(update, false).await; assert!(matches!( output.unwrap_err(), Error::UpdateError(UpdateError::InvalidMethodFragment(_)), )); - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; // version is still 4, no new events have been committed assert_eq!(snapshot.sequence(), Generation::from_u32(4)); @@ -286,40 +175,31 @@ async fn test_create_method_reserved_fragment() -> Result<()> { #[tokio::test] async fn test_create_method_duplicate_fragment() -> Result<()> { - let account: Account = new_account().await?; - let identity: IdentityId = IdentityId::from_u32(1); - - let command: Command = Command::CreateIdentity { - network: None, - method_secret: None, - authentication: MethodType::Ed25519VerificationKey2018, - }; + let account = Account::create_identity(account_setup(), IdentitySetup::default()).await?; - account.process(identity, command, false).await?; - - let command: Command = Command::CreateMethod { + let update: Update = Update::CreateMethod { scope: MethodScope::default(), method_secret: None, type_: MethodType::Ed25519VerificationKey2018, fragment: "key-1".to_owned(), }; - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; assert_eq!(snapshot.sequence(), Generation::from_u32(4)); - account.process(identity, command.clone(), false).await?; + account.process_update(update.clone(), false).await?; - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; assert_eq!(snapshot.sequence(), Generation::from_u32(6)); - let output: _ = account.process(identity, command, false).await; + let output: _ = account.process_update(update, false).await; assert!(matches!( output.unwrap_err(), Error::UpdateError(UpdateError::DuplicateKeyFragment(_)), )); - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; assert_eq!(snapshot.sequence(), Generation::from_u32(6)); Ok(()) @@ -327,33 +207,24 @@ async fn test_create_method_duplicate_fragment() -> Result<()> { #[tokio::test] async fn test_create_method_from_private_key() -> Result<()> { - let account: Account = new_account().await?; - let identity: IdentityId = IdentityId::from_u32(1); - - let command: Command = Command::CreateIdentity { - network: None, - method_secret: None, - authentication: MethodType::Ed25519VerificationKey2018, - }; - - account.process(identity, command, false).await?; + let account = Account::create_identity(account_setup(), IdentitySetup::default()).await?; let keypair = KeyPair::new_ed25519()?; - let command: Command = Command::CreateMethod { + let update: Update = Update::CreateMethod { scope: MethodScope::default(), method_secret: Some(MethodSecret::Ed25519(keypair.private().clone())), type_: MethodType::Ed25519VerificationKey2018, fragment: "key-1".to_owned(), }; - account.process(identity, command, false).await?; + account.process_update(update, false).await?; - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; let method: &TinyMethod = snapshot.identity().methods().fetch("key-1")?; - let public_key = account.store().key_get(identity, method.location()).await?; + let public_key = account.storage().key_get(account.did(), method.location()).await?; assert_eq!(public_key.as_ref(), keypair.public().as_ref()); @@ -362,28 +233,19 @@ async fn test_create_method_from_private_key() -> Result<()> { #[tokio::test] async fn test_create_method_from_invalid_private_key() -> Result<()> { - let account: Account = new_account().await?; - let identity: IdentityId = IdentityId::from_u32(1); - - let command: Command = Command::CreateIdentity { - network: None, - method_secret: None, - authentication: MethodType::Ed25519VerificationKey2018, - }; - - account.process(identity, command, false).await?; + let account = Account::create_identity(account_setup(), IdentitySetup::default()).await?; let private_bytes: Box<[u8]> = Box::new([0; 33]); let private_key = PrivateKey::from(private_bytes); - let command: Command = Command::CreateMethod { + let update: Update = Update::CreateMethod { scope: MethodScope::default(), method_secret: Some(MethodSecret::Ed25519(private_key)), type_: MethodType::Ed25519VerificationKey2018, fragment: "key-1".to_owned(), }; - let err = account.process(identity, command, false).await.unwrap_err(); + let err = account.process_update(update, false).await.unwrap_err(); assert!(matches!(err, Error::UpdateError(UpdateError::InvalidMethodSecret(_)))); @@ -392,41 +254,32 @@ async fn test_create_method_from_invalid_private_key() -> Result<()> { #[tokio::test] async fn test_create_method_with_type_secret_mismatch() -> Result<()> { - let account: Account = new_account().await?; - let identity: IdentityId = IdentityId::from_u32(1); - - let command: Command = Command::CreateIdentity { - network: None, - method_secret: None, - authentication: MethodType::Ed25519VerificationKey2018, - }; - - account.process(identity, command, false).await?; + let account = Account::create_identity(account_setup(), IdentitySetup::default()).await?; let private_bytes: Box<[u8]> = Box::new([0; 32]); let private_key = PrivateKey::from(private_bytes); - let command: Command = Command::CreateMethod { + let update: Update = Update::CreateMethod { scope: MethodScope::default(), method_secret: Some(MethodSecret::Ed25519(private_key)), type_: MethodType::MerkleKeyCollection2021, fragment: "key-1".to_owned(), }; - let err = account.process(identity, command, false).await.unwrap_err(); + let err = account.process_update(update, false).await.unwrap_err(); assert!(matches!(err, Error::UpdateError(UpdateError::InvalidMethodSecret(_)))); let key_collection = KeyCollection::new_ed25519(4).unwrap(); - let command: Command = Command::CreateMethod { + let update: Update = Update::CreateMethod { scope: MethodScope::default(), method_secret: Some(MethodSecret::MerkleKeyCollection(key_collection)), type_: MethodType::Ed25519VerificationKey2018, fragment: "key-2".to_owned(), }; - let err = account.process(identity, command, false).await.unwrap_err(); + let err = account.process_update(update, false).await.unwrap_err(); assert!(matches!(err, Error::UpdateError(UpdateError::InvalidMethodSecret(_)))); @@ -435,30 +288,21 @@ async fn test_create_method_with_type_secret_mismatch() -> Result<()> { #[tokio::test] async fn test_delete_method() -> Result<()> { - let account: Account = new_account().await?; - let identity: IdentityId = IdentityId::from_u32(1); - - let command: Command = Command::CreateIdentity { - network: None, - method_secret: None, - authentication: MethodType::Ed25519VerificationKey2018, - }; - - account.process(identity, command, false).await?; + let account = Account::create_identity(account_setup(), IdentitySetup::default()).await?; - let command: Command = Command::CreateMethod { + let update: Update = Update::CreateMethod { scope: MethodScope::default(), method_secret: None, type_: MethodType::Ed25519VerificationKey2018, fragment: "key-1".to_owned(), }; - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; assert_eq!(snapshot.sequence(), Generation::from_u32(4)); - account.process(identity, command, false).await?; + account.process_update(update, false).await?; - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; assert_eq!(snapshot.sequence(), Generation::from_u32(6)); assert_eq!(snapshot.identity().methods().len(), 2); @@ -466,13 +310,13 @@ async fn test_delete_method() -> Result<()> { assert!(snapshot.identity().methods().get("key-1").is_some()); assert!(snapshot.identity().methods().fetch("key-1").is_ok()); - let command: Command = Command::DeleteMethod { + let update: Update = Update::DeleteMethod { fragment: "key-1".to_owned(), }; - account.process(identity, command, false).await?; + account.process_update(update, false).await?; - let snapshot: IdentitySnapshot = account.load_snapshot(identity).await?; + let snapshot: IdentitySnapshot = account.load_snapshot().await?; assert_eq!(snapshot.sequence(), Generation::from_u32(8)); assert_eq!(snapshot.identity().methods().len(), 1); diff --git a/identity-account/src/tests/lazy.rs b/identity-account/src/tests/lazy.rs index f30f360b9a..af3ab96371 100644 --- a/identity-account/src/tests/lazy.rs +++ b/identity-account/src/tests/lazy.rs @@ -2,14 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use std::pin::Pin; +use std::sync::Arc; -use crate::account::Account; -use crate::identity::{IdentityCreate, IdentityState, IdentityUpdater}; +use crate::account::{Account, AccountConfig, AccountSetup}; +use crate::identity::IdentitySetup; +use crate::storage::MemStore; use crate::{Error as AccountError, Result}; use futures::Future; use identity_core::common::Url; use identity_iota::chain::DocumentHistory; -use identity_iota::did::{IotaDID, IotaVerificationMethod}; +use identity_iota::did::IotaVerificationMethod; use identity_iota::tangle::{Client, Network}; use identity_iota::Error as IotaError; @@ -20,7 +22,6 @@ async fn test_lazy_updates() -> Result<()> { // =========================================================================== // Create, update and publish an identity // =========================================================================== - let account: Account = Account::builder().autopublish(false).build().await?; let network = if test_run % 2 == 0 { Network::Devnet @@ -28,15 +29,14 @@ async fn test_lazy_updates() -> Result<()> { Network::Mainnet }; - let identity: IdentityState = account - .create_identity(IdentityCreate::new().network(network.name()).unwrap()) - .await?; - - let did: &IotaDID = identity.try_did()?; + let config = AccountConfig::default().autopublish(false); + let account_config = AccountSetup::new(Arc::new(MemStore::new())).config(config); - let did_updater: IdentityUpdater<'_, '_, _> = account.update_identity(did); + let mut account = + Account::create_identity(account_config, IdentitySetup::new().network(network.name()).unwrap()).await?; - did_updater + account + .update_identity() .create_service() .fragment("my-service") .type_("LinkedDomains") @@ -44,7 +44,8 @@ async fn test_lazy_updates() -> Result<()> { .apply() .await?; - did_updater + account + .update_identity() .create_service() .fragment("my-other-service") .type_("LinkedDomains") @@ -52,13 +53,13 @@ async fn test_lazy_updates() -> Result<()> { .apply() .await?; - account.publish_updates(did).await?; + account.publish_updates().await?; // =========================================================================== // First round of assertions // =========================================================================== - let doc = account.resolve_identity(identity.did().unwrap()).await?; + let doc = account.resolve_identity().await?; let services = doc.service(); @@ -76,23 +77,34 @@ async fn test_lazy_updates() -> Result<()> { // More updates to the identity // =========================================================================== - did_updater.delete_service().fragment("my-service").apply().await?; + account + .update_identity() + .delete_service() + .fragment("my-service") + .apply() + .await?; - did_updater + account + .update_identity() .delete_service() .fragment("my-other-service") .apply() .await?; - did_updater.create_method().fragment("new-method").apply().await?; + account + .update_identity() + .create_method() + .fragment("new-method") + .apply() + .await?; - account.publish_updates(did).await?; + account.publish_updates().await?; // =========================================================================== // Second round of assertions // =========================================================================== - let doc = account.resolve_identity(identity.did().unwrap()).await?; + let doc = account.resolve_identity().await?; let methods = doc.methods().collect::>(); assert_eq!(doc.service().len(), 0); @@ -111,7 +123,7 @@ async fn test_lazy_updates() -> Result<()> { let client: Client = Client::from_network(network).await?; - let history: DocumentHistory = client.resolve_history(did).await?; + let history: DocumentHistory = client.resolve_history(account.did()).await?; assert_eq!(history.integration_chain_data.len(), 1); assert_eq!(history.diff_chain_data.len(), 1); diff --git a/identity-account/src/tests/mod.rs b/identity-account/src/tests/mod.rs index 4cc77c616c..4c9d831550 100644 --- a/identity-account/src/tests/mod.rs +++ b/identity-account/src/tests/mod.rs @@ -1,5 +1,6 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod account; mod commands; mod lazy; diff --git a/identity-core/src/crypto/key/pair.rs b/identity-core/src/crypto/key/pair.rs index c5283a6446..a1fcad0533 100644 --- a/identity-core/src/crypto/key/pair.rs +++ b/identity-core/src/crypto/key/pair.rs @@ -1,6 +1,9 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::convert::TryInto; + +use crypto::signatures::ed25519; use zeroize::Zeroize; use crate::crypto::KeyRef; @@ -9,6 +12,7 @@ use crate::crypto::PrivateKey; use crate::crypto::PublicKey; use crate::error::Result; use crate::utils::generate_ed25519_keypair; +use crate::utils::keypair_from_ed25519_private_key; /// A convenient type for representing a pair of cryptographic keys. #[derive(Clone, Debug)] @@ -33,6 +37,23 @@ impl KeyPair { Ok(Self { type_, public, private }) } + /// Reconstructs the [`Ed25519`][`KeyType::Ed25519`] [`KeyPair`] from a private key. + pub fn try_from_ed25519_bytes(private_key_bytes: &[u8]) -> Result { + let private_key_bytes: [u8; ed25519::SECRET_KEY_LENGTH] = private_key_bytes + .try_into() + .map_err(|_| crypto::Error::PrivateKeyError)?; + + let private_key = ed25519::SecretKey::from_bytes(private_key_bytes); + + let (public, private) = keypair_from_ed25519_private_key(private_key); + + Ok(Self { + type_: KeyType::Ed25519, + public, + private, + }) + } + /// Returns the [`type`][`KeyType`] of the `KeyPair` object. pub const fn type_(&self) -> KeyType { self.type_ diff --git a/identity-core/src/utils/ed25519.rs b/identity-core/src/utils/ed25519.rs index 83c4644c75..cf4826375d 100644 --- a/identity-core/src/utils/ed25519.rs +++ b/identity-core/src/utils/ed25519.rs @@ -18,6 +18,16 @@ pub fn generate_ed25519_keypair() -> Result<(PublicKey, PrivateKey)> { Ok((public, private)) } +/// Reconstructs the ed25519 public key given the private key. +pub fn keypair_from_ed25519_private_key(private_key: ed25519::SecretKey) -> (PublicKey, PrivateKey) { + let public: ed25519::PublicKey = private_key.public_key(); + + let private: PrivateKey = private_key.to_bytes().to_vec().into(); + let public: PublicKey = public.to_bytes().to_vec().into(); + + (public, private) +} + /// Generates a list of public/private ed25519 keys. pub fn generate_ed25519_keypairs(count: usize) -> Result> { (0..count).map(|_| generate_ed25519_keypair()).collect()