Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add token_wallet module to allow funding IOTA DIDs #8

Draft
wants to merge 34 commits into
base: beta
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7218d6a
refactor: remove unused dependencies
daniel-mader Apr 3, 2024
70bfbba
refactor: use `try_decode()` before decoding from public JWK params
daniel-mader Apr 3, 2024
9e50a1e
Merge branch 'dev' into refactor/error-handling
daniel-mader Apr 5, 2024
2fa406d
feat: add `thiserror` dependency
daniel-mader Apr 9, 2024
4d88c98
refactor: introduce generic `ProducerError`
daniel-mader Apr 9, 2024
7ec8652
chore: update `rand` dependency
daniel-mader Apr 10, 2024
5bb6ed6
feat: add module `token_wallet`
daniel-mader Apr 11, 2024
4ce5bf3
chore: small adjustments
daniel-mader Apr 11, 2024
56e6d49
feat: produce and publish DID documents for method `did:iota:rms`
daniel-mader Apr 11, 2024
934d442
feat: resolve document for given wallet address
daniel-mader Apr 12, 2024
b23cba9
feat: add helper functions to generate a new stronghold and jwk
daniel-mader Apr 16, 2024
03e3eca
feat: create governor address, handle different amounts of AliasOutputs
daniel-mader Apr 16, 2024
3d23411
feat: add destruction of IOTA DIDs
daniel-mader Apr 16, 2024
7170097
test: prevent replacing an existing stronghold
daniel-mader Apr 16, 2024
62cf1d8
refactor: externalize `SecretManager`
daniel-mader Apr 17, 2024
a4bc44a
test: remove `alice.stronghold`, start full e2e test
daniel-mader Apr 17, 2024
44de6f8
docs: describe flow of producing IOTA DIDs
daniel-mader Apr 17, 2024
6dfb3c8
refactor: add `thiserror` to all crates
daniel-mader Apr 17, 2024
a5bfb7c
refactor: rename `Method` => `DidMethod`
daniel-mader Apr 17, 2024
062e503
docs: add usage example
daniel-mader Apr 17, 2024
57166e2
refactor: move `thiserror` to `shared`
daniel-mader Apr 17, 2024
0b0b950
fix: minor changes according to specifications
daniel-mader Apr 17, 2024
8b86c61
chore: use `test-log` for all tests
daniel-mader Apr 17, 2024
602a24c
test: assert against full json document
daniel-mader Apr 17, 2024
7b18f2f
chore: `cargo update`
daniel-mader Apr 17, 2024
4b382fa
Merge branch 'refactor/error-handling' into feat/token-wallet
daniel-mader Apr 17, 2024
61e64a7
refactor: replace `anyhow` with `thiserror`
daniel-mader Apr 17, 2024
d985200
WIP
daniel-mader Apr 18, 2024
8293a75
Merge branch 'dev' into feat/token-wallet
daniel-mader Apr 19, 2024
c989926
refactor: implement review findings
daniel-mader Apr 22, 2024
4f10dab
Merge branch 'refactor/error-handling' into feat/token-wallet
daniel-mader Apr 22, 2024
2865e66
refactor: use second Stronghold for IOTA wallet
daniel-mader Apr 22, 2024
ad082a2
chore: recreate stronghold
daniel-mader Apr 22, 2024
35083d5
chore: get reference to `SecretManager` from `IotaWallet`
daniel-mader Apr 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
421 changes: 229 additions & 192 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ repository = "https://github.com/impierce/did-manager"

[workspace]
resolver = "2"
members = ["consumer", "did_jwk", "did_key", "did_web", "producer", "shared"]
members = [
"consumer",
"did_iota",
"did_jwk",
"did_key",
"did_web",
"producer",
"shared",
"token_wallet",
]

