Skip to main content
Version: Develop

Update DID Documents

DID Documents can be extended by adding verification methods, services and custom properties. A verification method adds public keys, which can be used to digitally sign things like a DID message or a verifiable credential, while a service can provide metadata around the identity via URIs.

Verification Methods​

As demonstrated by the example below, the IOTA Identity framework offers easy-to-use methods for adding verification methods.

The following properties can be specified for a verification method:

  • id: a DID URL for the verification method. It can be specified by setting the fragment;
  • type: specifies the type of the verification method. The framework supports Ed25519 and X25519 key types. This property is automatically filled by the framework when specifying the verification material.
  • publicKeyMultibase: multibase encoded public key which concludes the verification material. This can be automatically generated by the framework or manually provided by users.

Verification Relationships​

Verification relationships express the relationship between the DID subject and the verification method. It can be used to specify the the purpose of the verification method.

The following relationships are supported by the Identity Framework:

Verification methods can be either embedded or referenced. Referencing verification methods allow them to be used by more than one verification relationship. Upon creating a verification method using the identity framework, specifying the MethodScope option will result in an embedded verification method. Leaving that option unset will create the verification method as a map entry of the verificationMethod property. Verification relationships can be added afterwards using references.

note

Updates to the DID Document are done through a state transition of the Alias Output by its state controller. The public key or address of the state controller does not need to be a verification method in the DID Document, since it is defined in the containing Alias Output.

Services​

Services allow adding other ways of communicating with the DID subject. An endpoint included in the DID Document can offer a way of reaching services for different purposes like authentication, communicating, and discovery.

The following properties can be specified for a service:

  • id: a DID URL for referencing the service in the DID document. It can be specified by setting the fragment.
  • type: a string used to maximize interoperability between services. The framework does not perform any checks on the content of this string.
  • serviceEndpoint: a URL that points to the service endpoint.

Example​

The following example demonstrates managing verification methods and services in a DID Document.

// 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 {
IotaDocument,
IotaIdentityClient,
IotaService,
IotaVerificationMethod,
KeyPair,
KeyType,
MethodRelationship,
MethodScope,
Timestamp,
} from "../../../node";
import { API_ENDPOINT, createDid } from "../util";

/** Demonstrates how to update a DID document in an existing Alias Output. */
export async function updateIdentity() {
const client = new Client({
primaryNode: API_ENDPOINT,
localPow: true,
});
const didClient = new IotaIdentityClient(client);

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

// Creates a new wallet and identity (see "0_create_did" example).
const { did } = await createDid(client, secretManager);

// Resolve the latest state of the document.
// Technically this is equivalent to the document above.
const document: IotaDocument = await didClient.resolveDid(did);

// Insert a new Ed25519 verification method in the DID document.
let keypair = new KeyPair(KeyType.Ed25519);
let method = new IotaVerificationMethod(document.id(), keypair.type(), keypair.public(), "#key-2");
document.insertMethod(method, MethodScope.VerificationMethod());

// Attach a new method relationship to the inserted method.
document.attachMethodRelationship(did.join("#key-2"), MethodRelationship.Authentication);

// Add a new Service.
const service: IotaService = new IotaService({
id: did.join("#linked-domain"),
type: "LinkedDomains",
serviceEndpoint: "https://iota.org/",
});
document.insertService(service);
document.setMetadataUpdated(Timestamp.nowUTC());

// Remove a verification method.
let originalMethod = document.resolveMethod("key-1") as IotaVerificationMethod;
document.removeMethod(originalMethod?.id());

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

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

// Publish the output.
const updated: IotaDocument = await didClient.publishDidOutput(secretManager, aliasOutput);
console.log("Updated DID document:", JSON.stringify(updated, null, 2));
}

Creating Identity​

The example above starts by creating an identity.

// Create a new client to interact with the IOTA ledger.
let client: Client = Client::builder().with_primary_node(NETWORK_ENDPOINT, None)?.finish()?;

// Create a new secret manager backed by a Stronghold.
let mut secret_manager: SecretManager = SecretManager::Stronghold(
StrongholdSecretManager::builder()
.password("secure_password")
.build(random_stronghold_path())?,
);

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

This creates and publishes an Alias Output containing a DID Document with one verification method.

