diff --git a/Cargo.toml b/Cargo.toml index c470944fbf..a61b6959d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,7 @@ members = [ "identity_jose", "identity_eddsa_verifier", "identity_sui_name_tbd", - "examples/iota", - "examples/kinesis", + "examples", ] exclude = ["bindings/wasm", "bindings/grpc"] diff --git a/examples/kinesis/0_basic/0_create_did.rs b/examples/0_basic/0_create_did.rs similarity index 88% rename from examples/kinesis/0_basic/0_create_did.rs rename to examples/0_basic/0_create_did.rs index f14462f9ab..0503a7bad3 100644 --- a/examples/kinesis/0_basic/0_create_did.rs +++ b/examples/0_basic/0_create_did.rs @@ -1,10 +1,10 @@ // Copyright 2020-2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; -use examples_kinesis::get_memstorage; +use examples::get_memstorage; /// Demonstrates how to create a DID Document and publish it on chain. /// diff --git a/examples/kinesis/0_basic/1_update_did.rs b/examples/0_basic/1_update_did.rs similarity index 93% rename from examples/kinesis/0_basic/1_update_did.rs rename to examples/0_basic/1_update_did.rs index 60fd715b47..42b418939a 100644 --- a/examples/kinesis/0_basic/1_update_did.rs +++ b/examples/0_basic/1_update_did.rs @@ -1,10 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_memstorage; -use examples_kinesis::TEST_GAS_BUDGET; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; +use examples::TEST_GAS_BUDGET; use identity_iota::core::json; use identity_iota::core::FromJson; use identity_iota::core::Timestamp; diff --git a/examples/kinesis/0_basic/2_resolve_did.rs b/examples/0_basic/2_resolve_did.rs similarity index 91% rename from examples/kinesis/0_basic/2_resolve_did.rs rename to examples/0_basic/2_resolve_did.rs index 0c62c1dffd..52b26f81e8 100644 --- a/examples/kinesis/0_basic/2_resolve_did.rs +++ b/examples/0_basic/2_resolve_did.rs @@ -1,10 +1,10 @@ // Copyright 2020-2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; -use examples_kinesis::get_memstorage; +use examples::get_memstorage; use identity_iota::iota::IotaDocument; use identity_iota::prelude::Resolver; diff --git a/examples/kinesis/0_basic/3_deactivate_did.rs b/examples/0_basic/3_deactivate_did.rs similarity index 88% rename from examples/kinesis/0_basic/3_deactivate_did.rs rename to examples/0_basic/3_deactivate_did.rs index a867ec7f80..1a850bbab7 100644 --- a/examples/kinesis/0_basic/3_deactivate_did.rs +++ b/examples/0_basic/3_deactivate_did.rs @@ -1,10 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_memstorage; -use examples_kinesis::TEST_GAS_BUDGET; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; +use examples::TEST_GAS_BUDGET; use identity_iota::iota::IotaDID; use identity_iota::iota::IotaDocument; @@ -14,8 +14,12 @@ async fn main() -> anyhow::Result<()> { // create new client to interact with chain and get funded account with keys let storage = get_memstorage()?; let identity_client = get_client_and_create_account(&storage).await?; + // create new DID document and publish it let (document, _) = create_kinesis_did_document(&identity_client, &storage).await?; + + println!("Published DID document: {document:#}"); + let did: IotaDID = document.id().clone(); // Deactivate the DID by publishing an empty document. diff --git a/examples/kinesis/0_basic/5_create_vc.rs b/examples/0_basic/5_create_vc.rs similarity index 96% rename from examples/kinesis/0_basic/5_create_vc.rs rename to examples/0_basic/5_create_vc.rs index 0d30958db1..0affc163e2 100644 --- a/examples/kinesis/0_basic/5_create_vc.rs +++ b/examples/0_basic/5_create_vc.rs @@ -9,9 +9,9 @@ //! //! cargo run --release --example 5_create_vc -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_memstorage; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::core::Object; diff --git a/examples/kinesis/0_basic/6_create_vp.rs b/examples/0_basic/6_create_vp.rs similarity index 98% rename from examples/kinesis/0_basic/6_create_vp.rs rename to examples/0_basic/6_create_vp.rs index 77e10327c4..3d3514d25e 100644 --- a/examples/kinesis/0_basic/6_create_vp.rs +++ b/examples/0_basic/6_create_vp.rs @@ -9,9 +9,9 @@ use std::collections::HashMap; -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_memstorage; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::core::Object; use identity_iota::credential::DecodedJwtCredential; diff --git a/examples/kinesis/0_basic/7_revoke_vc.rs b/examples/0_basic/7_revoke_vc.rs similarity index 97% rename from examples/kinesis/0_basic/7_revoke_vc.rs rename to examples/0_basic/7_revoke_vc.rs index e1c5a3c1b6..c4d7225e3e 100644 --- a/examples/kinesis/0_basic/7_revoke_vc.rs +++ b/examples/0_basic/7_revoke_vc.rs @@ -11,10 +11,10 @@ //! cargo run --release --example 7_revoke_vc use anyhow::anyhow; -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_memstorage; -use examples_kinesis::TEST_GAS_BUDGET; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; +use examples::TEST_GAS_BUDGET; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::core::json; use identity_iota::core::FromJson; diff --git a/examples/kinesis/0_basic/8_stronghold.rs b/examples/0_basic/8_stronghold.rs similarity index 91% rename from examples/kinesis/0_basic/8_stronghold.rs rename to examples/0_basic/8_stronghold.rs index 251ea40fe4..df2578db9e 100644 --- a/examples/kinesis/0_basic/8_stronghold.rs +++ b/examples/0_basic/8_stronghold.rs @@ -1,10 +1,10 @@ // Copyright 2020-2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_stronghold_storage; -use examples_kinesis::random_stronghold_path; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_stronghold_storage; +use examples::random_stronghold_path; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::credential::Jws; use identity_iota::document::verifiable::JwsVerificationOptions; diff --git a/examples/1_advanced/10_zkp_revocation.rs b/examples/1_advanced/10_zkp_revocation.rs new file mode 100644 index 0000000000..c1462fb843 --- /dev/null +++ b/examples/1_advanced/10_zkp_revocation.rs @@ -0,0 +1,538 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use examples::get_client_and_create_account; +use examples::{MemStorage, TEST_GAS_BUDGET}; + +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::core::json; +use identity_iota::core::Duration; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::Timestamp; +use identity_iota::core::Url; +use identity_iota::credential::Credential; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::DecodedJwtPresentation; +use identity_iota::credential::FailFast; +use identity_iota::credential::Jpt; +use identity_iota::credential::JptCredentialValidationOptions; +use identity_iota::credential::JptCredentialValidator; +use identity_iota::credential::JptCredentialValidatorUtils; +use identity_iota::credential::JptPresentationValidationOptions; +use identity_iota::credential::JptPresentationValidator; +use identity_iota::credential::JptPresentationValidatorUtils; +use identity_iota::credential::JwpCredentialOptions; +use identity_iota::credential::JwpPresentationOptions; +use identity_iota::credential::Jwt; +use identity_iota::credential::JwtPresentationOptions; +use identity_iota::credential::JwtPresentationValidationOptions; +use identity_iota::credential::JwtPresentationValidator; +use identity_iota::credential::JwtPresentationValidatorUtils; +use identity_iota::credential::JwtValidationError; +use identity_iota::credential::Presentation; +use identity_iota::credential::PresentationBuilder; +use identity_iota::credential::RevocationBitmap; +use identity_iota::credential::RevocationTimeframeStatus; +use identity_iota::credential::SelectiveDisclosurePresentation; +use identity_iota::credential::Status; +use identity_iota::credential::StatusCheck; +use identity_iota::credential::Subject; +use identity_iota::credential::SubjectHolderRelationship; +use identity_iota::did::CoreDID; +use identity_iota::did::DIDUrl; +use identity_iota::did::DID; +use identity_iota::document::verifiable::JwsVerificationOptions; +use identity_iota::document::Service; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::NetworkName; +use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwpDocumentExt; +use identity_iota::storage::JwsSignatureOptions; +use identity_iota::storage::KeyIdMemstore; +use identity_iota::storage::KeyType; +use identity_iota::storage::TimeframeRevocationExtension; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_iota::verification::MethodScope; +use identity_storage::Storage; +use identity_sui_name_tbd::client::{IdentityClient, IotaKeySignature}; +use identity_sui_name_tbd::transaction::Transaction; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use secret_storage::Signer; +use std::thread; +use std::time::Duration as SleepDuration; + +// // Creates a DID with a JWP verification method. +// pub async fn create_did( +// identity_client: &IdentityClient, +// storage: &Storage, +// key_type: KeyType, +// alg: ProofAlgorithm, +// ) -> anyhow::Result<(IotaDocument, String)> +// where +// K: identity_storage::JwkStorage + identity_storage::JwkStorageBbsPlusExt, +// I: identity_storage::KeyIdStorage, +// S: Signer + Sync, +// { +// // Create a new DID document with a placeholder DID. +// // The DID will be derived from the Alias Id of the Alias Output after publishing. +// let mut unpublished: IotaDocument = IotaDocument::new(identity_client.network()); + +// let verification_method_fragment = unpublished +// .generate_method_jwp(storage, key_type, alg, None, MethodScope::VerificationMethod) +// .await?; + +// let document = identity_client +// .publish_did_document(unpublished) +// .execute_with_gas(TEST_GAS_BUDGET, identity_client) +// .await?; + +// Ok((document, verification_method_fragment)) +// } +async fn create_did( + identity_client: &IdentityClient, + storage: &Storage, + key_type: KeyType, + alg: Option, + proof_alg: Option, +) -> anyhow::Result<(IotaDocument, String)> +where + K: identity_storage::JwkStorage + identity_storage::JwkStorageBbsPlusExt, + I: identity_storage::KeyIdStorage, + S: Signer + Sync, +{ + // Get the network name. + let network_name: &NetworkName = identity_client.network(); + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut unpublished: IotaDocument = IotaDocument::new(network_name); + + // New Verification Method containing a BBS+ key + let fragment = if let Some(alg) = alg { + unpublished + .generate_method(storage, key_type, alg, None, MethodScope::VerificationMethod) + .await? + } else if let Some(proof_alg) = proof_alg { + let fragment = unpublished + .generate_method_jwp(storage, key_type, proof_alg, None, MethodScope::VerificationMethod) + .await?; + + // Create a new empty revocation bitmap. No credential is revoked yet. + let revocation_bitmap: RevocationBitmap = RevocationBitmap::new(); + + // Add the revocation bitmap to the DID document of the issuer as a service. + let service_id: DIDUrl = unpublished.id().to_url().join("#my-revocation-service")?; + let service: Service = revocation_bitmap.to_service(service_id)?; + + assert!(unpublished.insert_service(service).is_ok()); + + fragment + } else { + return Err(anyhow::Error::msg("You have to pass at least one algorithm")); + }; + + let document = identity_client + .publish_did_document(unpublished) + .execute_with_gas(TEST_GAS_BUDGET, identity_client) + .await?; + + println!("Published DID document: {document:#}"); + + Ok((document, fragment)) +} + +/// Demonstrates how to create an Anonymous Credential with BBS+. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // =========================================================================== + // Step 1: Create identities and for the issuer and the holder. + // =========================================================================== + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let issuer_identity_client = get_client_and_create_account(&storage_issuer).await?; + + let holder_identity_client = get_client_and_create_account(&storage_holder).await?; + + let (mut issuer_document, fragment_issuer): (IotaDocument, String) = create_did( + &issuer_identity_client, + &storage_issuer, + JwkMemStore::BLS12381G2_KEY_TYPE, + None, + Some(ProofAlgorithm::BLS12381_SHA256), + ) + .await?; + + let (holder_document, fragment_holder): (IotaDocument, String) = create_did( + &holder_identity_client, + &storage_holder, + JwkMemStore::ED25519_KEY_TYPE, + Some(JwsAlgorithm::EdDSA), + None, + ) + .await?; + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "id": holder_document.id().as_str(), + "name": "Alice", + "mainCourses": ["Object-oriented Programming", "Mathematics"], + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // ========================================================================================= + // Step 1: Create a new RevocationTimeframeStatus containing the current validityTimeframe + // ======================================================================================= + let duration = Duration::minutes(1); + // The issuer also chooses a unique `RevocationBitmap` index to be able to revoke it later. + let service_url = issuer_document.id().to_url().join("#my-revocation-service")?; + let credential_index: u32 = 5; + + let start_validity_timeframe = Timestamp::now_utc(); + let status: Status = RevocationTimeframeStatus::new( + Some(start_validity_timeframe), + duration, + service_url.into(), + credential_index, + )? + .into(); + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .status(status) + .build()?; + + let credential_jpt: Jpt = issuer_document + .create_credential_jpt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwpCredentialOptions::default(), + None, + ) + .await?; + + // Validate the credential's proof using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + assert_eq!(credential, decoded_jpt.credential); + + // Issuer sends the Verifiable Credential to the holder. + println!( + "Sending credential (as JPT) to the holder: {}\n", + credential_jpt.as_str() + ); + + // Holder validate the credential and retrieve the JwpIssued, needed to construct the JwpPresented + + let validation_result = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ); + + let decoded_credential = validation_result.unwrap(); + + // =========================================================================== + // Credential's Status check + // =========================================================================== + + // Timeframe check + let timeframe_result = JptCredentialValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &decoded_credential.credential, + None, + StatusCheck::Strict, + ); + + assert!(timeframe_result.is_ok()); + + let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( + &decoded_credential.credential, + &issuer_document, + StatusCheck::Strict, + ); + + assert!(revocation_result.is_ok()); + + // Both checks + + let revocation_result = JptCredentialValidatorUtils::check_timeframes_and_revocation_with_validity_timeframe_2024( + &decoded_credential.credential, + &issuer_document, + None, + StatusCheck::Strict, + ); + + assert!(revocation_result.is_ok()); + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + let method_id = decoded_credential + .decoded_jwp + .get_issuer_protected_header() + .kid() + .unwrap(); + + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_credential.decoded_jwp); + selective_disclosure_presentation + .conceal_in_subject("mainCourses[1]") + .unwrap(); + selective_disclosure_presentation + .conceal_in_subject("degree.name") + .unwrap(); + + let presentation_jpt: Jpt = issuer_document + .create_presentation_jpt( + &mut selective_disclosure_presentation, + method_id, + &JwpPresentationOptions::default().nonce(challenge), + ) + .await?; + + // Holder sends a Presentation JPT to the Verifier. + println!( + "Sending presentation (as JPT) to the verifier: {}\n", + presentation_jpt.as_str() + ); + + // =========================================================================== + // Step 2a: Verifier receives the Presentation and verifies it. + // =========================================================================== + + let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); + + // Verifier validate the Presented Credential and retrieve the JwpPresented + let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( + &presentation_jpt, + &issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ) + .unwrap(); + + // Check validityTimeframe + + let timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &decoded_presented_credential.credential, + None, + StatusCheck::Strict, + ); + + assert!(timeframe_result.is_ok()); + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!( + "Presented Credential successfully validated: {:#}", + decoded_presented_credential.credential + ); + + // =========================================================================== + // Step 2b: Waiting for the next validityTimeframe, will result in the Credential timeframe interval NOT valid + // =========================================================================== + + thread::sleep(SleepDuration::from_secs(61)); + + let timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &decoded_presented_credential.credential, + None, + StatusCheck::Strict, + ); + + // We expect validation to no longer succeed because the credential was NOT updated. + if matches!(timeframe_result.unwrap_err(), JwtValidationError::OutsideTimeframe) { + println!("Validity Timeframe interval NOT valid\n"); + } + + // =========================================================================== + // 3: Update credential + // =========================================================================== + + // =========================================================================== + // 3.1: Issuer sends the holder a challenge and requests a signed Verifiable Presentation. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // The Holder and Issuer also agree that the signature should have an expiry date + // 10 minutes from now. + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + // =========================================================================== + // 3.2: Holder creates and signs a verifiable presentation from the issued credential. + // =========================================================================== + + // Create an unsigned Presentation from the previously issued ZK Verifiable Credential. + let presentation: Presentation = + PresentationBuilder::new(holder_document.id().to_url().into(), Default::default()) + .credential(credential_jpt) + .build()?; + + // Create a JWT verifiable presentation using the holder's verification method + // and include the requested challenge and expiry timestamp. + let presentation_jwt: Jwt = holder_document + .create_presentation_jwt( + &presentation, + &storage_holder, + &fragment_holder, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ) + .await?; + + // =========================================================================== + // 3.3: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + println!( + "Sending presentation (as JWT) to the Issuer: {}\n", + presentation_jwt.as_str() + ); + + // =========================================================================== + // 3.4: Issuer validate Verifiable Presentation and ZK Verifiable Credential. + // =========================================================================== + + // ================================================ + // 3.4.1: Issuer validate Verifiable Presentation. + // ================================================ + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_kinesis_iota_handler((*holder_identity_client).clone()); + + // Resolve the holder's document. + let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; + let holder: IotaDocument = resolver.resolve(&holder_did).await?; + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( + EdDSAJwsVerifier::default(), + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + // ======================================================================= + // 3.4.2: Issuer validate ZK Verifiable Credential inside the Presentation. + // ======================================================================== + + let validation_options: JptCredentialValidationOptions = JptCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + let jpt_credentials: &Vec = &presentation.presentation.verifiable_credential; + + // Extract ZK Verifiable Credential in JPT format + let jpt_vc = jpt_credentials.first().unwrap(); + + // Issuer checks the Credential integrity. + let mut verified_credential_result = + JptCredentialValidator::validate::<_, Object>(jpt_vc, &issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + + // Issuer checks if the Credential has been revoked + let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( + &verified_credential_result.credential, + &issuer_document, + StatusCheck::Strict, + ); + + assert!(!revocation_result.is_err_and(|e| matches!(e, JwtValidationError::Revoked))); + + // =========================================================================== + // 3.5: Issuer ready for Update. + // =========================================================================== + + // Since no errors were thrown during the Verifiable Presentation validation and the verification of inner Credentials + println!( + "Ready for Update - VP successfully validated: {:#?}", + presentation.presentation + ); + + // Issuer updates the credential + let new_credential_jpt = issuer_document + .update( + &storage_issuer, + &fragment_issuer, + None, + duration, + &mut verified_credential_result.decoded_jwp, + ) + .await?; + + // Issuer sends back the credential updated + + println!( + "Sending updated credential (as JPT) to the holder: {}\n", + new_credential_jpt.as_str() + ); + + // Holder check validity of the updated credential + + let validation_result = JptCredentialValidator::validate::<_, Object>( + &new_credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + let timeframe_result = JptCredentialValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &validation_result.credential, + None, + StatusCheck::Strict, + ); + + assert!(!timeframe_result + .as_ref() + .is_err_and(|e| matches!(e, JwtValidationError::OutsideTimeframe))); + println!("Updated credential is VALID!"); + + // =========================================================================== + // Issuer decides to Revoke Holder's Credential + // =========================================================================== + + println!("Issuer decides to revoke the Credential"); + + // Update the RevocationBitmap service in the issuer's DID Document. + // This revokes the credential's unique index. + + issuer_document.revoke_credentials("my-revocation-service", &[credential_index])?; + + // Publish the changes. + issuer_identity_client + .publish_did_document_update(issuer_document.clone(), TEST_GAS_BUDGET) + .await?; + + // Holder checks if his credential has been revoked by the Issuer + let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( + &decoded_credential.credential, + &issuer_document, + StatusCheck::Strict, + ); + assert!(revocation_result.is_err_and(|e| matches!(e, JwtValidationError::Revoked))); + + println!("Credential Revoked!"); + Ok(()) +} diff --git a/examples/1_advanced/11_linked_verifiable_presentation.rs b/examples/1_advanced/11_linked_verifiable_presentation.rs new file mode 100644 index 0000000000..c80a0afa76 --- /dev/null +++ b/examples/1_advanced/11_linked_verifiable_presentation.rs @@ -0,0 +1,162 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Context; + +use examples::get_memstorage; +use examples::{create_kinesis_did_document, get_client_and_create_account, MemStorage, TEST_GAS_BUDGET}; + +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::OrderedSet; +use identity_iota::core::Url; +use identity_iota::credential::CompoundJwtPresentationValidationError; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::DecodedJwtPresentation; +use identity_iota::credential::Jwt; +use identity_iota::credential::JwtPresentationOptions; +use identity_iota::credential::JwtPresentationValidationOptions; +use identity_iota::credential::JwtPresentationValidator; +use identity_iota::credential::JwtPresentationValidatorUtils; +use identity_iota::credential::LinkedVerifiablePresentationService; +use identity_iota::credential::PresentationBuilder; +use identity_iota::credential::Subject; +use identity_iota::did::CoreDID; +use identity_iota::did::DIDUrl; +use identity_iota::did::DID; +use identity_iota::document::verifiable::JwsVerificationOptions; +use identity_iota::iota::IotaDID; +use identity_iota::iota::IotaDocument; +use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::JwsSignatureOptions; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // =========================================================================== + // Step 1: Create identities and Client + // =========================================================================== + + let storage = get_memstorage()?; + + let identity_client = get_client_and_create_account(&storage).await?; + + // create new DID document and publish it + let (mut did_document, fragment) = create_kinesis_did_document(&identity_client, &storage).await?; + + println!("Published DID document: {did_document:#}"); + + let did: IotaDID = did_document.id().clone(); + + // ===================================================== + // Create Linked Verifiable Presentation service + // ===================================================== + + // The DID should link to the following VPs. + let verifiable_presentation_url_1: Url = Url::parse("https://foo.example.com/verifiable-presentation.jwt")?; + let verifiable_presentation_url_2: Url = Url::parse("https://bar.example.com/verifiable-presentation.jsonld")?; + + let mut verifiable_presentation_urls: OrderedSet = OrderedSet::new(); + verifiable_presentation_urls.append(verifiable_presentation_url_1.clone()); + verifiable_presentation_urls.append(verifiable_presentation_url_2.clone()); + + // Create a Linked Verifiable Presentation Service to enable the discovery of the linked VPs through the DID Document. + // This is optional since it is not a hard requirement by the specs. + let service_url: DIDUrl = did.clone().join("#linked-vp")?; + let linked_verifiable_presentation_service = + LinkedVerifiablePresentationService::new(service_url, verifiable_presentation_urls, Object::new())?; + did_document.insert_service(linked_verifiable_presentation_service.into())?; + + let updated_did_document: IotaDocument = identity_client + .publish_did_document_update(did_document, TEST_GAS_BUDGET) + .await?; + + println!("DID document with linked verifiable presentation service: {updated_did_document:#}"); + + // ===================================================== + // Verification + // ===================================================== + + // Init a resolver for resolving DID Documents. + let mut resolver: Resolver = Resolver::new(); + resolver.attach_kinesis_iota_handler((*identity_client).clone()); + + // Resolve the DID Document of the DID that issued the credential. + let did_document: IotaDocument = resolver.resolve(&did).await?; + + // Get the Linked Verifiable Presentation Services from the DID Document. + let linked_verifiable_presentation_services: Vec = did_document + .service() + .iter() + .cloned() + .filter_map(|service| LinkedVerifiablePresentationService::try_from(service).ok()) + .collect(); + + assert_eq!(linked_verifiable_presentation_services.len(), 1); + + // Get the VPs included in the service. + let _verifiable_presentation_urls: &[Url] = linked_verifiable_presentation_services + .first() + .ok_or_else(|| anyhow::anyhow!("expected verifiable presentation urls"))? + .verifiable_presentation_urls(); + + // Fetch the verifiable presentation from the URL (for example using `reqwest`). + // But since the URLs do not point to actual online resource, we will simply create an example JWT. + let presentation_jwt: Jwt = make_vp_jwt(&did_document, &storage, &fragment).await?; + + // Resolve the holder's document. + let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; + let holder: IotaDocument = resolver.resolve(&holder_did).await?; + + // Validate linked presentation. Note that this doesn't validate the included credentials. + let presentation_verifier_options: JwsVerificationOptions = JwsVerificationOptions::default(); + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let validation_result: Result, CompoundJwtPresentationValidationError> = + JwtPresentationValidator::with_signature_verifier(EdDSAJwsVerifier::default()).validate( + &presentation_jwt, + &holder, + &presentation_validation_options, + ); + + assert!(validation_result.is_ok()); + + Ok(()) +} + +async fn make_vp_jwt(did_doc: &IotaDocument, storage: &MemStorage, fragment: &str) -> anyhow::Result { + // first we create a credential encoding it as jwt + let credential = CredentialBuilder::new(Object::default()) + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(did_doc.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(Subject::from_json_value(serde_json::json!({ + "id": did_doc.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?) + .build()?; + let credential = did_doc + .create_credential_jwt(&credential, storage, fragment, &JwsSignatureOptions::default(), None) + .await?; + // then we create a presentation including the just created JWT encoded credential. + let presentation = PresentationBuilder::new(Url::parse(did_doc.id().as_str())?, Object::default()) + .credential(credential) + .build()?; + // we encode the presentation as JWT + did_doc + .create_presentation_jwt( + &presentation, + storage, + fragment, + &JwsSignatureOptions::default(), + &JwtPresentationOptions::default(), + ) + .await + .context("jwt presentation failed") +} diff --git a/examples/kinesis/1_advanced/4_alias_output_history.rs b/examples/1_advanced/4_alias_output_history.rs similarity index 95% rename from examples/kinesis/1_advanced/4_alias_output_history.rs rename to examples/1_advanced/4_alias_output_history.rs index 591bd314a4..ebfe5d2fbb 100644 --- a/examples/kinesis/1_advanced/4_alias_output_history.rs +++ b/examples/1_advanced/4_alias_output_history.rs @@ -1,10 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_memstorage; -use examples_kinesis::TEST_GAS_BUDGET; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; +use examples::TEST_GAS_BUDGET; use identity_iota::core::json; use identity_iota::core::FromJson; use identity_iota::core::Timestamp; diff --git a/examples/kinesis/1_advanced/5_custom_resolution.rs b/examples/1_advanced/5_custom_resolution.rs similarity index 96% rename from examples/kinesis/1_advanced/5_custom_resolution.rs rename to examples/1_advanced/5_custom_resolution.rs index 4d300ebcf8..dae4672fc8 100644 --- a/examples/kinesis/1_advanced/5_custom_resolution.rs +++ b/examples/1_advanced/5_custom_resolution.rs @@ -1,9 +1,9 @@ // Copyright 2020-2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_memstorage; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; use identity_iota::core::FromJson; use identity_iota::core::ToJson; use identity_iota::did::CoreDID; diff --git a/examples/kinesis/1_advanced/6_domain_linkage.rs b/examples/1_advanced/6_domain_linkage.rs similarity index 97% rename from examples/kinesis/1_advanced/6_domain_linkage.rs rename to examples/1_advanced/6_domain_linkage.rs index e87044d46a..397c7ba5ae 100644 --- a/examples/kinesis/1_advanced/6_domain_linkage.rs +++ b/examples/1_advanced/6_domain_linkage.rs @@ -1,10 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_memstorage; -use examples_kinesis::TEST_GAS_BUDGET; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; +use examples::TEST_GAS_BUDGET; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::core::Duration; use identity_iota::core::FromJson; diff --git a/examples/kinesis/1_advanced/7_sd_jwt.rs b/examples/1_advanced/7_sd_jwt.rs similarity index 97% rename from examples/kinesis/1_advanced/7_sd_jwt.rs rename to examples/1_advanced/7_sd_jwt.rs index 2ef15ab2bb..c7c877f31c 100644 --- a/examples/kinesis/1_advanced/7_sd_jwt.rs +++ b/examples/1_advanced/7_sd_jwt.rs @@ -6,10 +6,10 @@ //! //! cargo run --release --example 7_sd_jwt -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_memstorage; -use examples_kinesis::pretty_print_json; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; +use examples::pretty_print_json; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::core::json; use identity_iota::core::FromJson; diff --git a/examples/kinesis/1_advanced/8_status_list_2021.rs b/examples/1_advanced/8_status_list_2021.rs similarity index 97% rename from examples/kinesis/1_advanced/8_status_list_2021.rs rename to examples/1_advanced/8_status_list_2021.rs index 251db957ec..fba8dcc288 100644 --- a/examples/kinesis/1_advanced/8_status_list_2021.rs +++ b/examples/1_advanced/8_status_list_2021.rs @@ -1,9 +1,9 @@ // Copyright 2020-2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use examples_kinesis::create_kinesis_did_document; -use examples_kinesis::get_client_and_create_account; -use examples_kinesis::get_memstorage; +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::core::FromJson; use identity_iota::core::Object; diff --git a/examples/1_advanced/9_zkp.rs b/examples/1_advanced/9_zkp.rs new file mode 100644 index 0000000000..2e4e87d6e5 --- /dev/null +++ b/examples/1_advanced/9_zkp.rs @@ -0,0 +1,239 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use examples::get_client_and_create_account; + +use examples::get_memstorage; +use examples::TEST_GAS_BUDGET; +use identity_iota::core::json; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::Url; +use identity_iota::credential::Credential; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::FailFast; +use identity_iota::credential::Jpt; +use identity_iota::credential::JptCredentialValidationOptions; +use identity_iota::credential::JptCredentialValidator; +use identity_iota::credential::JptCredentialValidatorUtils; +use identity_iota::credential::JptPresentationValidationOptions; +use identity_iota::credential::JptPresentationValidator; +use identity_iota::credential::JptPresentationValidatorUtils; +use identity_iota::credential::JwpCredentialOptions; +use identity_iota::credential::JwpPresentationOptions; +use identity_iota::credential::SelectiveDisclosurePresentation; +use identity_iota::credential::Subject; +use identity_iota::did::CoreDID; +use identity_iota::did::DID; + +use identity_iota::iota::IotaDocument; +use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwpDocumentExt; +use identity_iota::storage::KeyType; +use identity_iota::verification::MethodScope; + +use identity_storage::Storage; +use identity_sui_name_tbd::client::{IdentityClient, IotaKeySignature}; +use identity_sui_name_tbd::transaction::Transaction; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use secret_storage::Signer; + +// Creates a DID with a JWP verification method. +pub async fn create_did( + identity_client: &IdentityClient, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, +) -> anyhow::Result<(IotaDocument, String)> +where + K: identity_storage::JwkStorage + identity_storage::JwkStorageBbsPlusExt, + I: identity_storage::KeyIdStorage, + S: Signer + Sync, +{ + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut unpublished: IotaDocument = IotaDocument::new(identity_client.network()); + + let verification_method_fragment = unpublished + .generate_method_jwp(storage, key_type, alg, None, MethodScope::VerificationMethod) + .await?; + + let document = identity_client + .publish_did_document(unpublished) + .execute_with_gas(TEST_GAS_BUDGET, identity_client) + .await?; + + Ok((document, verification_method_fragment)) +} + +/// Demonstrates how to create an Anonymous Credential with BBS+. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // =========================================================================== + // Step 1: Create identities and Client + // =========================================================================== + + let storage_issuer = get_memstorage()?; + + let identity_client = get_client_and_create_account(&storage_issuer).await?; + + let (issuer_document, fragment_issuer): (IotaDocument, String) = create_did( + &identity_client, + &storage_issuer, + JwkMemStore::BLS12381G2_KEY_TYPE, + ProofAlgorithm::BLS12381_SHA256, + ) + .await?; + + // =========================================================================== + // Step 2: Issuer creates and signs a Verifiable Credential with BBS algorithm. + // =========================================================================== + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "name": "Alice", + "mainCourses": ["Object-oriented Programming", "Mathematics"], + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jpt: Jpt = issuer_document + .create_credential_jpt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwpCredentialOptions::default(), + None, + ) + .await?; + + // Validate the credential's proof using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + assert_eq!(credential, decoded_jpt.credential); + + // =========================================================================== + // Step 3: Issuer sends the Verifiable Credential to the holder. + // =========================================================================== + println!( + "Sending credential (as JPT) to the holder: {}\n", + credential_jpt.as_str() + ); + + // ============================================================================================ + // Step 4: Holder resolves Issuer's DID, retrieve Issuer's document and validate the Credential + // ============================================================================================ + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_kinesis_iota_handler((*identity_client).clone()); + + // Holder resolves issuer's DID + let issuer: CoreDID = JptCredentialValidatorUtils::extract_issuer_from_issued_jpt(&credential_jpt).unwrap(); + let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; + + // Holder validates the credential and retrieve the JwpIssued, needed to construct the JwpPresented + let decoded_credential = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + // =========================================================================== + // Step 5: Verifier sends the holder a challenge and requests a Presentation. + // + // Please be aware that when we mention "Presentation," we are not alluding to the Verifiable Presentation standard as defined by W3C (https://www.w3.org/TR/vc-data-model/#presentations). + // Instead, our reference is to a JWP Presentation (https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-presented-form), which differs from the W3C standard. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // ========================================================================================================= + // Step 6: Holder engages in the Selective Disclosure of credential's attributes. + // ========================================================================================================= + + let method_id = decoded_credential + .decoded_jwp + .get_issuer_protected_header() + .kid() + .unwrap(); + + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_credential.decoded_jwp); + selective_disclosure_presentation + .conceal_in_subject("mainCourses[1]") + .unwrap(); + selective_disclosure_presentation + .conceal_in_subject("degree.name") + .unwrap(); + + // ======================================================================================================================================= + // Step 7: Holder needs Issuer's Public Key to compute the Signature Proof of Knowledge and construct the Presentation + // JPT. + // ======================================================================================================================================= + + // Construct a JPT(JWP in the Presentation form) representing the Selectively Disclosed Verifiable Credential + let presentation_jpt: Jpt = issuer_document + .create_presentation_jpt( + &mut selective_disclosure_presentation, + method_id, + &JwpPresentationOptions::default().nonce(challenge), + ) + .await?; + + // =========================================================================== + // Step 8: Holder sends a Presentation JPT to the Verifier. + // =========================================================================== + + println!( + "Sending presentation (as JPT) to the verifier: {}\n", + presentation_jpt.as_str() + ); + + // =========================================================================== + // Step 9: Verifier receives the Presentation and verifies it. + // =========================================================================== + + // Verifier resolve Issuer DID + let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); + let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; + + let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); + + // Verifier validate the Presented Credential and retrieve the JwpPresented + let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( + &presentation_jpt, + &issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ) + .unwrap(); + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!( + "Presented Credential successfully validated: {:#?}", + decoded_presented_credential.credential + ); + + Ok(()) +} diff --git a/examples/kinesis/Cargo.toml b/examples/Cargo.toml similarity index 70% rename from examples/kinesis/Cargo.toml rename to examples/Cargo.toml index 18d0f9475d..b8011a5e88 100644 --- a/examples/kinesis/Cargo.toml +++ b/examples/Cargo.toml @@ -1,26 +1,27 @@ [package] -name = "examples_kinesis" -version = "1.2.0" +name = "examples" +version = "1.4.0" authors = ["IOTA Stiftung"] edition = "2021" publish = false [dependencies] anyhow = "1.0.62" -identity_eddsa_verifier = { path = "../../identity_eddsa_verifier", default-features = false, features = [ +identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false, features = [ "ed25519", ] } -identity_storage = { path = "../../identity_storage" } -identity_stronghold = { path = "../../identity_stronghold", default-features = false, features = [ +identity_storage = { path = "../identity_storage" } +identity_stronghold = { path = "../identity_stronghold", default-features = false, features = [ "send-sync-storage", ] } -identity_sui_name_tbd = { path = "../../identity_sui_name_tbd" } +identity_sui_name_tbd = { path = "../identity_sui_name_tbd" } iota-sdk = { git = "https://github.com/iotaledger/iota.git", package = "iota-sdk", tag = "v0.7.0-alpha" } iota-sdk-legacy = { package = "iota-sdk", version = "1.0", default-features = false, features = [ "tls", "client", "stronghold", ] } +json-proof-token.workspace = true primitive-types = "0.12.1" rand = "0.8.5" sd-jwt-payload = { version = "0.2.1", default-features = false, features = [ @@ -31,7 +32,7 @@ serde_json = { version = "1.0", default-features = false } tokio = { version = "1.29", default-features = false, features = ["rt"] } [dependencies.identity_iota] -path = "../../identity_iota" +path = "../identity_iota" default-features = false features = [ "iota-client", @@ -64,9 +65,6 @@ name = "2_resolve_did" path = "0_basic/3_deactivate_did.rs" name = "3_deactivate_did" -# [[example]] -# path = "0_basic/4_delete_did.rs" -# name = "4_delete_did" [[example]] path = "0_basic/5_create_vc.rs" @@ -84,22 +82,6 @@ name = "7_revoke_vc" path = "0_basic/8_stronghold.rs" name = "8_stronghold" -# [[example]] -# path = "1_advanced/0_did_controls_did.rs" -# name = "0_did_controls_did" - -# [[example]] -# path = "1_advanced/1_did_issues_nft.rs" -# name = "1_did_issues_nft" - -# [[example]] -# path = "1_advanced/2_nft_owns_did.rs" -# name = "2_nft_owns_did" - -# [[example]] -# path = "1_advanced/3_did_issues_tokens.rs" -# name = "3_did_issues_tokens" - [[example]] path = "1_advanced/4_alias_output_history.rs" name = "4_alias_output_history" @@ -119,3 +101,15 @@ name = "7_sd_jwt" [[example]] path = "1_advanced/8_status_list_2021.rs" name = "8_status_list_2021" + +[[example]] +path = "1_advanced/9_zkp.rs" +name = "9_zkp" + +[[example]] +path = "1_advanced/10_zkp_revocation.rs" +name = "10_zkp_revocation" + +[[example]] +path = "1_advanced/11_linked_verifiable_presentation.rs" +name = "11_linked_verifiable_presentation" diff --git a/examples/iota/README.md b/examples/README.md similarity index 100% rename from examples/iota/README.md rename to examples/README.md diff --git a/examples/iota/0_basic/0_create_did.rs b/examples/iota/0_basic/0_create_did.rs deleted file mode 100644 index 12192efe00..0000000000 --- a/examples/iota/0_basic/0_create_did.rs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::get_address_with_funds; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::iota::NetworkName; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use identity_iota::verification::jws::JwsAlgorithm; -use identity_iota::verification::MethodScope; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::output::AliasOutput; - -/// Demonstrates how to create a DID Document and publish it in a new Alias Output. -/// -/// In this example we connect to a locally running private network, but it can be adapted -/// to run on any IOTA node by setting the network and faucet endpoints. -/// -/// See the following instructions on running your own private network -/// https://github.com/iotaledger/hornet/tree/develop/private_tangle -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // The API endpoint of an IOTA node, e.g. Hornet. - let api_endpoint: &str = "http://localhost"; - - // The faucet endpoint allows requesting funds for testing purposes. - let faucet_endpoint: &str = "http://localhost/faucet/api/enqueue"; - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(api_endpoint, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Get an address with funds for testing. - let address: Address = get_address_with_funds(&client, &secret_manager, faucet_endpoint).await?; - - // Get the Bech32 human-readable part (HRP) of the network. - let network_name: NetworkName = client.network_name().await?; - - // Create a new DID document with a placeholder DID. - // The DID will be derived from the Alias Id of the Alias Output after publishing. - let mut document: IotaDocument = IotaDocument::new(&network_name); - - // Insert a new Ed25519 verification method in the DID document. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - document - .generate_method( - &storage, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA, - None, - MethodScope::VerificationMethod, - ) - .await?; - - // Construct an Alias Output containing the DID document, with the wallet address - // set as both the state controller and governor. - let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; - - // Publish the Alias Output and get the published DID document. - let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; - println!("Published DID document: {document:#}"); - - Ok(()) -} diff --git a/examples/iota/0_basic/1_update_did.rs b/examples/iota/0_basic/1_update_did.rs deleted file mode 100644 index eb7bef1b7a..0000000000 --- a/examples/iota/0_basic/1_update_did.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_iota::core::json; -use identity_iota::core::FromJson; -use identity_iota::core::Timestamp; -use identity_iota::did::DIDUrl; -use identity_iota::did::DID; -use identity_iota::document::Service; -use identity_iota::iota::block::address::Address; -use identity_iota::iota::block::output::RentStructure; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDID; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use identity_iota::verification::jws::JwsAlgorithm; -use identity_iota::verification::MethodRelationship; -use identity_iota::verification::MethodScope; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::output::AliasOutput; -use iota_sdk::types::block::output::AliasOutputBuilder; - -/// Demonstrates how to update a DID document in an existing Alias Output. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let mut secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create a new DID in an Alias Output for us to modify. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, document, fragment_1): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager, &storage).await?; - let did: IotaDID = document.id().clone(); - - // Resolve the latest state of the document. - let mut document: IotaDocument = client.resolve_did(&did).await?; - - // Insert a new Ed25519 verification method in the DID document. - let fragment_2: String = document - .generate_method( - &storage, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA, - None, - MethodScope::VerificationMethod, - ) - .await?; - - // Attach a new method relationship to the inserted method. - document.attach_method_relationship( - &document.id().to_url().join(format!("#{fragment_2}"))?, - MethodRelationship::Authentication, - )?; - - // Add a new Service. - let service: Service = Service::from_json_value(json!({ - "id": document.id().to_url().join("#linked-domain")?, - "type": "LinkedDomains", - "serviceEndpoint": "https://iota.org/" - }))?; - assert!(document.insert_service(service).is_ok()); - document.metadata.updated = Some(Timestamp::now_utc()); - - // Remove a verification method. - let original_method: DIDUrl = document.resolve_method(fragment_1.as_str(), None).unwrap().id().clone(); - document.purge_method(&storage, &original_method).await.unwrap(); - - // Resolve the latest output and update it with the given document. - let alias_output: AliasOutput = client.update_did_output(document.clone()).await?; - - // Because the size of the DID document increased, we have to increase the allocated storage deposit. - // This increases the deposit amount to the new minimum. - let rent_structure: RentStructure = client.get_rent_structure().await?; - let alias_output: AliasOutput = AliasOutputBuilder::from(&alias_output) - .with_minimum_storage_deposit(rent_structure) - .finish()?; - - // Publish the updated Alias Output. - let updated: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; - println!("Updated DID document: {updated:#}"); - - Ok(()) -} diff --git a/examples/iota/0_basic/2_resolve_did.rs b/examples/iota/0_basic/2_resolve_did.rs deleted file mode 100644 index 7e0fc1a395..0000000000 --- a/examples/iota/0_basic/2_resolve_did.rs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_iota::iota::block::address::Address; - -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::prelude::Resolver; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::output::AliasOutput; - -/// Demonstrates how to resolve an existing DID in an Alias Output. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let mut secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create a new DID in an Alias Output for us to resolve. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, document, _): (Address, IotaDocument, String) = create_did(&client, &mut secret_manager, &storage).await?; - - let did = document.id().clone(); - - // We can resolve a `IotaDID` with the client itself. - // Resolve the associated Alias Output and extract the DID document from it. - let client_document: IotaDocument = client.resolve_did(&did).await?; - println!("Client resolved DID Document: {client_document:#}"); - - // We can also create a `Resolver` that has additional convenience methods, - // for example to resolve presentation issuers or to verify presentations. - let mut resolver = Resolver::::new(); - - // We need to register a handler that can resolve IOTA DIDs. - // This convenience method only requires us to provide a client. - resolver.attach_iota_handler(client.clone()); - - let resolver_document: IotaDocument = resolver.resolve(&did).await.unwrap(); - - // Client and Resolver resolve to the same document in this case. - assert_eq!(client_document, resolver_document); - - // We can also resolve the Alias Output directly. - let alias_output: AliasOutput = client.resolve_did_output(&did).await?; - - println!("The Alias Output holds {} tokens", alias_output.amount()); - - Ok(()) -} diff --git a/examples/iota/0_basic/3_deactivate_did.rs b/examples/iota/0_basic/3_deactivate_did.rs deleted file mode 100644 index eef5eca029..0000000000 --- a/examples/iota/0_basic/3_deactivate_did.rs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_iota::iota::block::address::Address; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDID; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::output::AliasOutput; -use iota_sdk::types::block::output::AliasOutputBuilder; - -/// Demonstrates how to deactivate a DID in an Alias Output. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let mut secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create a new DID in an Alias Output for us to modify. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, document, _): (Address, IotaDocument, String) = create_did(&client, &mut secret_manager, &storage).await?; - let did: IotaDID = document.id().clone(); - - // Resolve the latest state of the DID document. - let document: IotaDocument = client.resolve_did(&did).await?; - - // Deactivate the DID by publishing an empty document. - // This process can be reversed since the Alias Output is not destroyed. - // Deactivation may only be performed by the state controller of the Alias Output. - let deactivated_output: AliasOutput = client.deactivate_did_output(&did).await?; - - // Optional: reduce and reclaim the storage deposit, sending the tokens to the state controller. - let rent_structure = client.get_rent_structure().await?; - let deactivated_output = AliasOutputBuilder::from(&deactivated_output) - .with_minimum_storage_deposit(rent_structure) - .finish()?; - - // Publish the deactivated DID document. - let _ = client.publish_did_output(&secret_manager, deactivated_output).await?; - - // Resolving a deactivated DID returns an empty DID document - // with its `deactivated` metadata field set to `true`. - let deactivated: IotaDocument = client.resolve_did(&did).await?; - println!("Deactivated DID document: {deactivated:#}"); - assert_eq!(deactivated.metadata.deactivated, Some(true)); - - // Re-activate the DID by publishing a valid DID document. - let reactivated_output: AliasOutput = client.update_did_output(document.clone()).await?; - - // Increase the storage deposit to the minimum again, if it was reclaimed during deactivation. - let rent_structure = client.get_rent_structure().await?; - let reactivated_output = AliasOutputBuilder::from(&reactivated_output) - .with_minimum_storage_deposit(rent_structure) - .finish()?; - client.publish_did_output(&secret_manager, reactivated_output).await?; - - // Resolve the reactivated DID document. - let reactivated: IotaDocument = client.resolve_did(&did).await?; - assert_eq!(document, reactivated); - assert!(!reactivated.metadata.deactivated.unwrap_or_default()); - - Ok(()) -} diff --git a/examples/iota/0_basic/4_delete_did.rs b/examples/iota/0_basic/4_delete_did.rs deleted file mode 100644 index 662bc5e868..0000000000 --- a/examples/iota/0_basic/4_delete_did.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_iota::iota::Error; -use identity_iota::iota::IotaClientExt; - -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; - -/// Demonstrates how to delete a DID in an Alias Output, reclaiming the storage deposit. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let mut secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create a new DID in an Alias Output for us to modify. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (address, document, _): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager, &storage).await?; - let did = document.id().clone(); - - // Deletes the Alias Output and its contained DID Document, rendering the DID permanently destroyed. - // This operation is *not* reversible. - // Deletion can only be done by the governor of the Alias Output. - client.delete_did_output(&secret_manager, address, &did).await?; - - // Attempting to resolve a deleted DID results in a `NoOutput` error. - let error: Error = client.resolve_did(&did).await.unwrap_err(); - - assert!(matches!( - error, - identity_iota::iota::Error::DIDResolutionError(iota_sdk::client::Error::Node( - iota_sdk::client::node_api::error::Error::NotFound(..) - )) - )); - - Ok(()) -} diff --git a/examples/iota/0_basic/5_create_vc.rs b/examples/iota/0_basic/5_create_vc.rs deleted file mode 100644 index d1526756c8..0000000000 --- a/examples/iota/0_basic/5_create_vc.rs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! This example shows how to create a Verifiable Credential and validate it. -//! In this example, alice takes the role of the subject, while we also have an issuer. -//! The issuer signs a UniversityDegreeCredential type verifiable credential with Alice's name and DID. -//! This Verifiable Credential can be verified by anyone, allowing Alice to take control of it and share it with -//! whomever they please. -//! -//! cargo run --release --example 5_create_vc - -use examples_iota::create_did; -use examples_iota::MemStorage; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::core::Object; - -use identity_iota::credential::DecodedJwtCredential; -use identity_iota::credential::Jwt; -use identity_iota::credential::JwtCredentialValidationOptions; -use identity_iota::credential::JwtCredentialValidator; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwsSignatureOptions; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; - -use examples_iota::random_stronghold_path; -use examples_iota::API_ENDPOINT; -use identity_iota::core::json; -use identity_iota::core::FromJson; -use identity_iota::core::Url; -use identity_iota::credential::Credential; -use identity_iota::credential::CredentialBuilder; -use identity_iota::credential::FailFast; -use identity_iota::credential::Subject; -use identity_iota::did::DID; -use identity_iota::iota::IotaDocument; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create an identity for the issuer with one verification method `key-1`. - let mut secret_manager_issuer: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_1".to_owned())) - .build(random_stronghold_path())?, - ); - let issuer_storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, issuer_document, fragment): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_issuer, &issuer_storage).await?; - - // Create an identity for the holder, in this case also the subject. - let mut secret_manager_alice: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?, - ); - let alice_storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, alice_document, _): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_alice, &alice_storage).await?; - - // Create a credential subject indicating the degree earned by Alice. - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - // Build credential using subject above and issuer. - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt( - &credential, - &issuer_storage, - &fragment, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - // Before sending this credential to the holder the issuer wants to validate that some properties - // of the credential satisfy their expectations. - - // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, - // that the issuance date is not in the future and that the expiration date is not in the past: - let decoded_credential: DecodedJwtCredential = - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); - - println!("VC successfully validated"); - - println!("Credential JSON > {:#}", decoded_credential.credential); - - Ok(()) -} diff --git a/examples/iota/0_basic/6_create_vp.rs b/examples/iota/0_basic/6_create_vp.rs deleted file mode 100644 index f7c501dcb3..0000000000 --- a/examples/iota/0_basic/6_create_vp.rs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! This example shows how to create a Verifiable Presentation and validate it. -//! A Verifiable Presentation is the format in which a (collection of) Verifiable Credential(s) gets shared. -//! It is signed by the subject, to prove control over the Verifiable Credential with a nonce or timestamp. -//! -//! cargo run --release --example 6_create_vp - -use std::collections::HashMap; - -use examples_iota::create_did; -use examples_iota::MemStorage; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::core::Object; -use identity_iota::credential::DecodedJwtCredential; -use identity_iota::credential::DecodedJwtPresentation; -use identity_iota::credential::Jwt; -use identity_iota::credential::JwtCredentialValidatorUtils; -use identity_iota::credential::JwtPresentationOptions; -use identity_iota::credential::JwtPresentationValidationOptions; -use identity_iota::credential::JwtPresentationValidator; -use identity_iota::credential::JwtPresentationValidatorUtils; -use identity_iota::credential::Presentation; -use identity_iota::credential::PresentationBuilder; -use identity_iota::did::CoreDID; -use identity_iota::document::verifiable::JwsVerificationOptions; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwsSignatureOptions; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; - -use examples_iota::random_stronghold_path; -use examples_iota::API_ENDPOINT; -use identity_iota::core::json; -use identity_iota::core::Duration; -use identity_iota::core::FromJson; -use identity_iota::core::Timestamp; -use identity_iota::core::Url; -use identity_iota::credential::Credential; -use identity_iota::credential::CredentialBuilder; -use identity_iota::credential::FailFast; -use identity_iota::credential::JwtCredentialValidationOptions; -use identity_iota::credential::JwtCredentialValidator; -use identity_iota::credential::Subject; -use identity_iota::credential::SubjectHolderRelationship; -use identity_iota::did::DID; -use identity_iota::iota::IotaDocument; -use identity_iota::resolver::Resolver; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // =========================================================================== - // Step 1: Create identities for the issuer and the holder. - // =========================================================================== - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create an identity for the issuer with one verification method `key-1`. - let mut secret_manager_issuer: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_1".to_owned())) - .build(random_stronghold_path())?, - ); - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_issuer, &storage_issuer).await?; - - // Create an identity for the holder, in this case also the subject. - let mut secret_manager_alice: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?, - ); - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, alice_document, fragment_alice): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_alice, &storage_alice).await?; - - // =========================================================================== - // Step 2: Issuer creates and signs a Verifiable Credential. - // =========================================================================== - - // Create a credential subject indicating the degree earned by Alice. - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - // Build credential using subject above and issuer. - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - // Before sending this credential to the holder the issuer wants to validate that some properties - // of the credential satisfy their expectations. - - // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, - // that the issuance date is not in the future and that the expiration date is not in the past: - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); - - println!("VC successfully validated"); - - // =========================================================================== - // Step 3: Issuer sends the Verifiable Credential to the holder. - // =========================================================================== - println!("Sending credential (as JWT) to the holder: {credential:#}"); - - // =========================================================================== - // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. - // =========================================================================== - - // A unique random challenge generated by the requester per presentation can mitigate replay attacks. - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - // The verifier and holder also agree that the signature should have an expiry date - // 10 minutes from now. - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - // =========================================================================== - // Step 5: Holder creates and signs a verifiable presentation from the issued credential. - // =========================================================================== - - // Create an unsigned Presentation from the previously issued Verifiable Credential. - let presentation: Presentation = - PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) - .credential(credential_jwt) - .build()?; - - // Create a JWT verifiable presentation using the holder's verification method - // and include the requested challenge and expiry timestamp. - let presentation_jwt: Jwt = alice_document - .create_presentation_jwt( - &presentation, - &storage_alice, - &fragment_alice, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ) - .await?; - - // =========================================================================== - // Step 6: Holder sends a verifiable presentation to the verifier. - // =========================================================================== - println!("Sending presentation (as JWT) to the verifier: {presentation:#}"); - - // =========================================================================== - // Step 7: Verifier receives the Verifiable Presentation and verifies it. - // =========================================================================== - - // The verifier wants the following requirements to be satisfied: - // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) - // - JWT verification of the credentials. - // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property - // - The issuance date must not be in the future. - - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - - let mut resolver: Resolver = Resolver::new(); - resolver.attach_iota_handler(client); - - // Resolve the holder's document. - let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; - let holder: IotaDocument = resolver.resolve(&holder_did).await?; - - // Validate presentation. Note that this doesn't validate the included credentials. - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( - EdDSAJwsVerifier::default(), - ) - .validate(&presentation_jwt, &holder, &presentation_validation_options)?; - - // Concurrently resolve the issuers' documents. - let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; - let issuers: Vec = jwt_credentials - .iter() - .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) - .collect::, _>>()?; - let issuers_documents: HashMap = resolver.resolve_multiple(&issuers).await?; - - // Validate the credentials in the presentation. - let credential_validator: JwtCredentialValidator = - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()); - let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. - let issuer_document: &IotaDocument = &issuers_documents[&issuers[index]]; - - let _decoded_credential: DecodedJwtCredential = credential_validator - .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - } - - // Since no errors were thrown by `verify_presentation` we know that the validation was successful. - println!("VP successfully validated: {:#?}", presentation.presentation); - - // Note that we did not declare a latest allowed issuance date for credentials. This is because we only want to check - // that the credentials do not have an issuance date in the future which is a default check. - Ok(()) -} diff --git a/examples/iota/0_basic/7_revoke_vc.rs b/examples/iota/0_basic/7_revoke_vc.rs deleted file mode 100644 index c4d5b1bf32..0000000000 --- a/examples/iota/0_basic/7_revoke_vc.rs +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! This example shows how to revoke a verifiable credential. -//! It demonstrates two methods for revocation. The first uses a revocation bitmap of type `RevocationBitmap2022`, -//! while the second method simply removes the verification method (public key) that signed the credential -//! from the DID Document of the issuer. -//! -//! Note: make sure `API_ENDPOINT` and `FAUCET_ENDPOINT` are set to the correct network endpoints. -//! -//! cargo run --release --example 7_revoke_vc - -use anyhow::anyhow; -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::core::json; -use identity_iota::core::FromJson; -use identity_iota::core::Object; -use identity_iota::core::Url; -use identity_iota::credential::CompoundCredentialValidationError; -use identity_iota::credential::Credential; -use identity_iota::credential::CredentialBuilder; -use identity_iota::credential::DecodedJwtCredential; -use identity_iota::credential::FailFast; -use identity_iota::credential::Jwt; -use identity_iota::credential::JwtCredentialValidationOptions; -use identity_iota::credential::JwtCredentialValidator; -use identity_iota::credential::JwtCredentialValidatorUtils; -use identity_iota::credential::JwtValidationError; -use identity_iota::credential::RevocationBitmap; -use identity_iota::credential::RevocationBitmapStatus; -use identity_iota::credential::Status; -use identity_iota::credential::Subject; -use identity_iota::did::DIDUrl; -use identity_iota::did::DID; -use identity_iota::document::Service; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::prelude::IotaDID; -use identity_iota::resolver::Resolver; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwsSignatureOptions; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::output::AliasOutput; -use iota_sdk::types::block::output::AliasOutputBuilder; -use iota_sdk::types::block::output::RentStructure; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // =========================================================================== - // Create a Verifiable Credential. - // =========================================================================== - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - let mut secret_manager_issuer: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_1".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create an identity for the issuer with one verification method `key-1`. - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, mut issuer_document, fragment_issuer): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_issuer, &storage_issuer).await?; - - // Create an identity for the holder, in this case also the subject. - let mut secret_manager_alice: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?, - ); - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, alice_document, _): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_alice, &storage_alice).await?; - - // Create a new empty revocation bitmap. No credential is revoked yet. - let revocation_bitmap: RevocationBitmap = RevocationBitmap::new(); - - // Add the revocation bitmap to the DID document of the issuer as a service. - let service_id: DIDUrl = issuer_document.id().to_url().join("#my-revocation-service")?; - let service: Service = revocation_bitmap.to_service(service_id)?; - - assert!(issuer_document.insert_service(service).is_ok()); - - // Resolve the latest output and update it with the given document. - let alias_output: AliasOutput = client.update_did_output(issuer_document.clone()).await?; - - // Because the size of the DID document increased, we have to increase the allocated storage deposit. - // This increases the deposit amount to the new minimum. - let rent_structure: RentStructure = client.get_rent_structure().await?; - let alias_output: AliasOutput = AliasOutputBuilder::from(&alias_output) - .with_minimum_storage_deposit(rent_structure) - .finish()?; - - // Publish the updated Alias Output. - issuer_document = client.publish_did_output(&secret_manager_issuer, alias_output).await?; - - // Create a credential subject indicating the degree earned by Alice. - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - // Create an unsigned `UniversityDegree` credential for Alice. - // The issuer also chooses a unique `RevocationBitmap` index to be able to revoke it later. - let service_url = issuer_document.id().to_url().join("#my-revocation-service")?; - let credential_index: u32 = 5; - let status: Status = RevocationBitmapStatus::new(service_url, credential_index).into(); - - // Build credential using subject above, status, and issuer. - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .status(status) - .subject(subject) - .build()?; - - println!("Credential JSON > {credential:#}"); - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - let validator: JwtCredentialValidator = - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()); - // Validate the credential's signature using the issuer's DID Document. - validator.validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - )?; - - // =========================================================================== - // Revocation of the Verifiable Credential. - // =========================================================================== - - // Update the RevocationBitmap service in the issuer's DID Document. - // This revokes the credential's unique index. - issuer_document.revoke_credentials("my-revocation-service", &[credential_index])?; - - // Publish the changes. - let alias_output: AliasOutput = client.update_did_output(issuer_document.clone()).await?; - let rent_structure: RentStructure = client.get_rent_structure().await?; - let alias_output: AliasOutput = AliasOutputBuilder::from(&alias_output) - .with_minimum_storage_deposit(rent_structure) - .finish()?; - issuer_document = client.publish_did_output(&secret_manager_issuer, alias_output).await?; - - let validation_result: std::result::Result = validator - .validate( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ); - - // We expect validation to no longer succeed because the credential was revoked. - assert!(matches!( - validation_result.unwrap_err().validation_errors[0], - JwtValidationError::Revoked - )); - - // =========================================================================== - // Alternative revocation of the Verifiable Credential. - // =========================================================================== - - // By removing the verification method, that signed the credential, from the issuer's DID document, - // we effectively revoke the credential, as it will no longer be possible to validate the signature. - let original_method: DIDUrl = issuer_document - .resolve_method(&fragment_issuer, None) - .ok_or_else(|| anyhow!("expected method to exist"))? - .id() - .clone(); - issuer_document - .remove_method(&original_method) - .ok_or_else(|| anyhow!("expected method to exist"))?; - - // Publish the changes. - let alias_output: AliasOutput = client.update_did_output(issuer_document.clone()).await?; - let alias_output: AliasOutput = AliasOutputBuilder::from(&alias_output).finish()?; - client.publish_did_output(&secret_manager_issuer, alias_output).await?; - - // We expect the verifiable credential to be revoked. - let mut resolver: Resolver = Resolver::new(); - resolver.attach_iota_handler(client); - let resolved_issuer_did: IotaDID = JwtCredentialValidatorUtils::extract_issuer_from_jwt(&credential_jwt)?; - let resolved_issuer_doc: IotaDocument = resolver.resolve(&resolved_issuer_did).await?; - - let validation_result = validator.validate::<_, Object>( - &credential_jwt, - &resolved_issuer_doc, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ); - - println!("VC validation result: {validation_result:?}"); - assert!(validation_result.is_err()); - - println!("Credential successfully revoked!"); - - Ok(()) -} diff --git a/examples/iota/0_basic/8_stronghold.rs b/examples/iota/0_basic/8_stronghold.rs deleted file mode 100644 index e0ff9fbe67..0000000000 --- a/examples/iota/0_basic/8_stronghold.rs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::get_address_with_funds; -use examples_iota::random_stronghold_path; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::credential::Jws; -use identity_iota::document::verifiable::JwsVerificationOptions; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::iota::NetworkName; -use identity_iota::resolver::Resolver; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwsSignatureOptions; -use identity_iota::storage::Storage; -use identity_iota::verification::jws::DecodedJws; -use identity_iota::verification::jws::JwsAlgorithm; -use identity_iota::verification::MethodScope; -use identity_stronghold::StrongholdStorage; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::output::AliasOutput; - -/// Demonstrates how to use stronghold for secure storage. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // The API endpoint of an IOTA node, e.g. Hornet. - let api_endpoint: &str = "http://localhost"; - - // The faucet endpoint allows requesting funds for testing purposes. - let faucet_endpoint: &str = "http://localhost/faucet/api/enqueue"; - - // Stronghold snapshot path. - let path = random_stronghold_path(); - - // Stronghold password. - let password = Password::from("secure_password".to_owned()); - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(api_endpoint, None)? - .finish() - .await?; - - let stronghold = StrongholdSecretManager::builder() - .password(password.clone()) - .build(path.clone())?; - - // Create a `StrongholdStorage`. - // `StrongholdStorage` creates internally a `SecretManager` that can be - // referenced to avoid creating multiple instances around the same stronghold snapshot. - let stronghold_storage = StrongholdStorage::new(stronghold); - - // Create a DID document. - let address: Address = - get_address_with_funds(&client, stronghold_storage.as_secret_manager(), faucet_endpoint).await?; - let network_name: NetworkName = client.network_name().await?; - let mut document: IotaDocument = IotaDocument::new(&network_name); - - // Create storage for key-ids and JWKs. - // - // In this example, the same stronghold file that is used to store - // key-ids as well as the JWKs. - let storage = Storage::new(stronghold_storage.clone(), stronghold_storage.clone()); - - // Generates a verification method. This will store the key-id as well as the private key - // in the stronghold file. - let fragment = document - .generate_method( - &storage, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA, - None, - MethodScope::VerificationMethod, - ) - .await?; - - // Construct an Alias Output containing the DID document, with the wallet address - // set as both the state controller and governor. - let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; - - // Publish the Alias Output and get the published DID document. - let document: IotaDocument = client - .publish_did_output(stronghold_storage.as_secret_manager(), alias_output) - .await?; - - // Resolve the published DID Document. - let mut resolver = Resolver::::new(); - resolver.attach_iota_handler(client.clone()); - let resolved_document: IotaDocument = resolver.resolve(document.id()).await.unwrap(); - - drop(stronghold_storage); - - // Create the storage again to demonstrate that data are read from the stronghold file. - let stronghold = StrongholdSecretManager::builder() - .password(password.clone()) - .build(path.clone())?; - let stronghold_storage = StrongholdStorage::new(stronghold); - let storage = Storage::new(stronghold_storage.clone(), stronghold_storage.clone()); - - // Sign data with the created verification method. - let data = b"test_data"; - let jws: Jws = resolved_document - .create_jws(&storage, &fragment, data, &JwsSignatureOptions::default()) - .await?; - - // Verify Signature. - let decoded_jws: DecodedJws = resolved_document.verify_jws( - &jws, - None, - &EdDSAJwsVerifier::default(), - &JwsVerificationOptions::default(), - )?; - - assert_eq!(String::from_utf8_lossy(decoded_jws.claims.as_ref()), "test_data"); - - println!("successfully verified signature"); - - Ok(()) -} diff --git a/examples/iota/1_advanced/0_did_controls_did.rs b/examples/iota/1_advanced/0_did_controls_did.rs deleted file mode 100644 index 18e88545f9..0000000000 --- a/examples/iota/1_advanced/0_did_controls_did.rs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::ops::Deref; - -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_iota::iota::block::output::AliasId; -use identity_iota::iota::block::output::UnlockCondition; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDID; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::iota::NetworkName; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use identity_iota::verification::jws::JwsAlgorithm; -use identity_iota::verification::MethodScope; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::address::AliasAddress; -use iota_sdk::types::block::output::feature::IssuerFeature; -use iota_sdk::types::block::output::AliasOutput; -use iota_sdk::types::block::output::AliasOutputBuilder; -use iota_sdk::types::block::output::RentStructure; - -/// Demonstrates how an identity can control another identity. -/// -/// For this example, we consider the case where a parent company's DID controls the DID of a subsidiary. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // ======================================================== - // Create the company's and subsidiary's Alias Output DIDs. - // ======================================================== - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let mut secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create a new DID for the company. - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, company_document, _): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager, &storage_issuer).await?; - let company_did = company_document.id().clone(); - - // Get the current byte costs and network name. - let rent_structure: RentStructure = client.get_rent_structure().await?; - let network_name: NetworkName = client.network_name().await?; - - // Construct a new DID document for the subsidiary. - let subsidiary_document: IotaDocument = IotaDocument::new(&network_name); - - // Create a DID for the subsidiary that is controlled by the parent company's DID. - // This means the subsidiary's Alias Output can only be updated or destroyed by - // the state controller or governor of the company's Alias Output respectively. - let subsidiary_alias: AliasOutput = client - .new_did_output( - Address::Alias(AliasAddress::new(AliasId::from(&company_did))), - subsidiary_document, - Some(rent_structure), - ) - .await?; - - let subsidiary_alias: AliasOutput = AliasOutputBuilder::from(&subsidiary_alias) - // Optionally, we can mark the company as the issuer of the subsidiary DID. - // This allows to verify trust relationships between DIDs, as a resolver can - // verify that the subsidiary DID was created by the parent company. - .add_immutable_feature(IssuerFeature::new(AliasAddress::new(AliasId::from(&company_did)))) - // Adding the issuer feature means we have to recalculate the required storage deposit. - .with_minimum_storage_deposit(rent_structure) - .finish()?; - - // Publish the subsidiary's DID. - let mut subsidiary_document: IotaDocument = client.publish_did_output(&secret_manager, subsidiary_alias).await?; - - // ===================================== - // Update the subsidiary's Alias Output. - // ===================================== - - // Add a verification method to the subsidiary. - // This only serves as an example for updating the subsidiary DID. - - let storage_subsidary: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - subsidiary_document - .generate_method( - &storage_subsidary, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA, - None, - MethodScope::VerificationMethod, - ) - .await?; - - // Update the subsidiary's Alias Output with the updated document - // and increase the storage deposit. - let subsidiary_alias: AliasOutput = client.update_did_output(subsidiary_document).await?; - let subsidiary_alias: AliasOutput = AliasOutputBuilder::from(&subsidiary_alias) - .with_minimum_storage_deposit(rent_structure) - .finish()?; - - // Publish the updated subsidiary's DID. - // - // This works because `secret_manager` can unlock the company's Alias Output, - // which is required in order to update the subsidiary's Alias Output. - let subsidiary_document: IotaDocument = client.publish_did_output(&secret_manager, subsidiary_alias).await?; - - // =================================================================== - // Determine the controlling company's DID given the subsidiary's DID. - // =================================================================== - - // Resolve the subsidiary's Alias Output. - let subsidiary_output: AliasOutput = client.resolve_did_output(subsidiary_document.id()).await?; - - // Extract the company's Alias Id from the state controller unlock condition. - // - // If instead we wanted to determine the original creator of the DID, - // we could inspect the issuer feature. This feature needs to be set when creating the DID. - let company_alias_id: AliasId = if let Some(UnlockCondition::StateControllerAddress(address)) = - subsidiary_output.unlock_conditions().iter().next() - { - if let Address::Alias(alias) = *address.address() { - *alias.alias_id() - } else { - anyhow::bail!("expected an alias address as the state controller"); - } - } else { - anyhow::bail!("expected two unlock conditions"); - }; - - // Reconstruct the company's DID from the Alias Id and the network. - let company_did = IotaDID::new(company_alias_id.deref(), &network_name); - - // Resolve the company's DID document. - let company_document: IotaDocument = client.resolve_did(&company_did).await?; - - println!("Company: {company_document:#}"); - println!("Subsidiary: {subsidiary_document:#}"); - - Ok(()) -} diff --git a/examples/iota/1_advanced/1_did_issues_nft.rs b/examples/iota/1_advanced/1_did_issues_nft.rs deleted file mode 100644 index 0a3506dc0b..0000000000 --- a/examples/iota/1_advanced/1_did_issues_nft.rs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_iota::iota::block::output::feature::MetadataFeature; -use identity_iota::iota::IotaDID; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::iota::NetworkName; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::address::AliasAddress; -use iota_sdk::types::block::output::feature::IssuerFeature; -use iota_sdk::types::block::output::unlock_condition::AddressUnlockCondition; -use iota_sdk::types::block::output::AliasId; -use iota_sdk::types::block::output::Feature; -use iota_sdk::types::block::output::NftId; -use iota_sdk::types::block::output::NftOutput; -use iota_sdk::types::block::output::NftOutputBuilder; -use iota_sdk::types::block::output::Output; -use iota_sdk::types::block::output::OutputId; -use iota_sdk::types::block::output::RentStructure; -use iota_sdk::types::block::output::UnlockCondition; -use iota_sdk::types::block::payload::transaction::TransactionEssence; -use iota_sdk::types::block::payload::Payload; -use iota_sdk::types::block::Block; - -/// Demonstrates how an identity can issue and own NFTs, -/// and how observers can verify the issuer of the NFT. -/// -/// For this example, we consider the case where a manufacturer issues -/// a digital product passport (DPP) as an NFT. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // ============================================== - // Create the manufacturer's DID and the DPP NFT. - // ============================================== - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let mut secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create a new DID for the manufacturer. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, manufacturer_document, _): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager, &storage).await?; - let manufacturer_did = manufacturer_document.id().clone(); - - // Get the current byte cost. - let rent_structure: RentStructure = client.get_rent_structure().await?; - - // Create a Digital Product Passport NFT issued by the manufacturer. - let product_passport_nft: NftOutput = - NftOutputBuilder::new_with_minimum_storage_deposit(rent_structure, NftId::null()) - // The NFT will initially be owned by the manufacturer. - .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(Address::Alias( - AliasAddress::new(AliasId::from(&manufacturer_did)), - )))) - // Set the manufacturer as the immutable issuer. - .add_immutable_feature(Feature::Issuer(IssuerFeature::new(Address::Alias(AliasAddress::new( - AliasId::from(&manufacturer_did), - ))))) - // A proper DPP would hold its metadata here. - .add_immutable_feature(Feature::Metadata(MetadataFeature::new( - b"Digital Product Passport Metadata".to_vec(), - )?)) - .finish()?; - - // Publish the NFT. - let block: Block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(vec![product_passport_nft.into()])? - .finish() - .await?; - let _ = client.retry_until_included(&block.id(), None, None).await?; - - // ======================================================== - // Resolve the Digital Product Passport NFT and its issuer. - // ======================================================== - - // Extract the identifier of the NFT from the published block. - let nft_id: NftId = NftId::from(&get_nft_output_id( - block - .payload() - .ok_or_else(|| anyhow::anyhow!("expected block to contain a payload"))?, - )?); - - // Fetch the NFT Output. - let nft_output_id: OutputId = client.nft_output_id(nft_id).await?; - let output: Output = client.get_output(&nft_output_id).await?.into_output(); - - // Extract the issuer of the NFT. - let nft_output: NftOutput = if let Output::Nft(nft_output) = output { - nft_output - } else { - anyhow::bail!("expected NFT output") - }; - - let issuer_address: Address = if let Some(Feature::Issuer(issuer)) = nft_output.immutable_features().iter().next() { - *issuer.address() - } else { - anyhow::bail!("expected an issuer feature") - }; - - let manufacturer_alias_id: AliasId = if let Address::Alias(alias_address) = issuer_address { - *alias_address.alias_id() - } else { - anyhow::bail!("expected an Alias Address") - }; - - // Reconstruct the manufacturer's DID from the Alias Id. - let network: NetworkName = client.network_name().await?; - let manufacturer_did: IotaDID = IotaDID::new(&manufacturer_alias_id, &network); - - // Resolve the issuer of the NFT. - let manufacturer_document: IotaDocument = client.resolve_did(&manufacturer_did).await?; - - println!("The issuer of the Digital Product Passport NFT is: {manufacturer_document:#}"); - - Ok(()) -} - -// Helper function to get the output id for the first NFT output in a Block. -fn get_nft_output_id(payload: &Payload) -> anyhow::Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Nft(_nft_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - anyhow::bail!("no NFT output in transaction essence") - } - _ => anyhow::bail!("No transaction payload"), - } -} diff --git a/examples/iota/1_advanced/2_nft_owns_did.rs b/examples/iota/1_advanced/2_nft_owns_did.rs deleted file mode 100644 index 019897df9d..0000000000 --- a/examples/iota/1_advanced/2_nft_owns_did.rs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::create_did_document; -use examples_iota::get_address_with_funds; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use examples_iota::FAUCET_ENDPOINT; -use identity_iota::iota::block::address::NftAddress; -use identity_iota::iota::block::output::AliasOutput; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::iota::NetworkName; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::output::unlock_condition::AddressUnlockCondition; -use iota_sdk::types::block::output::NftId; -use iota_sdk::types::block::output::NftOutput; -use iota_sdk::types::block::output::NftOutputBuilder; -use iota_sdk::types::block::output::Output; -use iota_sdk::types::block::output::OutputId; -use iota_sdk::types::block::output::RentStructure; -use iota_sdk::types::block::output::UnlockCondition; -use iota_sdk::types::block::payload::transaction::TransactionEssence; -use iota_sdk::types::block::payload::Payload; -use iota_sdk::types::block::Block; - -/// Demonstrates how an identity can be owned by NFTs, -/// and how observers can verify that relationship. -/// -/// For this example, we consider the case where a car's NFT owns -/// the DID of the car, so that transferring the NFT also transfers DID ownership. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // ============================= - // Create the car's NFT and DID. - // ============================= - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Get an address with funds for testing. - let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; - - // Get the current byte cost. - let rent_structure: RentStructure = client.get_rent_structure().await?; - - // Create the car NFT with an Ed25519 address as the unlock condition. - let car_nft: NftOutput = NftOutputBuilder::new_with_minimum_storage_deposit(rent_structure, NftId::null()) - .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(address))) - .finish()?; - - // Publish the NFT output. - let block: Block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(vec![car_nft.into()])? - .finish() - .await?; - let _ = client.retry_until_included(&block.id(), None, None).await?; - - let car_nft_id: NftId = NftId::from(&get_nft_output_id( - block - .payload() - .ok_or_else(|| anyhow::anyhow!("expected the block to contain a payload"))?, - )?); - - let network: NetworkName = client.network_name().await?; - - // Construct a DID document for the car. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (car_document, _): (IotaDocument, _) = create_did_document(&network, &storage).await?; - - // Create a new DID for the car that is owned by the car NFT. - let car_did_output: AliasOutput = client - .new_did_output(Address::Nft(car_nft_id.into()), car_document, Some(rent_structure)) - .await?; - - // Publish the car DID. - let car_document: IotaDocument = client.publish_did_output(&secret_manager, car_did_output).await?; - - // ============================================ - // Determine the car's NFT given the car's DID. - // ============================================ - - // Resolve the Alias Output of the DID. - let output: AliasOutput = client.resolve_did_output(car_document.id()).await?; - - // Extract the NFT address from the state controller unlock condition. - let unlock_condition: &UnlockCondition = output - .unlock_conditions() - .iter() - .next() - .ok_or_else(|| anyhow::anyhow!("expected at least one unlock condition"))?; - - let car_nft_address: NftAddress = - if let UnlockCondition::StateControllerAddress(state_controller_unlock_condition) = unlock_condition { - if let Address::Nft(nft_address) = state_controller_unlock_condition.address() { - *nft_address - } else { - anyhow::bail!("expected an NFT address as the unlock condition"); - } - } else { - anyhow::bail!("expected an Address as the unlock condition"); - }; - - // Retrieve the NFT Output of the car. - let car_nft_id: &NftId = car_nft_address.nft_id(); - let output_id: OutputId = client.nft_output_id(*car_nft_id).await?; - let output: Output = client.get_output(&output_id).await?.into_output(); - - let car_nft: NftOutput = if let Output::Nft(nft_output) = output { - nft_output - } else { - anyhow::bail!("expected an NFT output"); - }; - - println!("The car's DID is: {car_document:#}"); - println!("The car's NFT is: {car_nft:#?}"); - - Ok(()) -} - -// Helper function to get the output id for the first NFT output in a Block. -fn get_nft_output_id(payload: &Payload) -> anyhow::Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Nft(_nft_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - anyhow::bail!("no NFT output in transaction essence") - } - _ => anyhow::bail!("No transaction payload"), - } -} diff --git a/examples/iota/1_advanced/3_did_issues_tokens.rs b/examples/iota/1_advanced/3_did_issues_tokens.rs deleted file mode 100644 index 4e5f7afa3a..0000000000 --- a/examples/iota/1_advanced/3_did_issues_tokens.rs +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::ops::Deref; - -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_iota::core::Duration; -use identity_iota::core::Timestamp; -use identity_iota::iota::block::output::unlock_condition::AddressUnlockCondition; -use identity_iota::iota::block::output::unlock_condition::ExpirationUnlockCondition; -use identity_iota::iota::block::output::BasicOutput; -use identity_iota::iota::block::output::BasicOutputBuilder; -use identity_iota::iota::block::output::Output; -use identity_iota::iota::block::output::OutputId; -use identity_iota::iota::IotaDID; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::iota::NetworkName; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::api::GetAddressesOptions; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::address::AliasAddress; -use iota_sdk::types::block::address::ToBech32Ext; -use iota_sdk::types::block::output::unlock_condition::ImmutableAliasAddressUnlockCondition; -use iota_sdk::types::block::output::AliasId; -use iota_sdk::types::block::output::AliasOutput; -use iota_sdk::types::block::output::AliasOutputBuilder; -use iota_sdk::types::block::output::FoundryId; -use iota_sdk::types::block::output::FoundryOutput; -use iota_sdk::types::block::output::FoundryOutputBuilder; -use iota_sdk::types::block::output::NativeToken; -use iota_sdk::types::block::output::RentStructure; -use iota_sdk::types::block::output::SimpleTokenScheme; -use iota_sdk::types::block::output::TokenId; -use iota_sdk::types::block::output::TokenScheme; -use iota_sdk::types::block::output::UnlockCondition; -use iota_sdk::types::block::Block; -use primitive_types::U256; - -/// Demonstrates how an identity can issue and control -/// a Token Foundry and its tokens. -/// -/// For this example, we consider the case where an authority issues -/// carbon credits that can be used to pay for carbon emissions or traded on a marketplace. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // =========================================== - // Create the authority's DID and the foundry. - // =========================================== - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let mut secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create a new DID for the authority. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, authority_document, _): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager, &storage).await?; - let authority_did = authority_document.id().clone(); - - let rent_structure: RentStructure = client.get_rent_structure().await?; - - // We want to update the foundry counter of the authority's Alias Output, so we create an - // updated version of the output. We pass in the previous document, - // because we don't want to modify it in this update. - let authority_document: IotaDocument = client.resolve_did(&authority_did).await?; - let authority_alias_output: AliasOutput = client.update_did_output(authority_document).await?; - - // We will add one foundry to this Alias Output. - let authority_alias_output = AliasOutputBuilder::from(&authority_alias_output) - .with_foundry_counter(1) - .finish()?; - - // Create a token foundry that represents carbon credits. - let token_scheme = TokenScheme::Simple(SimpleTokenScheme::new( - U256::from(500_000u32), - U256::from(0u8), - U256::from(1_000_000u32), - )?); - - // Create the identifier of the foundry, which is partially derived from the Alias Address. - let foundry_id = FoundryId::build( - &AliasAddress::new(AliasId::from(&authority_did)), - 1, - token_scheme.kind(), - ); - - // Create the Foundry Output. - let carbon_credits_foundry: FoundryOutput = - FoundryOutputBuilder::new_with_minimum_storage_deposit(rent_structure, 1, token_scheme) - // Initially, all carbon credits are owned by the foundry. - .add_native_token(NativeToken::new(TokenId::from(foundry_id), U256::from(500_000u32))?) - // The authority is set as the immutable owner. - .add_unlock_condition(UnlockCondition::ImmutableAliasAddress( - ImmutableAliasAddressUnlockCondition::new(AliasAddress::new(AliasId::from(&authority_did))), - )) - .finish()?; - - let carbon_credits_foundry_id: FoundryId = carbon_credits_foundry.id(); - - // Publish all outputs. - let block: Block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(vec![authority_alias_output.into(), carbon_credits_foundry.into()])? - .finish() - .await?; - let _ = client.retry_until_included(&block.id(), None, None).await?; - - // =================================== - // Resolve Foundry and its issuer DID. - // =================================== - - // Get the latest output that contains the foundry. - let foundry_output_id: OutputId = client.foundry_output_id(carbon_credits_foundry_id).await?; - let carbon_credits_foundry: Output = client.get_output(&foundry_output_id).await?.into_output(); - - let carbon_credits_foundry: FoundryOutput = if let Output::Foundry(foundry_output) = carbon_credits_foundry { - foundry_output - } else { - anyhow::bail!("expected foundry output") - }; - - // Get the Alias Id of the authority that issued the carbon credits foundry. - let authority_alias_id: &AliasId = carbon_credits_foundry.alias_address().alias_id(); - - // Reconstruct the DID of the authority. - let network: NetworkName = client.network_name().await?; - let authority_did: IotaDID = IotaDID::new(authority_alias_id.deref(), &network); - - // Resolve the authority's DID document. - let authority_document: IotaDocument = client.resolve_did(&authority_did).await?; - - println!("The authority's DID is: {authority_document:#}"); - - // ========================================================= - // Transfer 1000 carbon credits to the address of a company. - // ========================================================= - - // Create a new address that represents the company. - let company_address: Address = *secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::default() - .with_bech32_hrp((&network).try_into()?) - .with_range(1..2), - ) - .await?[0]; - - // Create the timestamp at which the basic output will expire. - let tomorrow: u32 = Timestamp::now_utc() - .checked_add(Duration::seconds(60 * 60 * 24)) - .ok_or_else(|| anyhow::anyhow!("timestamp overflow"))? - .to_unix() - .try_into() - .map_err(|err| anyhow::anyhow!("cannot fit timestamp into u32: {err}"))?; - - // Create a basic output containing our carbon credits that we'll send to the company's address. - let basic_output: BasicOutput = BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) - .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(company_address))) - .add_native_token(NativeToken::new(carbon_credits_foundry.token_id(), U256::from(1000))?) - // Allow the company to claim the credits within 24 hours by using an expiration unlock condition. - .add_unlock_condition(UnlockCondition::Expiration(ExpirationUnlockCondition::new( - Address::Alias(AliasAddress::new(*authority_alias_id)), - tomorrow, - )?)) - .finish()?; - - // Reduce the carbon credits in the foundry by the amount that is sent to the company. - let carbon_credits_foundry = FoundryOutputBuilder::from(&carbon_credits_foundry) - .with_native_tokens(vec![NativeToken::new( - carbon_credits_foundry.token_id(), - U256::from(499_000u32), - )?]) - .finish()?; - - // Publish the output, transferring the carbon credits. - let block: Block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(vec![basic_output.into(), carbon_credits_foundry.into()])? - .finish() - .await?; - let _ = client.retry_until_included(&block.id(), None, None).await?; - - println!( - "Sent carbon credits to {}", - company_address.to_bech32((&network).try_into()?) - ); - - Ok(()) -} diff --git a/examples/iota/1_advanced/4_alias_output_history.rs b/examples/iota/1_advanced/4_alias_output_history.rs deleted file mode 100644 index a305bb47bd..0000000000 --- a/examples/iota/1_advanced/4_alias_output_history.rs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use anyhow::Context; -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_iota::core::json; -use identity_iota::core::FromJson; -use identity_iota::core::Timestamp; -use identity_iota::did::DID; -use identity_iota::document::Service; -use identity_iota::iota::block::address::Address; -use identity_iota::iota::block::output::RentStructure; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDID; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClient; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use identity_iota::verification::MethodRelationship; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::input::Input; -use iota_sdk::types::block::output::AliasId; -use iota_sdk::types::block::output::AliasOutput; -use iota_sdk::types::block::output::AliasOutputBuilder; -use iota_sdk::types::block::output::Output; -use iota_sdk::types::block::output::OutputId; -use iota_sdk::types::block::output::OutputMetadata; -use iota_sdk::types::block::payload::transaction::TransactionEssence; -use iota_sdk::types::block::payload::Payload; -use iota_sdk::types::block::Block; - -/// Demonstrates how to obtain the alias output history. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Create a new client to interact with the IOTA ledger. - // NOTE: a permanode is required to fetch older output histories. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let mut secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create a new DID in an Alias Output for us to modify. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, document, fragment): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager, &storage).await?; - let did: IotaDID = document.id().clone(); - - // Resolve the latest state of the document. - let mut document: IotaDocument = client.resolve_did(&did).await?; - - // Attach a new method relationship to the existing method. - document.attach_method_relationship( - &document.id().to_url().join(format!("#{fragment}"))?, - MethodRelationship::Authentication, - )?; - - // Adding multiple services. - let services = [ - json!({"id": document.id().to_url().join("#my-service-0")?, "type": "MyService", "serviceEndpoint": "https://iota.org/"}), - ]; - for service in services { - let service: Service = Service::from_json_value(service)?; - assert!(document.insert_service(service).is_ok()); - document.metadata.updated = Some(Timestamp::now_utc()); - - // Increase the storage deposit and publish the update. - let alias_output: AliasOutput = client.update_did_output(document.clone()).await?; - let rent_structure: RentStructure = client.get_rent_structure().await?; - let alias_output: AliasOutput = AliasOutputBuilder::from(&alias_output) - .with_minimum_storage_deposit(rent_structure) - .finish()?; - client.publish_did_output(&secret_manager, alias_output).await?; - } - - // ==================================== - // Retrieving the Alias Output History - // ==================================== - let mut alias_history: Vec = Vec::new(); - - // Step 0 - Get the latest Alias Output - let alias_id: AliasId = AliasId::from(client.resolve_did(&did).await?.id()); - let (mut output_id, mut alias_output): (OutputId, AliasOutput) = client.get_alias_output(alias_id).await?; - - while alias_output.state_index() != 0 { - // Step 1 - Get the current block - let block: Block = current_block(&client, &output_id).await?; - // Step 2 - Get the OutputId of the previous block - output_id = previous_output_id(&block)?; - // Step 3 - Get the Alias Output from the block - alias_output = block_alias_output(&block, &alias_id)?; - alias_history.push(alias_output.clone()); - } - - println!("Alias History: {alias_history:?}"); - - Ok(()) -} - -async fn current_block(client: &Client, output_id: &OutputId) -> anyhow::Result { - let output_metadata: OutputMetadata = client.get_output_metadata(output_id).await?; - let block: Block = client.get_block(output_metadata.block_id()).await?; - Ok(block) -} - -fn previous_output_id(block: &Block) -> anyhow::Result { - match block - .payload() - .context("expected a transaction payload, but no payload was found")? - { - Payload::Transaction(transaction_payload) => match transaction_payload.essence() { - TransactionEssence::Regular(regular_transaction_essence) => { - match regular_transaction_essence - .inputs() - .first() - .context("expected an utxo for the block, but no input was found")? - { - Input::Utxo(utxo_input) => Ok(*utxo_input.output_id()), - Input::Treasury(_) => { - anyhow::bail!("expected an utxo input, found a treasury input"); - } - } - } - }, - Payload::Milestone(_) | Payload::TreasuryTransaction(_) | Payload::TaggedData(_) => { - anyhow::bail!("expected a transaction payload"); - } - } -} - -fn block_alias_output(block: &Block, alias_id: &AliasId) -> anyhow::Result { - match block - .payload() - .context("expected a transaction payload, but no payload was found")? - { - Payload::Transaction(transaction_payload) => match transaction_payload.essence() { - TransactionEssence::Regular(regular_transaction_essence) => { - for (index, output) in regular_transaction_essence.outputs().iter().enumerate() { - match output { - Output::Alias(alias_output) => { - if &alias_output.alias_id().or_from_output_id( - &OutputId::new( - transaction_payload.id(), - index.try_into().context("output index must fit into a u16")?, - ) - .context("failed to create OutputId")?, - ) == alias_id - { - return Ok(alias_output.clone()); - } - } - Output::Basic(_) | Output::Foundry(_) | Output::Nft(_) | Output::Treasury(_) => continue, - } - } - } - }, - Payload::Milestone(_) | Payload::TreasuryTransaction(_) | Payload::TaggedData(_) => { - anyhow::bail!("expected a transaction payload"); - } - } - anyhow::bail!("no alias output has been found"); -} diff --git a/examples/iota/1_advanced/5_custom_resolution.rs b/examples/iota/1_advanced/5_custom_resolution.rs deleted file mode 100644 index 177616dd84..0000000000 --- a/examples/iota/1_advanced/5_custom_resolution.rs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_iota::core::FromJson; -use identity_iota::core::ToJson; -use identity_iota::did::CoreDID; -use identity_iota::did::DID; -use identity_iota::document::CoreDocument; -use identity_iota::iota::IotaDID; -use identity_iota::iota::IotaDocument; -use identity_iota::resolver::Resolver; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; - -/// Demonstrates how to set up a resolver using custom handlers. -/// -/// NOTE: Since both `IotaDocument` and `FooDocument` implement `Into` we could have used -/// Resolver in this example and just worked with `CoreDocument` representations throughout. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Create a resolver returning an enum of the documents we are interested in and attach handlers for the "foo" and - // "iota" methods. - let mut resolver: Resolver = Resolver::new(); - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // This is a convenience method for attaching a handler for the "iota" method by providing just a client. - resolver.attach_iota_handler(client.clone()); - resolver.attach_handler("foo".to_owned(), resolve_did_foo); - - // A fake did:foo DID for demonstration purposes. - let did_foo: CoreDID = "did:foo:0e9c8294eeafee326a4e96d65dbeaca0".parse()?; - - // Create a new secret manager backed by a Stronghold. - let mut secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create a new DID for us to resolve. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, iota_document, _): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager, &storage).await?; - let iota_did: IotaDID = iota_document.id().clone(); - - // Resolve did_foo to get an abstract document. - let did_foo_doc: Document = resolver.resolve(&did_foo).await?; - - // Resolve iota_did to get an abstract document. - let iota_doc: Document = resolver.resolve(&iota_did).await?; - - // The Resolver is mainly meant for validating presentations, but here we will just - // check that the resolved documents match our expectations. - - let Document::Foo(did_foo_document) = did_foo_doc else { - anyhow::bail!("expected a foo DID document when resolving a foo DID"); - }; - - println!( - "Resolved DID foo document: {}", - did_foo_document.as_ref().to_json_pretty()? - ); - - let Document::Iota(iota_document) = iota_doc else { - anyhow::bail!("expected an IOTA DID document when resolving an IOTA DID") - }; - - println!("Resolved IOTA DID document: {}", iota_document.to_json_pretty()?); - - Ok(()) -} - -// Type safe representation of a document adhering to the imaginary "foo" method. -struct FooDocument(CoreDocument); -impl FooDocument { - fn new(document: CoreDocument) -> anyhow::Result { - if document.id().method() == "foo" { - Ok(Self(document)) - } else { - anyhow::bail!("cannot construct foo document: incorrect method") - } - } -} - -impl AsRef for FooDocument { - fn as_ref(&self) -> &CoreDocument { - &self.0 - } -} - -impl From for CoreDocument { - fn from(value: FooDocument) -> Self { - value.0 - } -} - -// Enum of the document types we want to handle. -enum Document { - Foo(FooDocument), - Iota(IotaDocument), -} - -impl From for Document { - fn from(value: FooDocument) -> Self { - Self::Foo(value) - } -} - -impl From for Document { - fn from(value: IotaDocument) -> Self { - Self::Iota(value) - } -} - -impl AsRef for Document { - fn as_ref(&self) -> &CoreDocument { - match self { - Self::Foo(doc) => doc.as_ref(), - Self::Iota(doc) => doc.as_ref(), - } - } -} - -/// Resolve a did to a DID document if the did method is "foo". -async fn resolve_did_foo(did: CoreDID) -> anyhow::Result { - let doc = CoreDocument::from_json(&format!( - r#"{{ - "id": "{did}", - "verificationMethod": [ - {{ - "id": "{did}#key-1", - "controller": "{did}", - "type": "Ed25519VerificationKey2018", - "publicKeyMultibase": "zGurPxZGpqnJ6j866DNBXYQJH2KzJjmQ9KBpCYp9oYJom" - }} - ] - }}"#, - )) - .unwrap(); - FooDocument::new(doc) -} diff --git a/examples/iota/1_advanced/6_domain_linkage.rs b/examples/iota/1_advanced/6_domain_linkage.rs deleted file mode 100644 index 66986359a5..0000000000 --- a/examples/iota/1_advanced/6_domain_linkage.rs +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::core::Duration; -use identity_iota::core::FromJson; -use identity_iota::core::Object; -use identity_iota::core::OrderedSet; -use identity_iota::core::Timestamp; -use identity_iota::core::ToJson; -use identity_iota::core::Url; -use identity_iota::credential::Credential; -use identity_iota::credential::DomainLinkageConfiguration; -use identity_iota::credential::DomainLinkageCredentialBuilder; -use identity_iota::credential::DomainLinkageValidationError; -use identity_iota::credential::Jwt; -use identity_iota::credential::JwtCredentialValidationOptions; -use identity_iota::credential::JwtDomainLinkageValidator; -use identity_iota::credential::LinkedDomainService; -use identity_iota::did::CoreDID; -use identity_iota::did::DIDUrl; -use identity_iota::did::DID; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDID; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::resolver::Resolver; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwsSignatureOptions; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::output::AliasOutput; -use iota_sdk::types::block::output::AliasOutputBuilder; -use iota_sdk::types::block::output::RentStructure; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new secret manager backed by a Stronghold. - let mut secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create a DID for the entity that will issue the Domain Linkage Credential. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, mut did_document, fragment): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager, &storage).await?; - let did: IotaDID = did_document.id().clone(); - - // ===================================================== - // Create Linked Domain service - // ===================================================== - - // The DID should be linked to the following domains. - let domain_1: Url = Url::parse("https://foo.example.com")?; - let domain_2: Url = Url::parse("https://bar.example.com")?; - - let mut domains: OrderedSet = OrderedSet::new(); - domains.append(domain_1.clone()); - domains.append(domain_2.clone()); - - // Create a Linked Domain Service to enable the discovery of the linked domains through the DID Document. - // This is optional since it is not a hard requirement by the specs. - let service_url: DIDUrl = did.clone().join("#domain-linkage")?; - let linked_domain_service: LinkedDomainService = LinkedDomainService::new(service_url, domains, Object::new())?; - did_document.insert_service(linked_domain_service.into())?; - let updated_did_document: IotaDocument = publish_document(client.clone(), secret_manager, did_document).await?; - - println!("DID document with linked domain service: {updated_did_document:#}"); - - // ===================================================== - // Create DID Configuration resource - // ===================================================== - - // Now the DID Document contains a service that includes the domains. - // To allow a bidirectional linkage, the domains must link to the DID. This is - // done by creating a `DID Configuration Resource` that includes a `Domain Linkage Credential` - // and can be made available on the domain. - - // Create the Domain Linkage Credential. - let domain_linkage_credential: Credential = DomainLinkageCredentialBuilder::new() - .issuer(updated_did_document.id().clone().into()) - .origin(domain_1.clone()) - .issuance_date(Timestamp::now_utc()) - // Expires after a year. - .expiration_date( - Timestamp::now_utc() - .checked_add(Duration::days(365)) - .ok_or_else(|| anyhow::anyhow!("calculation should not overflow"))?, - ) - .build()?; - - let jwt: Jwt = updated_did_document - .create_credential_jwt( - &domain_linkage_credential, - &storage, - &fragment, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - // Create the DID Configuration Resource which wraps the Domain Linkage credential. - let configuration_resource: DomainLinkageConfiguration = DomainLinkageConfiguration::new(vec![jwt]); - println!("Configuration Resource >>: {configuration_resource:#}"); - - // The DID Configuration resource can be made available on `https://foo.example.com/.well-known/did-configuration.json`. - let configuration_resource_json: String = configuration_resource.to_json()?; - - // Now the DID Document links to the Domains through the service, and the Foo domain links to the DID - // through the DID Configuration resource. A bidirectional linkage is established. - // Note however that bidirectionality is not a hard requirement. It is valid to have a Domain Linkage - // credential point to a DID, without the DID having a service that points back. - - // ===================================================== - // Verification can start from two different places. - // The first case answers the question "What DID is this domain linked to?" - // while the second answers "What domain is this DID linked to?". - // ===================================================== - - // Init a resolver for resolving DID Documents. - let mut resolver: Resolver = Resolver::new(); - resolver.attach_iota_handler(client.clone()); - - // ===================================================== - // → Case 1: starting from domain - // ===================================================== - let domain_foo: Url = domain_1.clone(); - - // Fetch the DID Configuration resource - // let configuration_resource: DomainLinkageConfiguration = - // DomainLinkageConfiguration::fetch_configuration(domain_foo.clone()).await?; - - // Retrieve the issuers of the Domain Linkage Credentials which correspond to the possibly linked DIDs. - let linked_dids: Vec = configuration_resource.issuers()?; - assert_eq!(linked_dids.len(), 1); - - // Resolve the DID Document of the DID that issued the credential. - let issuer_did_document: IotaDocument = resolver.resolve(&did).await?; - - // Validate the linkage between the Domain Linkage Credential in the configuration and the provided issuer DID. - let validation_result: Result<(), DomainLinkageValidationError> = - JwtDomainLinkageValidator::with_signature_verifier(EdDSAJwsVerifier::default()).validate_linkage( - &issuer_did_document, - &configuration_resource, - &domain_foo, - &JwtCredentialValidationOptions::default(), - ); - assert!(validation_result.is_ok()); - - // ===================================================== - // → Case 2: starting from a DID - // ===================================================== - let did_document: IotaDocument = resolver.resolve(&did).await?; - - // Get the Linked Domain Services from the DID Document. - let linked_domain_services: Vec = did_document - .service() - .iter() - .cloned() - .filter_map(|service| LinkedDomainService::try_from(service).ok()) - .collect(); - assert_eq!(linked_domain_services.len(), 1); - - // Get the domains included in the Linked Domain Service. - let domains: &[Url] = linked_domain_services - .first() - .ok_or_else(|| anyhow::anyhow!("expected a domain"))? - .domains(); - - let domain_foo: Url = domains - .first() - .ok_or_else(|| anyhow::anyhow!("expected a domain"))? - .clone(); - assert_eq!(domain_foo, domain_1); - - // Fetch the DID Configuration resource - // let configuration_resource: DomainLinkageConfiguration = - // DomainLinkageConfiguration::fetch_configuration(domain_foo.clone()).await?; - - // But since the DID Configuration - // resource isn't available online in this example, we will simply deserialize the JSON. - let configuration_resource: DomainLinkageConfiguration = - DomainLinkageConfiguration::from_json(&configuration_resource_json)?; - - // Validate the linkage. - let validation_result: Result<(), DomainLinkageValidationError> = - JwtDomainLinkageValidator::with_signature_verifier(EdDSAJwsVerifier::default()).validate_linkage( - &did_document, - &configuration_resource, - &domain_foo, - &JwtCredentialValidationOptions::default(), - ); - assert!(validation_result.is_ok()); - Ok(()) -} - -async fn publish_document( - client: Client, - secret_manager: SecretManager, - document: IotaDocument, -) -> anyhow::Result { - // Resolve the latest output and update it with the given document. - let alias_output: AliasOutput = client.update_did_output(document.clone()).await?; - - // Because the size of the DID document increased, we have to increase the allocated storage deposit. - // This increases the deposit amount to the new minimum. - let rent_structure: RentStructure = client.get_rent_structure().await?; - let alias_output: AliasOutput = AliasOutputBuilder::from(&alias_output) - .with_minimum_storage_deposit(rent_structure) - .finish()?; - - // Publish the updated Alias Output. - Ok(client.publish_did_output(&secret_manager, alias_output).await?) -} diff --git a/examples/iota/1_advanced/7_sd_jwt.rs b/examples/iota/1_advanced/7_sd_jwt.rs deleted file mode 100644 index c972fb2298..0000000000 --- a/examples/iota/1_advanced/7_sd_jwt.rs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! This example shows how to create a selective disclosure verifiable credential and validate it -//! using the standard [Selective Disclosure for JWTs (SD-JWT)](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html). -//! -//! cargo run --release --example 7_sd_jwt - -use examples_iota::create_did; -use examples_iota::pretty_print_json; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::core::json; -use identity_iota::core::FromJson; -use identity_iota::core::Object; -use identity_iota::core::Timestamp; -use identity_iota::core::ToJson; -use identity_iota::core::Url; -use identity_iota::credential::Credential; -use identity_iota::credential::CredentialBuilder; -use identity_iota::credential::FailFast; -use identity_iota::credential::Jws; -use identity_iota::credential::JwtCredentialValidationOptions; -use identity_iota::credential::KeyBindingJWTValidationOptions; -use identity_iota::credential::SdJwtCredentialValidator; -use identity_iota::credential::Subject; -use identity_iota::did::DID; -use identity_iota::iota::IotaDocument; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwsSignatureOptions; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use sd_jwt_payload::KeyBindingJwtClaims; -use sd_jwt_payload::SdJwt; -use sd_jwt_payload::SdObjectDecoder; -use sd_jwt_payload::SdObjectEncoder; -use sd_jwt_payload::Sha256Hasher; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // =========================================================================== - // Step 1: Create identities for the issuer and the holder. - // =========================================================================== - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create an identity for the issuer with one verification method `key-1`. - let mut secret_manager_issuer: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_1".to_owned())) - .build(random_stronghold_path())?, - ); - let issuer_storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, issuer_document, fragment): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_issuer, &issuer_storage).await?; - - // Create an identity for the holder, in this case also the subject. - let mut secret_manager_alice: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?, - ); - let alice_storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, alice_document, alice_fragment): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_alice, &alice_storage).await?; - - // =========================================================================== - // Step 2: Issuer creates and signs a selectively disclosable JWT verifiable credential. - // =========================================================================== - - // Create an address credential subject. - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "address": { - "locality": "Maxstadt", - "postal_code": "12344", - "country": "DE", - "street_address": "Weidenstraße 22" - } - }))?; - - // Build credential using subject above and issuer. - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.com/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("AddressCredential") - .subject(subject) - .build()?; - - // In Order to create an selective disclosure JWT, the plain text JWT - // claims set must be created first. - let payload = credential.serialize_jwt(None)?; - pretty_print_json("Claims set in plain text", &payload); - - // Using the crate `sd-jwt` properties of the claims can be made selectively disclosable. - // The default sha-256 hasher will be used to create the digests. - // Read more in https://github.com/iotaledger/sd-jwt-payload . - let mut encoder = SdObjectEncoder::new(&payload)?; - - // Make "locality", "postal_code" and "street_address" selectively disclosable while keeping - // other properties in plain text. - let disclosures = vec![ - encoder.conceal("/vc/credentialSubject/address/locality", None)?, - encoder.conceal("/vc/credentialSubject/address/postal_code", None)?, - encoder.conceal("/vc/credentialSubject/address/street_address", None)?, - ]; - - // Add the `_sd_alg` property. - encoder.add_sd_alg_property(); - - let encoded_payload = encoder.try_to_string()?; - - pretty_print_json("Claims set with disclosure digests", &encoded_payload); - - // Create the signed JWT. - let jwt: Jws = issuer_document - .create_jws( - &issuer_storage, - &fragment, - encoded_payload.as_bytes(), - &JwsSignatureOptions::default(), - ) - .await?; - - // =========================================================================== - // Step 3: Issuer sends the JWT and the disclosures to the holder. - // =========================================================================== - - // One way to send the JWT and the disclosures, is by creating an SD-JWT with all the - // disclosures. - let disclosures: Vec = disclosures - .into_iter() - .map(|disclosure| disclosure.to_string()) - .collect(); - let sd_jwt_str = SdJwt::new(jwt.into(), disclosures, None).presentation(); - - // =========================================================================== - // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. - // =========================================================================== - - const VERIFIER_DID: &str = "did:example:verifier"; - // A unique random challenge generated by the requester per presentation can mitigate replay attacks. - let nonce: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - // =========================================================================== - // Step 5: Holder creates an SD-JWT to be presented to a verifier. - // =========================================================================== - - let sd_jwt = SdJwt::parse(&sd_jwt_str)?; - - // The holder only wants to present "locality" and "postal_code" but not "street_address". - let disclosures = vec![ - sd_jwt.disclosures.first().unwrap().clone(), - sd_jwt.disclosures.get(1).unwrap().clone(), - ]; - - // Optionally, the holder can add a Key Binding JWT (KB-JWT). This is dependent on the verifier's policy. - // Issuing the KB-JWT is done by creating the claims set and setting the header `typ` value - // with the help of `KeyBindingJwtClaims`. - let binding_claims = KeyBindingJwtClaims::new( - &Sha256Hasher::new(), - sd_jwt.jwt.as_str().to_string(), - disclosures.clone(), - nonce.to_string(), - VERIFIER_DID.to_string(), - Timestamp::now_utc().to_unix(), - ) - .to_json()?; - - // Setting the `typ` in the header is required. - let options = JwsSignatureOptions::new().typ(KeyBindingJwtClaims::KB_JWT_HEADER_TYP); - - // Create the KB-JWT. - let kb_jwt: Jws = alice_document - .create_jws(&alice_storage, &alice_fragment, binding_claims.as_bytes(), &options) - .await?; - - // Create the final SD-JWT. - let sd_jwt_obj = SdJwt::new(sd_jwt.jwt, disclosures, Some(kb_jwt.into())); - - // =========================================================================== - // Step 6: Holder presents the SD-JWT to the verifier. - // =========================================================================== - - let sd_jwt_presentation: String = sd_jwt_obj.presentation(); - - // =========================================================================== - // Step 7: Verifier receives the SD-JWT and verifies it. - // =========================================================================== - - let sd_jwt = SdJwt::parse(&sd_jwt_presentation)?; - - // Verify the JWT. - let decoder = SdObjectDecoder::new_with_sha256(); - let validator = SdJwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default(), decoder); - let validation = validator - .validate_credential::<_, Object>( - &sd_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); - - println!("JWT successfully validated"); - pretty_print_json("Decoded Credential", &validation.credential.to_string()); - - // Verify the Key Binding JWT. - let options = KeyBindingJWTValidationOptions::new().nonce(nonce).aud(VERIFIER_DID); - let _kb_validation = validator.validate_key_binding_jwt(&sd_jwt_obj, &alice_document, &options)?; - - println!("Key Binding JWT successfully validated"); - - Ok(()) -} diff --git a/examples/iota/1_advanced/8_status_list_2021.rs b/examples/iota/1_advanced/8_status_list_2021.rs deleted file mode 100644 index 0905b1e869..0000000000 --- a/examples/iota/1_advanced/8_status_list_2021.rs +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use examples_iota::create_did; -use examples_iota::random_stronghold_path; -use examples_iota::MemStorage; -use examples_iota::API_ENDPOINT; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::core::FromJson; -use identity_iota::core::Object; -use identity_iota::core::ToJson; -use identity_iota::core::Url; -use identity_iota::credential::status_list_2021::StatusList2021; -use identity_iota::credential::status_list_2021::StatusList2021Credential; -use identity_iota::credential::status_list_2021::StatusList2021CredentialBuilder; -use identity_iota::credential::status_list_2021::StatusList2021Entry; -use identity_iota::credential::status_list_2021::StatusPurpose; -use identity_iota::credential::Credential; -use identity_iota::credential::CredentialBuilder; -use identity_iota::credential::FailFast; -use identity_iota::credential::Issuer; -use identity_iota::credential::Jwt; -use identity_iota::credential::JwtCredentialValidationOptions; -use identity_iota::credential::JwtCredentialValidator; -use identity_iota::credential::JwtCredentialValidatorUtils; -use identity_iota::credential::JwtValidationError; -use identity_iota::credential::Status; -use identity_iota::credential::StatusCheck; -use identity_iota::credential::Subject; -use identity_iota::did::DID; -use identity_iota::iota::IotaDocument; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwsSignatureOptions; -use identity_iota::storage::KeyIdMemstore; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use serde_json::json; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // =========================================================================== - // Create a Verifiable Credential. - // =========================================================================== - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - let mut secret_manager_issuer: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_1".to_owned())) - .build(random_stronghold_path())?, - ); - - // Create an identity for the issuer with one verification method `key-1`. - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_issuer, &storage_issuer).await?; - - // Create an identity for the holder, in this case also the subject. - let mut secret_manager_alice: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?, - ); - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, alice_document, _): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_alice, &storage_alice).await?; - - // Create a new empty status list. No credentials have been revoked yet. - let status_list: StatusList2021 = StatusList2021::default(); - - // Create a status list credential so that the status list can be stored anywhere. - // The issuer makes this credential available on `http://example.com/credential/status`. - // For the purposes of this example, the credential will be used directly without fetching. - let status_list_credential: StatusList2021Credential = StatusList2021CredentialBuilder::new(status_list) - .purpose(StatusPurpose::Revocation) - .subject_id(Url::parse("http://example.com/credential/status")?) - .issuer(Issuer::Url(issuer_document.id().to_url().into())) - .build()?; - - println!("Status list credential > {status_list_credential:#}"); - - // Create a credential subject indicating the degree earned by Alice. - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - // Create an unsigned `UniversityDegree` credential for Alice. - // The issuer also chooses a unique `StatusList2021` index to be able to revoke it later. - let credential_index: usize = 420; - let status: Status = StatusList2021Entry::new( - status_list_credential.id().cloned().unwrap(), - status_list_credential.purpose(), - credential_index, - None, - ) - .into(); - - // Build credential using subject above, status, and issuer. - let mut credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .status(status) - .subject(subject) - .build()?; - - println!("Credential JSON > {credential:#}"); - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - let validator: JwtCredentialValidator = - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()); - - // The validator has no way of retriving the status list to check for the - // revocation of the credential. Let's skip that pass and perform the operation manually. - let mut validation_options = JwtCredentialValidationOptions::default(); - validation_options.status = StatusCheck::SkipUnsupported; - // Validate the credential's signature using the issuer's DID Document. - validator.validate::<_, Object>( - &credential_jwt, - &issuer_document, - &validation_options, - FailFast::FirstError, - )?; - // Check manually for revocation - JwtCredentialValidatorUtils::check_status_with_status_list_2021( - &credential, - &status_list_credential, - StatusCheck::Strict, - )?; - println!("Credential is valid."); - - let status_list_credential_json = status_list_credential.to_json().unwrap(); - - // =========================================================================== - // Revocation of the Verifiable Credential. - // =========================================================================== - - // At a later time, the issuer university found out that Alice cheated in her final exam. - // The issuer will revoke Alice's credential. - - // The issuer retrieves the status list credential. - let mut status_list_credential = - serde_json::from_str::(status_list_credential_json.as_str()).unwrap(); - - // Set the value of the chosen index entry to true to revoke the credential - status_list_credential.set_credential_status(&mut credential, credential_index, true)?; - - // validate the credential and check for revocation - validator.validate::<_, Object>( - &credential_jwt, - &issuer_document, - &validation_options, - FailFast::FirstError, - )?; - let revocation_result = JwtCredentialValidatorUtils::check_status_with_status_list_2021( - &credential, - &status_list_credential, - StatusCheck::Strict, - ); - - assert!(revocation_result.is_err_and(|e| matches!(e, JwtValidationError::Revoked))); - println!("The credential has been successfully revoked."); - - Ok(()) -} diff --git a/examples/iota/Cargo.toml b/examples/iota/Cargo.toml deleted file mode 100644 index bc981ef0c9..0000000000 --- a/examples/iota/Cargo.toml +++ /dev/null @@ -1,105 +0,0 @@ -[package] -name = "examples_iota" -version = "1.2.0" -authors = ["IOTA Stiftung"] -edition = "2021" -publish = false - -[dependencies] -anyhow = "1.0.62" -identity_eddsa_verifier = { path = "../../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } -identity_stronghold = { path = "../../identity_stronghold", default-features = false, features = ["send-sync-storage"] } -iota-sdk = { version = "1.0", default-features = false, features = ["tls", "client", "stronghold"] } -primitive-types = "0.12.1" -rand = "0.8.5" -sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"] } -serde_json = { version = "1.0", default-features = false } -tokio = { version = "1.29", default-features = false, features = ["rt"] } - -[dependencies.identity_iota] -path = "../../identity_iota" -default-features = false -features = [ - "iota-client", - "client", - "memstore", - "domain-linkage", - "revocation-bitmap", - "status-list-2021", - "resolver", -] - -[lib] -path = "utils/utils.rs" - -[[example]] -path = "0_basic/0_create_did.rs" -name = "0_create_did" - -[[example]] -path = "0_basic/1_update_did.rs" -name = "1_update_did" - -[[example]] -path = "0_basic/2_resolve_did.rs" -name = "2_resolve_did" - -[[example]] -path = "0_basic/3_deactivate_did.rs" -name = "3_deactivate_did" - -[[example]] -path = "0_basic/4_delete_did.rs" -name = "4_delete_did" - -[[example]] -path = "0_basic/5_create_vc.rs" -name = "5_create_vc" - -[[example]] -path = "0_basic/6_create_vp.rs" -name = "6_create_vp" - -[[example]] -path = "0_basic/7_revoke_vc.rs" -name = "7_revoke_vc" - -[[example]] -path = "0_basic/8_stronghold.rs" -name = "8_stronghold" - -[[example]] -path = "1_advanced/0_did_controls_did.rs" -name = "0_did_controls_did" - -[[example]] -path = "1_advanced/1_did_issues_nft.rs" -name = "1_did_issues_nft" - -[[example]] -path = "1_advanced/2_nft_owns_did.rs" -name = "2_nft_owns_did" - -[[example]] -path = "1_advanced/3_did_issues_tokens.rs" -name = "3_did_issues_tokens" - -[[example]] -path = "1_advanced/4_alias_output_history.rs" -name = "4_alias_output_history" - -[[example]] -path = "1_advanced/5_custom_resolution.rs" -name = "5_custom_resolution" - -[[example]] -path = "1_advanced/6_domain_linkage.rs" -name = "6_domain_linkage" - -[[example]] -path = "1_advanced/7_sd_jwt.rs" -name = "7_sd_jwt" - -[[example]] -path = "1_advanced/8_status_list_2021.rs" -name = "8_status_list_2021" diff --git a/examples/iota/utils/utils.rs b/examples/iota/utils/utils.rs deleted file mode 100644 index 68a0c2bc57..0000000000 --- a/examples/iota/utils/utils.rs +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::path::PathBuf; - -use anyhow::Context; - -use identity_iota::iota::block::output::AliasOutput; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::iota::NetworkName; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use identity_iota::storage::Storage; -use identity_iota::verification::jws::JwsAlgorithm; -use identity_iota::verification::MethodScope; - -use iota_sdk::client::api::GetAddressesOptions; -use iota_sdk::client::node_api::indexer::query_parameters::QueryParameter; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::crypto::keys::bip39; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::address::Bech32Address; -use iota_sdk::types::block::address::Hrp; -use rand::distributions::DistString; -use serde_json::Value; - -pub static API_ENDPOINT: &str = "http://localhost"; -pub static FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; -pub const TEST_GAS_BUDGET: u64 = 50_000_000; - -pub type MemStorage = Storage; - -/// Creates a DID Document and publishes it in a new Alias Output. -/// -/// Its functionality is equivalent to the "create DID" example -/// and exists for convenient calling from the other examples. -pub async fn create_did( - client: &Client, - secret_manager: &mut SecretManager, - storage: &MemStorage, -) -> anyhow::Result<(Address, IotaDocument, String)> { - let address: Address = get_address_with_funds(client, secret_manager, FAUCET_ENDPOINT) - .await - .context("failed to get address with funds")?; - - let network_name: NetworkName = client.network_name().await?; - - let (document, fragment): (IotaDocument, String) = create_did_document(&network_name, storage).await?; - - let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; - - let document: IotaDocument = client.publish_did_output(secret_manager, alias_output).await?; - - Ok((address, document, fragment)) -} - -/// Creates an example DID document with the given `network_name`. -/// -/// Its functionality is equivalent to the "create DID" example -/// and exists for convenient calling from the other examples. -pub async fn create_did_document( - network_name: &NetworkName, - storage: &MemStorage, -) -> anyhow::Result<(IotaDocument, String)> { - let mut document: IotaDocument = IotaDocument::new(network_name); - - let fragment: String = document - .generate_method( - storage, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA, - None, - MethodScope::VerificationMethod, - ) - .await?; - - Ok((document, fragment)) -} - -/// Generates an address from the given [`SecretManager`] and adds funds from the faucet. -pub async fn get_address_with_funds( - client: &Client, - stronghold: &SecretManager, - faucet_endpoint: &str, -) -> anyhow::Result
{ - let address: Bech32Address = get_address(client, stronghold).await?; - - request_faucet_funds(client, address, faucet_endpoint) - .await - .context("failed to request faucet funds")?; - - Ok(*address) -} - -/// Initializes the [`SecretManager`] with a new mnemonic, if necessary, -/// and generates an address from the given [`SecretManager`]. -pub async fn get_address(client: &Client, secret_manager: &SecretManager) -> anyhow::Result { - let random: [u8; 32] = rand::random(); - let mnemonic = bip39::wordlist::encode(random.as_ref(), &bip39::wordlist::ENGLISH) - .map_err(|err| anyhow::anyhow!(format!("{err:?}")))?; - - if let SecretManager::Stronghold(ref stronghold) = secret_manager { - match stronghold.store_mnemonic(mnemonic).await { - Ok(()) => (), - Err(iota_sdk::client::stronghold::Error::MnemonicAlreadyStored) => (), - Err(err) => anyhow::bail!(err), - } - } else { - anyhow::bail!("expected a `StrongholdSecretManager`"); - } - - let bech32_hrp: Hrp = client.get_bech32_hrp().await?; - let address: Bech32Address = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::default() - .with_range(0..1) - .with_bech32_hrp(bech32_hrp), - ) - .await?[0]; - - Ok(address) -} - -/// Requests funds from the faucet for the given `address`. -async fn request_faucet_funds(client: &Client, address: Bech32Address, faucet_endpoint: &str) -> anyhow::Result<()> { - iota_sdk::client::request_funds_from_faucet(faucet_endpoint, &address).await?; - - tokio::time::timeout(std::time::Duration::from_secs(45), async { - loop { - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - - let balance = get_address_balance(client, &address) - .await - .context("failed to get address balance")?; - if balance > 0 { - break; - } - } - Ok::<(), anyhow::Error>(()) - }) - .await - .context("maximum timeout exceeded")??; - - Ok(()) -} - -/// Returns the balance of the given Bech32-encoded `address`. -async fn get_address_balance(client: &Client, address: &Bech32Address) -> anyhow::Result { - let output_ids = client - .basic_output_ids(vec![ - QueryParameter::Address(address.to_owned()), - QueryParameter::HasExpiration(false), - QueryParameter::HasTimelock(false), - QueryParameter::HasStorageDepositReturn(false), - ]) - .await?; - - let outputs = client.get_outputs(&output_ids).await?; - - let mut total_amount = 0; - for output_response in outputs { - total_amount += output_response.output().amount(); - } - - Ok(total_amount) -} - -/// Creates a random stronghold path in the temporary directory, whose exact location is OS-dependent. -pub fn random_stronghold_path() -> PathBuf { - let mut file = std::env::temp_dir(); - file.push("test_strongholds"); - file.push(rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 32)); - file.set_extension("stronghold"); - file.to_owned() -} - -pub fn pretty_print_json(label: &str, value: &str) { - let data: Value = serde_json::from_str(value).unwrap(); - let pretty_json = serde_json::to_string_pretty(&data).unwrap(); - println!("--------------------------------------"); - println!("{}:", label); - println!("--------------------------------------"); - println!("{} \n", pretty_json); -} diff --git a/examples/kinesis/README.md b/examples/kinesis/README.md deleted file mode 100644 index 2076f8b4b2..0000000000 --- a/examples/kinesis/README.md +++ /dev/null @@ -1,51 +0,0 @@ -![banner](./../documentation/static/img/Banner/banner_identity.svg) - -# IOTA Identity Examples - -This folder provides code examples to learn how IOTA Identity can be used. - -You can run each example using - -```rust -cargo run --example -``` - -For instance, to run the example `0_create_did`, use: - -```rust -cargo run --release --example 0_create_did -``` - -### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. - - -## Basic Examples - -The following basic CRUD (Create, Read, Update, Delete) examples are available: - -| Name | Information | -|:--------------------------------------------------|:-------------------------------------------------------------------------------------| -| [0_create_did](./0_basic/0_create_did.rs) | Demonstrates how to create a DID Document and publish it in a new Alias Output. | -| [1_update_did](./0_basic/1_update_did.rs) | Demonstrates how to update a DID document in an existing Alias Output. | -| [2_resolve_did](./0_basic/2_resolve_did.rs) | Demonstrates how to resolve an existing DID in an Alias Output. | -| [3_deactivate_did](./0_basic/3_deactivate_did.rs) | Demonstrates how to deactivate a DID in an Alias Output. | -| [4_delete_did](./0_basic/4_delete_did.rs) | Demonstrates how to delete a DID in an Alias Output, reclaiming the storage deposit. | -| [5_create_vc](./0_basic/5_create_vc.rs) | Demonstrates how to create and verify verifiable credentials. | -| [6_create_vp](./0_basic/6_create_vp.rs) | Demonstrates how to create and verify verifiable presentations. | -| [7_revoke_vc](./0_basic/7_revoke_vc.rs) | Demonstrates how to revoke a verifiable credential. | - -## Advanced Examples - -The following advanced examples are available: - -| Name | Information | -|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------| -| [0_did_controls_did](./1_advanced/0_did_controls_did.rs) | Demonstrates how an identity can control another identity. | -| [1_did_issues_nft](./1_advanced/1_did_issues_nft.rs) | Demonstrates how an identity can issue and own NFTs, and how observers can verify the issuer of the NFT. | -| [2_nft_owns_did](./1_advanced/2_nft_owns_did.rs) | Demonstrates how an identity can be owned by NFTs, and how observers can verify that relationship. | -| [3_did_issues_tokens](./1_advanced/3_did_issues_tokens.rs) | Demonstrates how an identity can issue and control a Token Foundry and its tokens. | -| [4_alias_output_history](./1_advanced/4_alias_output_history.rs) | Demonstrates fetching the history of an Alias Output. | -| [5_custom_resolution](./1_advanced/5_custom_resolution.rs) | Demonstrates how to set up a resolver using custom handlers. | -| [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | -| [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | -| [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | diff --git a/examples/kinesis/utils/utils.rs b/examples/utils/utils.rs similarity index 97% rename from examples/kinesis/utils/utils.rs rename to examples/utils/utils.rs index a370473dc1..75bec23e7e 100644 --- a/examples/kinesis/utils/utils.rs +++ b/examples/utils/utils.rs @@ -31,8 +31,6 @@ use rand::distributions::DistString; use secret_storage::Signer; use serde_json::Value; -pub static API_ENDPOINT: &str = "http://localhost"; -pub static FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; pub const TEST_GAS_BUDGET: u64 = 50_000_000; pub type MemStorage = Storage; @@ -107,7 +105,9 @@ where .and_then(|pkg_str| pkg_str.parse().context("invalid package id"))?; let read_only_client = IdentityClientReadOnly::new(iota_client, package_id).await?; + let signer = StorageSigner::new(storage, generate.key_id, public_key_jwk); + let identity_client = IdentityClient::new(read_only_client, signer).await?; Ok(identity_client) diff --git a/identity_sui_name_tbd/packages/identity_iota/Move.lock b/identity_sui_name_tbd/packages/identity_iota/Move.lock index 1699994980..b2d404d3e1 100644 --- a/identity_sui_name_tbd/packages/identity_iota/Move.lock +++ b/identity_sui_name_tbd/packages/identity_iota/Move.lock @@ -47,6 +47,6 @@ published-version = "1" [env.localnet] chain-id = "3e40a728" -original-published-id = "0x40406765d42bf9042457124736ad6ab0eacaf959eee35a1a25b74de92402aeea" -latest-published-id = "0x40406765d42bf9042457124736ad6ab0eacaf959eee35a1a25b74de92402aeea" +original-published-id = "0x656cabb08dfe1f71e89a9682beb8f5f250042e92cdd8591ae7fc010fb811abf1" +latest-published-id = "0x656cabb08dfe1f71e89a9682beb8f5f250042e92cdd8591ae7fc010fb811abf1" published-version = "1"