[dependencies]
consumer = { path = "consumer" }
Expand All @@ -26,6 +35,8 @@ identity_iota = { version = "1.2.0" }
identity_stronghold = { version = "1.2.0", features = ["send-sync-storage"] }
iota-sdk = { version = "1.0", features = ["stronghold"] }
log = "0.4"
rand = "0.8"
signature = "2.2"
test-log = "0.2"
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,29 @@ Implementation of [identity.rs](https://github.com/iotaledger/identity.rs) inter
| [did:iota](https://wiki.iota.org/identity.rs/references/specifications/iota-did-method-spec/) | :ballot_box_with_check: | |
| [did:iota:smr](https://wiki.iota.org/identity.rs/references/specifications/iota-did-method-spec/) | :ballot_box_with_check: | |
| [did:iota:rms](https://wiki.iota.org/identity.rs/references/specifications/iota-did-method-spec/) | :ballot_box_with_check: | |

## Usage

> [!NOTE]
> This workspace is structured in a way that keeps the individual DID method implementations as separate crates to allow easier replacement and extensibility.

### Consuming DIDs

```rust
use did_manager::Resolver;
use identity_iota::document::CoreDocument;

let resolver = Resolver::new().await;
let did = "did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL";
let document: CoreDocument = resolver.resolve(did).await.unwrap();
```

### Producing DIDs

```rust
use did_manager::{DidMethod, SecretManager};
use identity_iota::document::CoreDocument;

let secret_manager = SecretManager::generate("/path/to/file", "p4ssw0rd").await.unwrap();
let document: CoreDocument = secret_manager.produce_document(DidMethod::Jwk).await.unwrap();
```
7 changes: 6 additions & 1 deletion consumer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ repository.workspace = true
rust-version.workspace = true

[dependencies]
did_iota = { path = "../did_iota" }
did_jwk = { path = "../did_jwk" }
did_key = { path = "../did_key" }
did_web = { path = "../did_web" }
shared = { path = "../shared" }

identity_iota.workspace = true
iota-sdk.workspace = true
signature.workspace = true
tokio.workspace = true

[dev-dependencies]
test-log.workspace = true
65 changes: 19 additions & 46 deletions consumer/src/resolver.rs
Original file line number Diff line number Diff line change
@@ -1,75 +1,47 @@
use did_iota::consumer::iota_clients;
use did_jwk::consumer::resolve_did_jwk;
use did_key::consumer::resolve_did_key;
use did_web::consumer::resolve_did_web;
use identity_iota::did::CoreDID;
use identity_iota::document::CoreDocument;
use identity_iota::resolver::Resolver as IdentityResolver;
use iota_sdk::client::Client;
use shared::error::ConsumerError;

pub struct Resolver {
pub(crate) resolver: IdentityResolver,
}

impl Resolver {
pub async fn new() -> Self {
let resolver = configure_resolver(IdentityResolver::new()).await;
let resolver = configure_resolver(IdentityResolver::new())
.await
.expect("Failed to configure resolver");
Self { resolver }
}

pub async fn resolve(&self, did: &str) -> std::result::Result<CoreDocument, Box<dyn std::error::Error>> {
pub async fn resolve(&self, did: &str) -> Result<CoreDocument, ConsumerError> {
let did = CoreDID::parse(did)?;
let document: CoreDocument = self.resolver.resolve(&did).await?;
Ok(document)
}
}

// TODO: error handling (return Result), use expect() for Client::builder()
async fn configure_resolver(mut resolver: IdentityResolver) -> IdentityResolver {
async fn configure_resolver(mut resolver: IdentityResolver) -> Result<IdentityResolver, ConsumerError> {
resolver.attach_handler("jwk".to_owned(), resolve_did_jwk);
resolver.attach_handler("key".to_owned(), resolve_did_key);
resolver.attach_handler("web".to_owned(), resolve_did_web);
resolver.attach_multiple_iota_handlers(iota_clients().await?);

// ------------------ IOTA resolvers ------------------
static MAINNET_URL: &str = "https://api.stardust-mainnet.iotaledger.net";
static SHIMMER_URL: &str = "https://api.shimmer.network";
static TESTNET_URL: &str = "https://api.testnet.shimmer.network";
// ----------------------------------------------------

let iota_client: Client = Client::builder()
.with_primary_node(MAINNET_URL, None)
.unwrap()
.finish()
.await
.unwrap();

let smr_client: Client = Client::builder()
.with_primary_node(SHIMMER_URL, None)
.unwrap()
.finish()
.await
.unwrap();

let shimmer_testnet_client: Client = Client::builder()
.with_primary_node(TESTNET_URL, None)
.unwrap()
.finish()
.await
.unwrap();

resolver.attach_multiple_iota_handlers(vec![
("iota", iota_client),
("smr", smr_client),
("rms", shimmer_testnet_client),
]);

resolver
Ok(resolver)
}

#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
use test_log::test;

#[test(tokio::test)]
async fn resolve_all_supported_methods() {
let resolver = Resolver::new().await;
let did = "did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL";
Expand All @@ -83,7 +55,7 @@ mod tests {
// TODO: add more ...
}

#[tokio::test]
#[test(tokio::test)]
async fn fails_on_unsupported_method() {
let resolver = Resolver::new().await;
let did = "did:foo:bar";
Expand All @@ -92,8 +64,8 @@ mod tests {
assert!(result.is_err());
}

#[tokio::test]
#[ignore]
#[test(tokio::test)]
async fn resolves_did_iota() {
let resolver = Resolver::new().await;
let did = "did:iota:0xe4edef97da1257e83cbeb49159cfdd2da6ac971ac447f233f8439cf29376ebfe";
Expand All @@ -105,8 +77,8 @@ mod tests {
);
}

#[tokio::test]
#[ignore]
#[test(tokio::test)]
async fn resolves_did_iota_smr() {
let resolver = Resolver::new().await;
let did = "did:iota:smr:0xe4edef97da1257e83cbeb49159cfdd2da6ac971ac447f233f8439cf29376ebfe";
Expand All @@ -118,16 +90,17 @@ mod tests {
);
}

#[tokio::test]
#[ignore = "unnecessary"]
#[test(tokio::test)]
async fn resolves_did_iota_rms() {
// TODO: are these tests really necessary? (they're essentially just testing the resolver from identity.rs and require internet)
let resolver = Resolver::new().await;
let did = "did:iota:rms:0x4868d61773a9f8e54741261a0e82fc883e299c2614c94b2400e2423d4c5bbe6a";
let did = "did:iota:rms:0x29418b0a0120d10e20d0dacc78896c200ecd1cc1e3b153be482f150859a96739";
let document = resolver.resolve(did).await.unwrap();

assert_eq!(
document.id(),
"did:iota:rms:0x4868d61773a9f8e54741261a0e82fc883e299c2614c94b2400e2423d4c5bbe6a"
"did:iota:rms:0x29418b0a0120d10e20d0dacc78896c200ecd1cc1e3b153be482f150859a96739"
);
}
}
22 changes: 22 additions & 0 deletions did_iota/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "did_iota"
version = "0.1.0"
edition = "2021"
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true

[dependencies]
shared = { path = "../shared" }
token_wallet = { path = "../token_wallet" }

identity_iota.workspace = true
identity_stronghold.workspace = true
iota-sdk.workspace = true
log.workspace = true
tokio.workspace = true

[dev-dependencies]
iota_stronghold = { version = "2.0" }
test-log.workspace = true
43 changes: 43 additions & 0 deletions did_iota/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Producing IOTA DIDs

## Flowchart

```mermaid
flowchart TD
%% ======== Define nodes ========
crypto_wallet([Crypto wallet])
faucet([Faucet])
funding_address[Funding address]
stronghold{{Stronghold}}
governor["`Governor address
State Controller
address`"]

%% ======== Style nodes ========
style faucet stroke-dasharray: 5 5;

%% ======== Flow ========
crypto_wallet --send tokens--> funding_address
faucet -.send tokens.-> funding_address
stronghold --key A, index 0--> funding_address
stronghold --key B, index 0--> governor
funding_address --> AliasOutput
```

### Description

#### Creation

1. Stronghold generates an Ed25519 keypair.
2. Stronghold generates a funding address deterministically.
3. Stronghold generates a `Governor` address deterministically from another key.
4. queries tangle for existing alias outputs for that governor.
5. if none, `DID Manager` produces an `AliasOutput` containing the DID document and determines the cost for storage deposit.
6. User sends tokens to funding address.
7. Funding address pays for storage deposit and instantly returns the remaining tokens to the user's address.
8. DID Manager published the AliasOutput.

#### Destruction

1. DID Manager destroys the Alias Outputs and sends the remaining tokens back to the funding address.
2. New DID can be created or tokens send to another address or returned to same initial address of the user.
36 changes: 36 additions & 0 deletions did_iota/src/consumer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use iota_sdk::client::error::Error;
use iota_sdk::client::Client;

static MAINNET_URL: &str = "https://api.stardust-mainnet.iotaledger.net";
static SHIMMER_URL: &str = "https://api.shimmer.network";
static TESTNET_URL: &str = "https://api.testnet.shimmer.network";

/// Gets all IOTA clients.
pub async fn iota_clients() -> Result<Vec<(&'static str, Client)>, Error> {
let mainnet_client: Client = Client::builder()
.with_primary_node(MAINNET_URL, None)
.unwrap()
.finish()
.await
.unwrap();

let shimmer_client: Client = Client::builder()
.with_primary_node(SHIMMER_URL, None)
.unwrap()
.finish()
.await
.unwrap();

let testnet_client: Client = Client::builder()
.with_primary_node(TESTNET_URL, None)
.unwrap()
.finish()
.await
.unwrap();

Ok(vec![
("iota", mainnet_client),
("smr", shimmer_client),
("rms", testnet_client),
])
}
2 changes: 2 additions & 0 deletions did_iota/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod consumer;
pub mod producer;
Loading