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 revocation examples #1076

Merged
merged 8 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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 bindings/wasm/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ The following basic CRUD (Create, Read, Update, Delete) examples are available:
| [2_resolve_did](src/0_basic/2_resolve_did.ts) | Demonstrates how to resolve an existing DID in an Alias Output. |
| [3_deactivate_did](src/0_basic/3_deactivate_did.ts) | Demonstrates how to deactivate a DID in an Alias Output. |
| [4_delete_did](src/0_basic/4_delete_did.ts) | Demonstrates how to delete a DID in an Alias Output, reclaiming the storage deposit. |
| [5_create_vc](src/0_basic/5_create_vc.ts) | Demonstrates how to create and verify verifiable credentials. |
| [5_create_vc](src/0_basic/5_create_vc.ts) | Demonstrates how to create and verify verifiable credentials. |
| [6_create_vp](src/0_basic/6_create_vp.ts) | Demonstrates how to create and verify verifiable presentations. |
| [7_revoke_vc](src/0_basic/7_revoke_vc.ts) | Demonstrates how to revoke a verifiable credential. |

## Advanced Examples

Expand Down
157 changes: 157 additions & 0 deletions bindings/wasm/examples/src/0_basic/7_revoke_vc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { Bip39 } from "@iota/crypto.js";
import { Client, MnemonicSecretManager } from "@iota/iota-client-wasm/node";
import { IAliasOutput, IRent, TransactionHelper } from "@iota/iota.js";
import {
Credential,
CredentialValidationOptions,
CredentialValidator,
FailFast,
IotaDocument,
IotaIdentityClient,
IotaService,
IotaVerificationMethod,
ProofOptions,
Resolver,
RevocationBitmap,
} from "../../../node";
import { API_ENDPOINT, createDid } from "../util";

/**
* 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.
*/
export async function revokeVC() {
// ===========================================================================
// Create a Verifiable Credential.
// ===========================================================================

const client = await Client.new({
primaryNode: API_ENDPOINT,
localPow: true,
});
const didClient = new IotaIdentityClient(client);

// Generate a random mnemonic for our wallet.
const secretManager: MnemonicSecretManager = {
mnemonic: Bip39.randomMnemonic(),
};

// Create an identity for the issuer with one verification method `key-1`.
let { document: issuerDocument, keypair: keypairIssuer } = await createDid(client, secretManager);

// Create an identity for the holder, in this case also the subject.
const { document: aliceDocument } = await createDid(client, secretManager);

// Create a new empty revocation bitmap. No credential is revoked yet.
const revocationBitmap = new RevocationBitmap();

// Add the revocation bitmap to the DID Document of the issuer as a service.
const service: IotaService = new IotaService({
id: issuerDocument.id().join("#my-revocation-service"),
type: RevocationBitmap.type(),
serviceEndpoint: revocationBitmap.toEndpoint(),
});
issuerDocument.insertService(service);

// Resolve the latest output and update it with the given document.
let aliasOutput: IAliasOutput = await didClient.updateDidOutput(issuerDocument);

// 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 rentStructure: IRent = await didClient.getRentStructure();
aliasOutput.amount = TransactionHelper.getStorageDeposit(aliasOutput, rentStructure).toString();

// Publish the document.
issuerDocument = await didClient.publishDidOutput(secretManager, aliasOutput);

// Create a credential subject indicating the degree earned by Alice, linked to their DID.
const subject = {
id: aliceDocument.id(),
name: "Alice",
degreeName: "Bachelor of Science and Arts",
degreeType: "BachelorDegree",
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.
const CREDENTIAL_INDEX = 5;
const unsignedVc = new Credential({
id: "https://example.edu/credentials/3732",
type: "UniversityDegreeCredential",
credentialStatus: {
id: issuerDocument.id() + "#my-revocation-service",
type: RevocationBitmap.type(),
revocationBitmapIndex: CREDENTIAL_INDEX,
},
issuer: issuerDocument.id(),
credentialSubject: subject,
});

// Sign Credential.
let signedVc = issuerDocument.signCredential(unsignedVc, keypairIssuer.private(), "#key-1", ProofOptions.default());
console.log(`Credential JSON > ${JSON.stringify(signedVc, null, 2)}`);

// ===========================================================================
// Revocation of the Verifiable Credential.
// ===========================================================================

abdulmth marked this conversation as resolved.
Show resolved Hide resolved
// Update the RevocationBitmap service in the issuer's DID Document.
// This revokes the credential's unique index.
issuerDocument.revokeCredentials("my-revocation-service", CREDENTIAL_INDEX);

// Publish the changes.
aliasOutput = await didClient.updateDidOutput(issuerDocument);
rentStructure = await didClient.getRentStructure();
aliasOutput.amount = TransactionHelper.getStorageDeposit(aliasOutput, rentStructure).toString();
const update2: IotaDocument = await didClient.publishDidOutput(secretManager, aliasOutput);

// Credential verification now fails.
try {
CredentialValidator.validate(signedVc, update2, CredentialValidationOptions.default(), FailFast.FirstError);
console.log("Revocation Failed!");
} catch (e) {
console.log(`Error during validation: ${e}`);
}

// ===========================================================================
// 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 originalMethod = issuerDocument.resolveMethod("#key-1") as IotaVerificationMethod;
await issuerDocument.removeMethod(originalMethod.id());

// Publish the changes.
aliasOutput = await didClient.updateDidOutput(issuerDocument);
rentStructure = await didClient.getRentStructure();
aliasOutput.amount = TransactionHelper.getStorageDeposit(aliasOutput, rentStructure).toString();
issuerDocument = await didClient.publishDidOutput(secretManager, aliasOutput);

// We expect the verifiable credential to be revoked.
const resolver = new Resolver({ client: didClient });
try {
// Resolve the issuer's updated DID Document to ensure the key was revoked successfully.
const resolvedIssuerDoc = await resolver.resolve(issuerDocument.id().toString());
CredentialValidator.validate(
signedVc,
resolvedIssuerDoc,
CredentialValidationOptions.default(),
FailFast.FirstError,
);

// `CredentialValidator.validate` will throw an error, hence this will not be reached.
console.log("Revocation failed!");
} catch (e) {
console.log(`Error during validation: ${e}`);
console.log(`Credential successfully revoked!`);
}
}
3 changes: 3 additions & 0 deletions bindings/wasm/examples/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { deactivateIdentity } from "./0_basic/3_deactivate_did";
import { deleteIdentity } from "./0_basic/4_delete_did";
import { createVC } from "./0_basic/5_create_vc";
import { createVP } from "./0_basic/6_create_vp";
import { revokeVC } from "./0_basic/7_revoke_vc";
import { didControlsDid } from "./1_advanced/0_did_controls_did";
import { didIssuesNft } from "./1_advanced/1_did_issues_nft";
import { nftOwnsDid } from "./1_advanced/2_nft_owns_did";
Expand Down Expand Up @@ -37,6 +38,8 @@ async function main() {
return await createVC();
case "6_create_vp":
return await createVP();
case "7_revoke_vc":
return await revokeVC();
case "0_did_controls_did":
return await didControlsDid();
case "1_did_issues_nft":
Expand Down
8 changes: 8 additions & 0 deletions bindings/wasm/examples/src/tests/7_revoke_vc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { revokeVC } from "../0_basic/7_revoke_vc";

// Only verifies that no uncaught exceptions are thrown, including syntax errors etc.
describe("Test node examples", function() {
it("Revoke VC", async () => {
await revokeVC();
});
});
192 changes: 192 additions & 0 deletions examples/0_basic/7_revoke_vc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright 2020-2022 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 --example 7_revoke_vc

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::Url;
use identity_iota::credential::AbstractThreadSafeValidatorDocument;
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::RevocationBitmapStatus;
use identity_iota::credential::Status;
use identity_iota::credential::Subject;
use identity_iota::credential::ValidationError;
use identity_iota::crypto::KeyPair;
use identity_iota::crypto::ProofOptions;
use identity_iota::did::DIDUrl;
use identity_iota::did::Document;
use identity_iota::did::RevocationBitmap;
use identity_iota::did::Service;
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::iota::IotaService;
use identity_iota::resolver::Resolver;
use iota_client::block::address::Address;
use iota_client::block::output::AliasOutput;
use iota_client::block::output::AliasOutputBuilder;
use iota_client::block::output::RentStructure;
use iota_client::secret::stronghold::StrongholdSecretManager;
use iota_client::secret::SecretManager;
use iota_client::Client;

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

// 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 (_, alice_document, _): (Address, IotaDocument, KeyPair) = create_did(&client, &mut secret_manager_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: IotaService = Service::from_json_value(json!({
"id": issuer_document.id().to_url().join("#my-revocation-service")?,
"type": RevocationBitmap::TYPE,
"serviceEndpoint": revocation_bitmap.to_endpoint()?
}))?;
issuer_document.insert_service(service);

