Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add v. credentials and presentations examples #1070

Merged
merged 7 commits into from
Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/0_basic/1_update_did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ async fn main() -> anyhow::Result<()> {
);

// Create a new DID in an Alias Output for us to modify.
let (_, did): (Address, IotaDID) = create_did(&client, &mut secret_manager).await?;
let (_, document, _): (Address, IotaDocument, KeyPair) = create_did(&client, &mut secret_manager).await?;
let did: IotaDID = document.id().clone();

// Resolve the latest state of the document.
let mut document: IotaDocument = client.resolve_did(&did).await?;
Expand Down
4 changes: 3 additions & 1 deletion examples/0_basic/2_resolve_did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use examples::create_did;
use examples::random_stronghold_path;
use examples::API_ENDPOINT;
use identity_iota::crypto::KeyPair;
use identity_iota::iota::block::address::Address;
use identity_iota::iota::IotaDID;
use identity_iota::iota::IotaDocument;
Expand All @@ -28,7 +29,8 @@ async fn main() -> anyhow::Result<()> {
);

// Create a new DID in an Alias Output for us to resolve.
let (_, did): (Address, IotaDID) = create_did(&client, &mut secret_manager).await?;
let (_, document, _): (Address, IotaDocument, KeyPair) = create_did(&client, &mut secret_manager).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.
Expand Down
6 changes: 4 additions & 2 deletions examples/0_basic/3_deactivate_did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use examples::create_did;
use examples::random_stronghold_path;
use examples::API_ENDPOINT;
use identity_iota::crypto::KeyPair;
use identity_iota::iota::block::address::Address;
use identity_iota::iota::IotaClientExt;
use identity_iota::iota::IotaDID;
Expand All @@ -29,9 +30,10 @@ async fn main() -> anyhow::Result<()> {
);

// Create a new DID in an Alias Output for us to modify.
let (_, did): (Address, IotaDID) = create_did(&client, &mut secret_manager).await?;
let (_, document, _): (Address, IotaDocument, KeyPair) = create_did(&client, &mut secret_manager).await?;
let did: IotaDID = document.id().clone();

// Resolve the latest state of the DID document, so we can reactivate it later.
// Resolve the latest state of the DID document.
let document: IotaDocument = client.resolve_did(&did).await?;

// Deactivate the DID by publishing an empty document.
Expand Down
5 changes: 4 additions & 1 deletion examples/0_basic/4_delete_did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
use examples::create_did;
use examples::random_stronghold_path;
use examples::API_ENDPOINT;
use identity_iota::crypto::KeyPair;
use identity_iota::iota::Error;
use identity_iota::iota::IotaClientExt;
use identity_iota::iota::IotaDID;
use identity_iota::iota::IotaDocument;
use identity_iota::iota::IotaIdentityClientExt;
use iota_client::block::address::Address;
use iota_client::secret::stronghold::StrongholdSecretManager;
Expand All @@ -27,7 +29,8 @@ async fn main() -> anyhow::Result<()> {
);

// Create a new DID in an Alias Output for us to modify.
let (address, did): (Address, IotaDID) = create_did(&client, &mut secret_manager).await?;
let (address, document, _): (Address, IotaDocument, KeyPair) = create_did(&client, &mut secret_manager).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.
Expand Down
102 changes: 102 additions & 0 deletions examples/0_basic/5_create_vc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use examples::create_did;
use examples::random_stronghold_path;
use examples::API_ENDPOINT;
use identity_iota::core::json;
use identity_iota::core::FromJson;
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::CredentialValidationOptions;
use identity_iota::credential::CredentialValidator;
use identity_iota::credential::FailFast;
use identity_iota::credential::Subject;
use identity_iota::crypto::KeyPair;
use identity_iota::crypto::KeyType;
use identity_iota::crypto::ProofOptions;
use identity_iota::did::MethodRelationship;
use identity_iota::did::MethodScope;
use identity_iota::did::DID;
use identity_iota::iota::block::output::AliasOutput;
use identity_iota::iota::block::output::AliasOutputBuilder;
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::iota::IotaVerificationMethod;
use iota_client::block::address::Address;
use iota_client::secret::stronghold::StrongholdSecretManager;
use iota_client::secret::SecretManager;
use iota_client::Client;

#[tokio::main]
async fn main() -> anyhow::Result<String> {
olivereanderson marked this conversation as resolved.
Show resolved Hide resolved
// Create a new client to interact with the IOTA ledger.
let client: Client = Client::builder().with_primary_node(API_ENDPOINT, None)?.finish()?;

// Create an identity for the issuer with one verification method `key-1`.
let mut secret_manager_issuer: SecretManager = SecretManager::Stronghold(
StrongholdSecretManager::builder()
.password("secure_password_1")
.build(random_stronghold_path())?,
);
let (_, mut issuer_document, key_pair): (Address, IotaDocument, KeyPair) =
create_did(&client, &mut secret_manager_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("secure_password_2")
.build(random_stronghold_path())?,
);
let (_, mut alice_document, _): (Address, IotaDocument, KeyPair) =
create_did(&client, &mut secret_manager_alice).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 mut 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()?;

// Sign the Credential with the issuer's verification method.
issuer_document.sign_data(&mut credential, key_pair.private(), "#key-1", ProofOptions::default());
println!("Credential JSON > {:#}", credential);

// 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:
CredentialValidator::validate(
&credential,
&issuer_document,
&CredentialValidationOptions::default(),
FailFast::FirstError,
)
.unwrap();

println!("VC successfully validated");

// The issuer is now sure that the credential they are about to issue satisfies their expectations.
// The credential is then serialized to JSON and transmitted to the subject in a secure manner.
let credential_json: String = credential.to_json()?;
return Ok(credential_json);
}
198 changes: 198 additions & 0 deletions examples/0_basic/6_create_vp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use iota_client::block::address::Address;
use iota_client::secret::stronghold::StrongholdSecretManager;
use iota_client::secret::SecretManager;
use iota_client::Client;

