Skip to content

Commit

Permalink
Refactor the Account: internal state, one identity (#453)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* 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 <[email protected]>

* 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 3d1b716.

* Revert "Only impl `IdentityCreate` under `#[cfg(test)]`"

This reverts commit 08ada09.

* Revert "Impl `IdentityBuilder`"

This reverts commit 1a5d645.

* 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 <[email protected]>
Co-authored-by: Thoralf-M <[email protected]>
Co-authored-by: Craig Bester <[email protected]>
Co-authored-by: Jelle Femmo Millenaar <[email protected]>

* Apply new fmt granularity

* Refactor the `Account` state (#462)

* Get rid of `IdentitySnapshot`

* Start replacing parts of `IdentityState`

* Create identity document from core structures

* Impl int/diff update determination

* Remove `Deref` impls for IdentityState

* Implement `CreateMethod` update

* Impl attach/detach `MethodRelationship`

* Update to cap inv refactor

* Use `IotaDocument` directly to construct the doc

* Update tests for account updates

* Remove unused types & variables

* Return to unified publish method

* Implement & test attaching relationships

* Fix attach/detach tests post-merge

* Implement detach relationship test

* Implement insert and remove service

* Move `Publish` to low-level API

* Remove did field from account

* Refactor publishing & storing

* Rename int/diff generations

* Change fresh id detection & fix `store_state` call

* Factor out `ChainState` from `IdentityState`

* Remove `this_message_id` from chain state

* Test the chain state

* Fix some error TODOs

* Fix some TODOs

* Fix some storage todos/errors

* Make `IotaDocument::remove_method` fallible again

* Use name instead of identifier to call from_did

* Move state loading into the else clause

* Remove `persist` flag on `process_update`

* Remove unnecessary cloning

* Rename `as_document` -> `document`

* Improve `ChainState` docs

* Fix post-merge things & clippy lint

* Remove `Tiny*` and other unused state types

* `InvalidMethodTarget` -> `InvalidTargetMethod`

* `as_document_mut` -> `document_mut`

* Expand lazy test

* Rename impl_command_builder to update

* Remove event context

* Rename `events` module to `updates`

* Apply fmt

* Rename `commands` -> `updates`

* Remove `UnixTimestamp`

* Remove unused errors in account & update

* Change `fromDID` method in Wasm

* Return `Option<Publish>` from constructor

* Use strum, add todo!(), rename `PublishType`

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

* Move attach/detach relationship to `CoreDocument`

* Return bool from `detach_method_relationship`

* Use `MethodQuery` in attach/detach

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

* Revert "Change `fromDID` method in Wasm"

This reverts commit cb0cf35.

* Nest `MethodRelationship` in `MethodScope`

* Move some chain state logic to publish_diff_change

* Don't expect, bubble up

* Return early if there's nothing to publish

* Add todo about error handling

* Remove mut state methods

* Move account constructors to the top

* Use `try_resolve_method_with_scope` in tests

* Remove todos, inline code, explicitly ignore res.

* Remove todo comments, `unwrap`

* Rename chain state fields to `last_*`

* Improve `InvalidTargetMethod` Error name

* Increment actions when updating

* check for updated referenced method in publish

* Enable diff updates for merkle cap. inv. methods

* Only attach relationship on non-embedded methods

* Add proper type annotations in publish

* Improve chain state docs & variable naming

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

* Fix READMEs

* Deduplicate setup code in builder

* Remove tangle dependency in autopublish test

* Add docs for attach/detach relationship

* Deduplicate signing key extraction

* Fix documentation & annotate types in examples

Co-authored-by: Matt Renaud <[email protected]>
Co-authored-by: Thoralf-M <[email protected]>
Co-authored-by: Craig Bester <[email protected]>
Co-authored-by: Jelle Femmo Millenaar <[email protected]>
  • Loading branch information
5 people authored Nov 18, 2021
1 parent 70cddef commit 9bf01bb
Show file tree
Hide file tree
Showing 69 changed files with 3,031 additions and 3,627 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
branches:
- main
- dev
- epic/*
paths:
- '.github/workflows/build-and-test.yml'
- '.github/actions'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/clippy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
branches:
- main
- dev
- epic/*
paths:
- '.github/workflows/clippy.yml'
- '**.rs'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
branches:
- main
- dev
- epic/*
paths:
- '.github/workflows/coverage.yml'
- '.github/workflows/scripts/coverage.sh'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
branches:
- main
- dev
- epic/*
paths:
- '.github/workflows/format.yml'
- '**.rs'
Expand Down
24 changes: 8 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,39 +91,31 @@ 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]
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.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);

Expand Down
2 changes: 1 addition & 1 deletion bindings/wasm/src/did/wasm_verification_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl WasmVerificationMethod {
/// Creates a new `VerificationMethod` object from the given `did` and `key`.
#[wasm_bindgen(js_name = fromDID)]
pub fn from_did(did: &WasmDID, key: &KeyPair, fragment: String) -> Result<WasmVerificationMethod, JsValue> {
IotaVerificationMethod::from_did(did.0.clone(), &key.0, &fragment)
IotaVerificationMethod::from_did(did.0.clone(), key.0.type_(), key.0.public(), &fragment)
.map_err(wasm_error)
.map(Self)
}
Expand Down
4 changes: 4 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ The following examples are available for using the basic account (A high-level A
| # | Name | Information |
| :--: | :----------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- |
| 1 | [getting_started](./getting_started.rs) | Introductory example for you to test whether the library is set up / working properly and compiles. |
| 2 | [account_create_did](./account/create_did.rs) | A basic example that generates and publishes a DID Document, the fundamental building block for decentralized identity. |
| 2 | [account_create](./account/create_did.rs) | A basic example that generates and publishes a DID Document, the fundamental building block for decentralized identity. |
| 3 | [account_config](./account/config.rs) | How to configure the account to work with different networks and other settings. |
| 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:
Expand Down
30 changes: 15 additions & 15 deletions examples/account/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,7 +16,7 @@ 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`.
Expand All @@ -33,13 +33,14 @@ async fn main() -> Result<()> {
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
Expand All @@ -55,26 +56,25 @@ async fn main() -> Result<()> {
.client(network.clone(), |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 identity_setup: IdentitySetup = 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(identity_setup).await {
Ok(identity) => identity,
Err(err) => {
eprintln!("[Example] Error: {:?} {}", err, err.to_string());
eprintln!("[Example] Is your Tangle node listening on {}?", private_node_url);
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 = {}{}",
network.explorer_url().expect("no explorer url was set").to_string(),
Expand Down
23 changes: 12 additions & 11 deletions examples/account/create_did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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().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(())
}
31 changes: 14 additions & 17 deletions examples/account/lazy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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(),
Expand Down
33 changes: 15 additions & 18 deletions examples/account/manipulate_did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ 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;
use identity::did::MethodRelationship;
use identity::iota::IotaDID;

#[tokio::main]
Expand All @@ -27,42 +26,36 @@ 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()
.await?;

// 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)
.scope(MethodScope::CapabilityInvocation)
.relationship(MethodRelationship::CapabilityDelegation)
.relationship(MethodRelationship::CapabilityInvocation)
.apply()
.await?;

// Add a new service to the identity.
account
.update_identity(&iota_did)
.update_identity()
.create_service()
.fragment("my-service-1")
.type_("MyCustomService")
Expand All @@ -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(),
Expand Down
Loading

0 comments on commit 9bf01bb

Please sign in to comment.