// 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()?;
let alias_output: AliasOutput = AliasOutputBuilder::from(&alias_output)
.with_minimum_storage_deposit(rent_structure)
.finish(client.get_token_supply()?)?;

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

// 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);

// ===========================================================================
// Revocation of the Verifiable Credential.
// ===========================================================================

abdulmth marked this conversation as resolved.
Show resolved Hide resolved
// 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()?;
let alias_output: AliasOutput = AliasOutputBuilder::from(&alias_output)
.with_minimum_storage_deposit(rent_structure)
.finish(client.get_token_supply()?)?;
issuer_document = client.publish_did_output(&secret_manager_issuer, alias_output).await?;

let validation_result = CredentialValidator::validate(
&credential,
&issuer_document,
&CredentialValidationOptions::default(),
FailFast::FirstError,
);

// We expect validation to no longer succeed because the credential was revoked.
assert!(matches!(
validation_result.unwrap_err().validation_errors[0],
ValidationError::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<IotaDID> = issuer_document.resolve_method("#key-1", None).unwrap().id().clone();
issuer_document.remove_method(&original_method).unwrap();

// 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.get_token_supply()?)?;
client.publish_did_output(&secret_manager_issuer, alias_output).await?;

// We expect the verifiable credential to be revoked.
let mut resolver: Resolver = Resolver::new();
abdulmth marked this conversation as resolved.
Show resolved Hide resolved
resolver.attach_iota_handler(client);
let resolved_issuer_doc: AbstractThreadSafeValidatorDocument =
resolver.resolve_credential_issuer(&credential).await?;

let validation_result = CredentialValidator::validate(
&credential,
&resolved_issuer_doc,
&CredentialValidationOptions::default(),
FailFast::FirstError,
);

println!("VC validation result: {:?}", validation_result);
assert!(validation_result.is_err());

println!("Credential successfully revoked!");

Ok(())
}
4 changes: 4 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ name = "5_create_vc"
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 = "1_advanced/0_did_controls_did.rs"
name = "0_did_controls_did"
Expand Down
Loading