{
"doc": {
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"verificationMethod": [
{
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-1",
"controller": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z94fP8Vo6qJtejpycjUcYtiSTbLGuCNYUTrjQX9hT2gSv"
}
]
},
"meta": {
"created": "2022-09-12T21:58:06Z",
"updated": "2022-09-12T21:58:06Z"
}
}

Adding Verification Method​

  // Insert a new Ed25519 verification method in the DID document.
let keypair: KeyPair = KeyPair::new(KeyType::Ed25519)?;
let method: IotaVerificationMethod =
IotaVerificationMethod::new(document.id().clone(), keypair.type_(), keypair.public(), "#key-2")?;
document.insert_method(method, MethodScope::VerificationMethod)?;

This creates a new verification method that includes a newly generated Ed25519 public key.

{
"doc": {
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"verificationMethod": [
{
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-1",
"controller": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z94fP8Vo6qJtejpycjUcYtiSTbLGuCNYUTrjQX9hT2gSv"
},
{
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-2",
"controller": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "zHiCj7kbZdWznNvBhqxXwgoEChYseKrArLFdi5kPKAVRq"
}
]
},
"meta": {
"created": "2022-09-12T21:58:06Z",
"updated": "2022-09-12T21:58:06Z"
}
}

Notice that these changes to the document are not published yet. This will be done in a later stage.

Adding Verification Relationships​

Verification relationship can be attached to a verification method by referencing its fragment.

// Attach a new method relationship to the existing method.
document.attach_method_relationship(
&document.id().to_url().join("#key-1")?,
MethodRelationship::Authentication,
)?;

This will add Authentication relationship to the verification method with the fragment key-1. Note that Authentication references the already included verification method:

{
"doc": {
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"verificationMethod": [
{
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-1",
"controller": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z94fP8Vo6qJtejpycjUcYtiSTbLGuCNYUTrjQX9hT2gSv"
},
{
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-2",
"controller": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "zHiCj7kbZdWznNvBhqxXwgoEChYseKrArLFdi5kPKAVRq"
}
],
"authentication": ["did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-2"]
},
"meta": {
"created": "2022-09-12T21:58:06Z",
"updated": "2022-09-12T21:58:06Z"
}
}

Adding a Service​

// Add a new Service.
let service: StardustService = Service::from_json_value(json!({
"id": document.id().to_url().join("#linked-domain")?,
"type": "LinkedDomains",
"serviceEndpoint": "https://iota.org/"
}))?;
assert!(document.insert_service(service));
document.metadata.updated = Some(Timestamp::now_utc());

Additionally, custom properties can be added to a service by setting properties in both Rust and JavaScript.

The updated Document with the newly created service looks as follows.

{
"doc": {
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"verificationMethod": [
{
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-1",
"controller": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z94fP8Vo6qJtejpycjUcYtiSTbLGuCNYUTrjQX9hT2gSv"
},
{
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-2",
"controller": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "zHiCj7kbZdWznNvBhqxXwgoEChYseKrArLFdi5kPKAVRq"
}
],
"authentication": ["did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-2"],
"service": [
{
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#linked-domain",
"type": "LinkedDomains",
"serviceEndpoint": "https://iota.org/"
}
]
},
"meta": {
"created": "2022-09-12T21:58:06Z",
"updated": "2022-09-12T21:58:19Z"
}
}

Removing Verification Method​

// Remove a verification method.
let original_method: DIDUrl<IotaDID> = document.resolve_method("key-1", None).unwrap().id().clone();
document.remove_method(&original_method).unwrap();

This removes the original verification method with the fragment key-1.

{
"doc": {
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"verificationMethod": [
{
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-2",
"controller": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "zHiCj7kbZdWznNvBhqxXwgoEChYseKrArLFdi5kPKAVRq"
}
],
"authentication": ["did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#key-2"],
"service": [
{
"id": "did:iota:rms:0x6fdcc441ab461aaee2ec1837ea5068fe2bc643a9ac0729a055ef5df42a762483#linked-domain",
"type": "LinkedDomains",
"serviceEndpoint": "https://iota.org/"
}
]
},
"meta": {
"created": "2022-09-12T21:58:06Z",
"updated": "2022-09-12T21:58:19Z"
}
}

Publishing​

Publish the updated DID Document inside the Alias Output taking into account the increase in the storage deposit needed.

// 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: StardustDocument = client.publish_did_output(&secret_manager, alias_output).await?;