use examples::create_did;
use examples::random_stronghold_path;
use examples::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::ToJson;
use identity_iota::core::Url;
use identity_iota::credential::Credential;
use identity_iota::credential::CredentialBuilder;
use identity_iota::credential::CredentialValidationOptions;
use identity_iota::credential::CredentialValidator;
use identity_iota::credential::FailFast;
use identity_iota::credential::Presentation;
use identity_iota::credential::PresentationBuilder;
use identity_iota::credential::PresentationValidationOptions;
use identity_iota::credential::PresentationValidator;
use identity_iota::credential::Subject;
use identity_iota::credential::SubjectHolderRelationship;
use identity_iota::crypto::KeyPair;
use identity_iota::crypto::ProofOptions;
use identity_iota::did::verifiable::VerifierOptions;
use identity_iota::did::DID;
use identity_iota::iota::IotaDocument;

#[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()?;

// Create an identity for the issuer with one verification method `key-1`.
let mut secret_manager_issuer: SecretManager = SecretManager::Stronghold(
StrongholdSecretManager::builder()
.password("secure_password_1")
.build(random_stronghold_path())?,
);
let (_, issuer_document, key_pair_issuer): (Address, IotaDocument, KeyPair) =
create_did(&client, &mut secret_manager_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("secure_password_2")
.build(random_stronghold_path())?,
);
let (_, alice_document, key_pair_alice): (Address, IotaDocument, KeyPair) =
create_did(&client, &mut secret_manager_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 mut 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()?;

// Sign the Credential with the issuer's verification method.
issuer_document.sign_data(
&mut credential,
key_pair_issuer.private(),
"#key-1",
ProofOptions::default(),
)?;
println!("Credential JSON > {:#}", credential);

// 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:
CredentialValidator::validate(
&credential,
&issuer_document,
&CredentialValidationOptions::default(),
FailFast::FirstError,
)
.unwrap();

println!("VC successfully validated");

// ===========================================================================
// Step 3: Issuer sends the Verifiable Credential to the holder.
// ===========================================================================

// The issuer is now sure that the credential they are about to issue satisfies their expectations.
// The credential is then serialized to JSON and transmitted to the subject in a secure manner.
let credential_json: String = credential.to_json()?;

// ===========================================================================
// 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.
// ===========================================================================

// Deserialize the credential.
let credential: Credential = Credential::from_json(credential_json.as_str())?;

// Create an unsigned Presentation from the previously issued Verifiable Credential.
let mut presentation: Presentation = PresentationBuilder::default()
.holder(Url::parse(alice_document.id().as_ref())?)
.credential(credential)
.build()?;

// Sign the verifiable presentation using the holder's verification method
// and include the requested challenge and expiry timestamp.
alice_document.sign_data(
&mut presentation,
key_pair_alice.private(),
"#key-1",
ProofOptions::new().challenge(challenge.to_string()).expires(expires),
)?;

// ===========================================================================
// Step 6: Holder sends a verifiable presentation to the verifier.
// ===========================================================================

// Convert the Verifiable Presentation to JSON to send it to the verifier.
let presentation_json: String = presentation.to_json()?;

// ===========================================================================
// Step 7: Verifier receives the Verifiable Presentation and verifies it.
// ===========================================================================

// Deserialize the presentation from the holder:
let presentation: Presentation = Presentation::from_json(&presentation_json)?;

// The verifier wants the following requirements to be satisfied:
// - Signature verification (including checking the requested challenge to mitigate replay attacks)
// - Presentation validation must fail if credentials expiring within the next 10 hours are encountered
// - 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: VerifierOptions = VerifierOptions::new()
.challenge(challenge.to_owned())
.allow_expired(false);

// Do not allow credentials that expire within the next 10 hours.
let credential_validation_options: CredentialValidationOptions = CredentialValidationOptions::default()
.earliest_expiry_date(Timestamp::now_utc().checked_add(Duration::hours(10)).unwrap());

let presentation_validation_options = PresentationValidationOptions::default()
.presentation_verifier_options(presentation_verifier_options.clone())
.shared_validation_options(credential_validation_options)
.subject_holder_relationship(SubjectHolderRelationship::AlwaysSubject);

PresentationValidator::validate(
&presentation,
&alice_document,
&[issuer_document],
&presentation_validation_options,
FailFast::FirstError,
)?;
olivereanderson marked this conversation as resolved.
Show resolved Hide resolved

// Since no errors were thrown by `verify_presentation` we know that the validation was successful.
println!("VP successfully validated");

// 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.

return Ok(());
}
3 changes: 2 additions & 1 deletion examples/1_advanced/0_did_controls_did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ async fn main() -> anyhow::Result<()> {
);

// Create a new DID for the company.
let (_, company_did): (Address, IotaDID) = create_did(&client, &mut secret_manager).await?;
let (_, company_document, _): (Address, IotaDocument, KeyPair) = create_did(&client, &mut secret_manager).await?;
let company_did = company_document.id().clone();

// Get the current byte costs and network name.
let rent_structure: RentStructure = client.get_rent_structure()?;
Expand Down
Loading