Resolve an IOTA Identity
DID resolution is the process of fetching a DID Document corresponding to a given DID.
The IOTA Identity Framework supports resolving DID Documents that are stored on an IOTA Tangle (public or private). The main tool supplied
by the IOTA Identity Framework to handle DID Document resolution in a type safe manner is the Resolver
. A DID Resolver as defined in the W3C Decentralized Identifiers specification
enforces the signature of the resolution function in a manner that is more centered around Web/API resolution rather than a strongly typed framework. This is the reason why the Resolver
provided by the IOTA Identity Framework deviates somewhat from
the W3C specification.
Resolving a DID from the main network
The following example demonstrates how to resolve the DID: "did:iota:H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" from the main
network.
- Rust
- Node.js
use identity_iota::client::Resolver;
use identity_iota::iota_core::IotaDID;
use identity_iota::client::ResolvedIotaDocument;
let resolver: Resolver = Resolver::new().await?;
let did: IotaDID = IotaDID::parse("did:iota:H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV")?;
let doc: ResolvedIotaDocument = resolver.resolve(&did).await?;
const {
DID,
Resolver,
ResolvedDocument,
} = require('@iota/identity-wasm/node');
const resolver = new Resolver();
const did = DID.parse("did:iota:H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV");
const doc = await resolver.resolve(did);
Resolving from a private tangle
Resolving a DID from a private tangle is similar to resolving a DID from the main net. The only difference is that
the resolver needs to be configured to have a client capable of operating on said private tangle. Building a Client
configured for a specified Tangle is explained in this example in Rust and this example in Javascript.
The following example demonstrates how one can setup a Resolver
with a given client
and then attempt resolving a specified did
which may be on any Tangle (public or private).
- Rust
- Node.js
use identity_iota::client::Resolver;
use identity_iota::client::ResolverBuilder;
use identity_iota::iota_core::IotaDID;
use identity_iota::client::Client;
use identity_iota::client::Result;
async fn build_and_resolve(client: Client, did: IotaDID) -> Result<ResolvedIotaDocument> {
let resolver_builder: ResolverBuilder = ResolverBuilder::new().await?;
let resolver: Resolver = resolver_builder.client(client).build().await?;
resolver.resolve(did).await
}
const {
DID,
Resolver,
ResolvedDocument,
Client,
} = require('@iota/identity-wasm/node');
async function buildAndResolve(client, did) {
const resolver = await Resolver.builder().client(client).build();
const resolvedDocument = await resolver.resolve(did);
return resolvedDocument;
}
In the example above the resolver will automatically try to resolve the DID from the network specified in the did
(See DID Format).
If the resolver was not built with a client configured for the given network name then an error will be thrown. Note that the ResolverBuilder
can configure the Resolver
to use
multiple networks as long as they have distinct valid names (max six characters).
Note that in the context of an identity managed by an Account
the DID document can also be resolved by simply calling the resolve
method on the Account
directly.
Resolution in the context of Verifiable Presentations
As explained in Verifiable Presentations one resolves the DID Documents of the credential issuers and presentation holder
during verification of a verifiable presentation. Resolving the necessary DID Documents is done automatically when verifying presentations via the Resolver
, but there are certain
advanced use cases where more control is desired. To accommodate for such situations the Resolver
also comes equipped with additional stand alone methods that enable:
- resolving a presentation holder's DID Document
- resolving all DID Documents of the distinct issuers of the credentials contained in the presentation
- resolving the issuer's DID Document for a given verifiable credential
Resolving the history of a DID Document.
The fact that a DID Document can be updated implies that the state of the DID Document can change over time, or in other words the result of resolving a DID
also depends on when this operation was carried out. The Resolver
provides a way to view the entire history of a DID Document (up to the time when the method is called).
- Rust
- Node.js
use identity_iota::client::Resolver;
use identity_iota::iota_core::IotaDID;
use identity_iota::client::DocumentHistory;
use identity_iota::client::Result;
async fn call_resolve_history(did: IotaDID) -> Result<DocumentHistory> {
let resolver: Resolver = Resolver::new().await?;
resolver.resolve_history(did).await?
}
const {
DID,
Resolver,
DocumentHistory,
} = require('@iota/identity-wasm/node');
async function callResolveHistory(did) {
const resolver = new Resolver();
const documentHistory = await resolver.resolveHistory(did);
return documentHistory;
}
Complete examples
This section shows complete examples from the Iota Identity Framework code base. The first example creates a DID Document, publishes it to the Tangle and then resolves it.
This second example demonstrates creating, publishing changes and then resolving the history of a DID Document.
- Node.js
- Rust
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
import {
Client,
Document,
KeyPair,
KeyType,
MethodScope,
Service,
Timestamp,
VerificationMethod
} from '@iota/identity-wasm';
import {createIdentity} from "./create_did";
/**
Advanced example that performs multiple updates and demonstrates how to resolve the DID Document history to view them.
@param {{network: Network, explorer: ExplorerUrl}} clientConfig
**/
async function resolveHistory(clientConfig) {
// Create a client instance to publish messages to the configured Tangle network.
const client = await Client.fromConfig({
network: clientConfig.network
});
// ===========================================================================
// DID Creation
// ===========================================================================
// Create a new identity (see "create_did.js" example).
const {doc, key, receipt: originalReceipt} = await createIdentity(clientConfig);
// ===========================================================================
// Integration Chain Spam
// ===========================================================================
// Publish several spam messages to the same index as the integration chain on the Tangle.
// These are not valid DID documents and are simply to demonstrate that invalid messages can be
// included in the history, potentially for debugging invalid DID documents.
const intIndex = doc.integrationIndex();
await client.publishJSON(intIndex, {"intSpam:1": true});
await client.publishJSON(intIndex, {"intSpam:2": true});
await client.publishJSON(intIndex, {"intSpam:3": true});
await client.publishJSON(intIndex, {"intSpam:4": true});
await client.publishJSON(intIndex, {"intSpam:5": true});
// ===========================================================================
// Integration Chain Update 1
// ===========================================================================
// Prepare an integration chain update, which writes the full updated DID document to the Tangle.
const intDoc1 = doc.clone();
// Add a new Service with the tag "linked-domain-1"
const service1 = new Service({
id: intDoc1.id().toUrl().join("#linked-domain-1"),
type: "LinkedDomains",
serviceEndpoint: "https://iota.org",
});
intDoc1.insertService(service1);
// Add a second Service with the tag "linked-domain-2"
const service2 = new Service({
id: intDoc1.id().toUrl().join("#linked-domain-2"),
type: "LinkedDomains",
serviceEndpoint: {
"origins": ["https://iota.org/", "https://example.com/"]
},
});
intDoc1.insertService(service2);
// Add a new VerificationMethod with a new KeyPair, with the tag "keys-1"
const keys1 = new KeyPair(KeyType.Ed25519);
const method1 = new VerificationMethod(intDoc1.id(), keys1.type(), keys1.public(), "keys-1");
intDoc1.insertMethod(method1, MethodScope.VerificationMethod());
// Add the `messageId` of the previous message in the chain.
// This is REQUIRED in order for the messages to form a chain.
// Skipping / forgetting this will render the publication useless.
intDoc1.setMetadataPreviousMessageId(originalReceipt.messageId());
intDoc1.setMetadataUpdated(Timestamp.nowUTC());
// Sign the DID Document with the original private key.
intDoc1.signSelf(key, intDoc1.defaultSigningMethod().id());
// Publish the updated DID Document to the Tangle, updating the integration chain.
// This may take a few seconds to complete proof-of-work.
const intReceipt1 = await client.publishDocument(intDoc1);
// Log the results.
console.log(`Int. Chain Update (1): ${clientConfig.explorer.messageUrl(intReceipt1.messageId())}`);
// ===========================================================================
// DID History 1
// ===========================================================================
// Retrieve the message history of the DID.
const history1 = await client.resolveHistory(doc.id());
// The history shows two documents in the integration chain.
console.log(`History (1): ${JSON.stringify(history1, null, 2)}`);
// ===========================================================================
// Integration Chain Update 2
// ===========================================================================
// Publish a second integration chain update
let intDoc2 = Document.fromJSON(intDoc1.toJSON());
// Remove the #keys-1 VerificationMethod
intDoc2.removeMethod(intDoc2.id().toUrl().join("#keys-1"));
// Remove the #linked-domain-1 Service
intDoc2.removeService(intDoc2.id().toUrl().join("#linked-domain-1"));
// Add a VerificationMethod with a new KeyPair, called "keys-2"
const keys2 = new KeyPair(KeyType.Ed25519);
const method2 = new VerificationMethod(intDoc2.id(), keys2.type(), keys2.public(), "keys-2");
intDoc2.insertMethod(method2, MethodScope.VerificationMethod());
// Note: the `previous_message_id` points to the `message_id` of the last integration chain
// update.
intDoc2.setMetadataPreviousMessageId(intReceipt1.messageId());
intDoc2.setMetadataUpdated(Timestamp.nowUTC());
intDoc2.signSelf(key, intDoc2.defaultSigningMethod().id());
const intReceipt2 = await client.publishDocument(intDoc2);
// Log the results.
console.log(`Int. Chain Update (2): ${clientConfig.explorer.messageUrl(intReceipt2.messageId())}`);
// ===========================================================================
// DID History 2
// ===========================================================================
// Retrieve the updated message history of the DID.
const history2 = await client.resolveHistory(doc.id());
// The history now shows three documents in the integration chain.
console.log(`History (2): ${JSON.stringify(history2, null, 2)}`);
}
export {resolveHistory};
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
//! Advanced example that performs multiple updates and demonstrates how to resolve the
//! DID Document history to view them.
//!
//! cargo run --example did_history
use identity_iota::client::Client;
use identity_iota::client::DocumentHistory;
use identity_iota::client::Receipt;
use identity_iota::client::Result;
use identity_iota::core::json;
use identity_iota::core::FromJson;
use identity_iota::core::Timestamp;
use identity_iota::crypto::KeyPair;
use identity_iota::did::MethodScope;
use identity_iota::did::Service;
use identity_iota::did::DID;
use identity_iota::iota_core::IotaDocument;
use identity_iota::iota_core::IotaService;
use identity_iota::iota_core::IotaVerificationMethod;
use identity_iota::prelude::*;
mod create_did;
#[rustfmt::skip]
#[tokio::main]
async fn main() -> Result<()> {
// Create a client instance to send messages to the Tangle.
let client: Client = Client::new().await?;
// ===========================================================================
// DID Creation
// ===========================================================================
// Create a signed DID Document and KeyPair (see "create_did.rs" example).
let (document, keypair, original_receipt): (IotaDocument, KeyPair, Receipt) = create_did::run().await?;
// ===========================================================================
// Integration Chain Spam
// ===========================================================================
// Publish several spam messages to the same index as the integration chain on the Tangle.
// These are not valid DID messages and are simply to demonstrate that invalid messages
// can be included in the history for debugging invalid DID documents.
let int_index: &str = document.integration_index();
client.publish_json(int_index, &json!({ "intSpam:1": true })).await?;
client.publish_json(int_index, &json!({ "intSpam:2": true })).await?;
client.publish_json(int_index, &json!({ "intSpam:3": true })).await?;
client.publish_json(int_index, &json!({ "intSpam:4": true })).await?;
client.publish_json(int_index, &json!({ "intSpam:5": true })).await?;
// ===========================================================================
// Integration Chain Update 1
// ===========================================================================
// Prepare an integration chain update, which writes the full updated DID document to the Tangle.
let int_doc_1 = {
let mut int_doc_1 = document.clone();
// Add a new Service with the tag "linked-domain-1".
let service: IotaService = Service::from_json_value(json!({
"id": int_doc_1.id().to_url().join("#linked-domain-1")?,
"type": "LinkedDomains",
"serviceEndpoint": "https://iota.org/"
}))?;
assert!(int_doc_1.insert_service(service));
// Add a second Service with the tag "linked-domain-2".
let service: IotaService = Service::from_json_value(json!({
"id": int_doc_1.id().to_url().join("#linked-domain-2")?,
"type": "LinkedDomains",
"serviceEndpoint": {
"origins": ["https://iota.org/", "https://example.com/"]
}
}))?;
assert!(int_doc_1.insert_service(service));
// Add a new VerificationMethod with a new KeyPair, with the tag "keys-1"
let keys_1: KeyPair = KeyPair::new(KeyType::Ed25519)?;
let method_1: IotaVerificationMethod = IotaVerificationMethod::new(int_doc_1.id().clone(), keys_1.type_(), keys_1.public(), "keys-1")?;
assert!(int_doc_1.insert_method(method_1, MethodScope::VerificationMethod).is_ok());
// Add the `message_id` of the previous message in the chain.
// This is REQUIRED in order for the messages to form a chain.
// Skipping / forgetting this will render the publication useless.
int_doc_1.metadata.previous_message_id = *original_receipt.message_id();
int_doc_1.metadata.updated = Some(Timestamp::now_utc());
// Sign the DID Document with the original private key.
int_doc_1.sign_self(keypair.private(), int_doc_1.default_signing_method()?.id().clone())?;
int_doc_1
};
// Publish the updated DID Document to the Tangle, updating the integration chain.
// This may take a few seconds to complete proof-of-work.
let int_receipt_1: Receipt = client.publish_document(&int_doc_1).await?;
// ===========================================================================
// DID History 1
// ===========================================================================
// Retrieve the message history of the DID.
let history_1: DocumentHistory = client.resolve_history(document.id()).await?;
// The history shows two documents in the integration chain.
println!("History (1) = {:#?}", history_1);
// ===========================================================================
// Integration Chain Update 2
// ===========================================================================
// Publish a second integration chain update
let int_doc_2 = {
let mut int_doc_2 = int_doc_1.clone();
// Remove the #keys-1 VerificationMethod
int_doc_2.remove_method(&int_doc_2.id().to_url().join("#keys-1")?)?;
// Remove the #linked-domain-1 Service
int_doc_2.remove_service(&int_doc_2.id().to_url().join("#linked-domain-1")?);
// Add a VerificationMethod with a new KeyPair, called "keys-2"
let keys_2: KeyPair = KeyPair::new(KeyType::Ed25519)?;
let method_2: IotaVerificationMethod = IotaVerificationMethod::new(int_doc_2.id().clone(), keys_2.type_(), keys_2.public(), "keys-2")?;
assert!(int_doc_2.insert_method(method_2, MethodScope::VerificationMethod).is_ok());
// Note: the `previous_message_id` points to the `message_id` of the last integration chain
// update.
int_doc_2.metadata.previous_message_id = *int_receipt_1.message_id();
int_doc_2.metadata.updated = Some(Timestamp::now_utc());
int_doc_2.sign_self(keypair.private(), int_doc_2.default_signing_method()?.id().clone())?;
int_doc_2
};
let _int_receipt_2: Receipt = client.publish_document(&int_doc_2).await?;
// ===========================================================================
// DID History 2
// ===========================================================================
// Retrieve the updated message history of the DID.
let history_2: DocumentHistory = client.resolve_history(document.id()).await?;
// The history now shows three documents in the integration chain.
println!("History (2) = {:#?}", history_2);
Ok(())
}
Note that this example used the Client
to resolve the history of the DID Document, but one could also use the Resolver
for this task.