Update DID Documents
You can extend DID Documents 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.
Properties
You can specify the following properties for a verification method:
- id: A DID URL for the verification method. You can specify it by setting the fragment.
- type: Specifies the type of the verification method. The framework supports
Ed25519
andX25519
key types. This property is automatically filled by the framework when specifying the verification material. - publicKeyMultibase: A multi-base 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. You can use it to specify the purpose of the verification method.
Relationships
The Identity Framework supports the following relationships:
- Authentication: Used to specify authentication methods for the DID subject.
- Assertion: Used to verify verifiable credentials.
- Key Agreement: Used for establishing secure communication channels.
- Capability Invocation: Can be used to authorize updates to the DID Document.
- Capability Delegation: A mechanism to delegate cryptographic capability to another party.
Verification methods can be either embedded or referenced. Referencing verification
methods allows them to be used by more than one verification relationship.
When you create 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.
You can also add verification relationships afterward using references.
Updates to the DID Document are done through the invocation of Identity::execute_update
API by an Identity
's controller.
The public key or address of the controller does not need to be a verification method in the DID Document,
since the controller is authenticated through possession of a ControllerCap
token.
Services
Services allow you to add 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.
Properties
You can specify the following properties for a service:
- id: A DID URL for referencing the service in the DID document. You can specify it 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.
Create Identity
Before you can update anything, you will need to create an Identity.
- Rust
- Typescript (Node.js)
// create new client to interact with chain and get funded account with keys
let storage = get_memstorage()?;
let identity_client = get_funded_client(&storage).await?;
// create new DID document and publish it
let (document, vm_fragment_1) = create_did_document(&identity_client, &storage).await?;
let did: IotaDID = document.id().clone();
const iotaClient = new IotaClient({ url: NETWORK_URL });
const network = await iotaClient.getChainIdentifier();
const storage = getMemstorage();
const identityClient = await getFundedClient(storage);
const [unpublished, vmFragment1] = await createDocumentForNetwork(storage, network);
// create new identity for this account and publish document for it
const { output: identity } = await identityClient
.createIdentity(unpublished)
.finish()
.buildAndExecute(identityClient);
const did = identity.didDocument().id();
This creates and publishes an Identity object containing a DID Document with one verification method.
{
"doc": {
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"verificationMethod": [
{
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#RqsnXWDY536aFs-8ZzuxcUFYmjG4whZzt3glj7YTi_g",
"controller": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"type": "JsonWebKey2020",
"publicKeyJwk": {
"kty": "OKP",
"alg": "EdDSA",
"kid": "RqsnXWDY536aFs-8ZzuxcUFYmjG4whZzt3glj7YTi_g",
"crv": "Ed25519",
"x": "OJ1vnsXh8Hm69iPmTMlxKpndfKmcGRZXhWKt-dFB488"
}
}
]
},
"meta": {
"created": "2025-03-11T10:01:28Z",
"updated": "2025-03-11T10:01:28Z"
}
}
Add a Verification Method
- Rust
- Typescript (Node.js)
// Insert a new Ed25519 verification method in the DID document.
let vm_fragment_2: String = document
.generate_method(
&storage,
JwkMemStore::ED25519_KEY_TYPE,
JwsAlgorithm::EdDSA,
None,
MethodScope::VerificationMethod,
)
.await?;
// Insert a new Ed25519 verification method in the DID document.
const vmFragment2 = await resolved.generateMethod(
storage,
JwkMemStore.ed25519KeyType(),
JwsAlgorithm.EdDSA,
null,
MethodScope.VerificationMethod(),
);
This creates a new verification method that includes a newly generated Ed25519 public key.
{
"doc": {
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"verificationMethod": [
{
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#RqsnXWDY536aFs-8ZzuxcUFYmjG4whZzt3glj7YTi_g",
"controller": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"type": "JsonWebKey2020",
"publicKeyJwk": {
"kty": "OKP",
"alg": "EdDSA",
"kid": "RqsnXWDY536aFs-8ZzuxcUFYmjG4whZzt3glj7YTi_g",
"crv": "Ed25519",
"x": "OJ1vnsXh8Hm69iPmTMlxKpndfKmcGRZXhWKt-dFB488"
}
},
{
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw",
"controller": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"type": "JsonWebKey2020",
"publicKeyJwk": {
"kty": "OKP",
"alg": "EdDSA",
"kid": "N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw",
"crv": "Ed25519",
"x": "EO6qiaEQgV33XzMGU86eeSBp6Y52i10Tg-VWWsj986Q"
}
}
]
},
"meta": {
"created": "2025-03-11T10:01:28Z",
"updated": "2025-03-11T10:01:28Z"
}
}
Add Verification Relationships
You can attach verification relationships to a verification method by referencing its fragment.
- Rust
- Typescript (Node.js)
// Attach a new method relationship to the inserted method.
document.attach_method_relationship(
&document.id().to_url().join(format!("#{vm_fragment_2}"))?,
MethodRelationship::Authentication,
)?;
// Attach a new method relationship to the inserted method.
resolved.attachMethodRelationship(did.join(`#${vmFragment2}`), MethodRelationship.Authentication);
This will add Authentication
relationship to the verification method with the fragment key-1
.
Note that Authentication
references the already included key-2
verification method:
{
"doc": {
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"verificationMethod": [
{
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#RqsnXWDY536aFs-8ZzuxcUFYmjG4whZzt3glj7YTi_g",
"controller": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"type": "JsonWebKey2020",
"publicKeyJwk": {
"kty": "OKP",
"alg": "EdDSA",
"kid": "RqsnXWDY536aFs-8ZzuxcUFYmjG4whZzt3glj7YTi_g",
"crv": "Ed25519",
"x": "OJ1vnsXh8Hm69iPmTMlxKpndfKmcGRZXhWKt-dFB488"
}
},
{
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw",
"controller": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"type": "JsonWebKey2020",
"publicKeyJwk": {
"kty": "OKP",
"alg": "EdDSA",
"kid": "N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw",
"crv": "Ed25519",
"x": "EO6qiaEQgV33XzMGU86eeSBp6Y52i10Tg-VWWsj986Q"
}
}
],
"authentication": [
"did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw"
]
},
"meta": {
"created": "2025-03-11T10:01:28Z",
"updated": "2025-03-11T10:01:28Z"
}
}
Add a Service
You can also add custom properties can to a service by setting properties
:
- Rust
- Typescript (Node.js)
// 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());
// Add a new Service.
const service: Service = new Service({
id: did.join("#linked-domain"),
type: "LinkedDomains",
serviceEndpoint: "https://iota.org/",
});
resolved.insertService(service);
The updated Document with the newly created service looks as follows.
{
"doc": {
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"verificationMethod": [
{
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#RqsnXWDY536aFs-8ZzuxcUFYmjG4whZzt3glj7YTi_g",
"controller": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"type": "JsonWebKey2020",
"publicKeyJwk": {
"kty": "OKP",
"alg": "EdDSA",
"kid": "RqsnXWDY536aFs-8ZzuxcUFYmjG4whZzt3glj7YTi_g",
"crv": "Ed25519",
"x": "OJ1vnsXh8Hm69iPmTMlxKpndfKmcGRZXhWKt-dFB488"
}
},
{
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw",
"controller": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"type": "JsonWebKey2020",
"publicKeyJwk": {
"kty": "OKP",
"alg": "EdDSA",
"kid": "N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw",
"crv": "Ed25519",
"x": "EO6qiaEQgV33XzMGU86eeSBp6Y52i10Tg-VWWsj986Q"
}
}
],
"authentication": [
"did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw"
],
"service": [
{
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#linked-domain",
"type": "LinkedDomains",
"serviceEndpoint": "https://iota.org/"
}
]
},
"meta": {
"created": "2025-03-11T10:01:28Z",
"updated": "2025-03-11T10:01:28Z"
}
}
Remove a Verification Method
You can also remove verification methods at any time using the following snippet:
- Rust
- Typescript (Node.js)
// Remove a verification method.
let original_method: DIDUrl = document.resolve_method(&vm_fragment_1, None).unwrap().id().clone();
document.purge_method(&storage, &original_method).await.unwrap();
// Remove a verification method.
let originalMethod = resolved.resolveMethod(vmFragment1) as VerificationMethod;
await resolved.purgeMethod(storage, originalMethod?.id());
This removes the original verification method.
{
"doc": {
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"verificationMethod": [
{
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw",
"controller": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a",
"type": "JsonWebKey2020",
"publicKeyJwk": {
"kty": "OKP",
"alg": "EdDSA",
"kid": "N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw",
"crv": "Ed25519",
"x": "EO6qiaEQgV33XzMGU86eeSBp6Y52i10Tg-VWWsj986Q"
}
}
],
"authentication": [
"did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#N4dDJyf6fi83gP42llhd8qSV5ajHCIH4cfWxpPEFeYw"
],
"service": [
{
"id": "did:iota:aa8c860b:0x38469f7a226cc62446452841c19a10971b04bc1d718cd808d61363caceb6988a#linked-domain",
"type": "LinkedDomains",
"serviceEndpoint": "https://iota.org/"
}
]
},
"meta": {
"created": "2025-03-11T10:01:28Z",
"updated": "2025-03-11T10:01:28Z"
}
}
Full Example Code
- Rust
- Typescript (Node.js)
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
use examples::create_did_document;
use examples::get_funded_client;
use examples::get_memstorage;
use examples::TEST_GAS_BUDGET;
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::IotaDID;
use identity_iota::iota::IotaDocument;
use identity_iota::storage::JwkDocumentExt;
use identity_iota::storage::JwkMemStore;
use identity_iota::verification::jws::JwsAlgorithm;
use identity_iota::verification::MethodRelationship;
use identity_iota::verification::MethodScope;
/// Demonstrates how to update a DID document in an existing identity.
#[tokio::main]
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_funded_client(&storage).await?;
// create new DID document and publish it
let (document, vm_fragment_1) = create_did_document(&identity_client, &storage).await?;
let did: IotaDID = document.id().clone();
// Resolve the latest state of the document.
let mut document: IotaDocument = identity_client.resolve_did(&did).await?;
// Insert a new Ed25519 verification method in the DID document.
let vm_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!("#{vm_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(&vm_fragment_1, None).unwrap().id().clone();
document.purge_method(&storage, &original_method).await.unwrap();
let updated = identity_client
.publish_did_document_update(document.clone(), TEST_GAS_BUDGET)
.await?;
println!("Updated DID document result: {updated:#}");
let resolved: IotaDocument = identity_client.resolve_did(&did).await?;
println!("Updated DID document resolved from chain: {resolved:#}");
Ok(())
}
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
import {
JwkMemStore,
JwsAlgorithm,
MethodRelationship,
MethodScope,
Service,
VerificationMethod,
} from "@iota/identity-wasm/node";
import { IotaClient } from "@iota/iota-sdk/client";
import { createDocumentForNetwork, getFundedClient, getMemstorage, NETWORK_URL, TEST_GAS_BUDGET } from "../util";
/** Demonstrates how to update a DID document in an existing identity. */
export async function updateIdentity() {
// create new clients and create new account
const iotaClient = new IotaClient({ url: NETWORK_URL });
const network = await iotaClient.getChainIdentifier();
const storage = getMemstorage();
const identityClient = await getFundedClient(storage);
const [unpublished, vmFragment1] = await createDocumentForNetwork(storage, network);
// create new identity for this account and publish document for it
const { output: identity } = await identityClient
.createIdentity(unpublished)
.finish()
.buildAndExecute(identityClient);
const did = identity.didDocument().id();
// Resolve the latest state of the document.
// Technically this is equivalent to the document above.
const resolved = await identityClient.resolveDid(did);
// Insert a new Ed25519 verification method in the DID document.
const vmFragment2 = await resolved.generateMethod(
storage,
JwkMemStore.ed25519KeyType(),
JwsAlgorithm.EdDSA,
null,
MethodScope.VerificationMethod(),
);
// Attach a new method relationship to the inserted method.
resolved.attachMethodRelationship(did.join(`#${vmFragment2}`), MethodRelationship.Authentication);
// Add a new Service.
const service: Service = new Service({
id: did.join("#linked-domain"),
type: "LinkedDomains",
serviceEndpoint: "https://iota.org/",
});
resolved.insertService(service);
// Remove a verification method.
let originalMethod = resolved.resolveMethod(vmFragment1) as VerificationMethod;
await resolved.purgeMethod(storage, originalMethod?.id());
let controllerToken = await identity.getControllerToken(identityClient);
let maybePendingProposal = await identity
.updateDidDocument(resolved.clone(), controllerToken!)
.withGasBudget(TEST_GAS_BUDGET)
.buildAndExecute(identityClient)
.then(result => result.output);
console.assert(maybePendingProposal == null, "the proposal should have been executed right away!");
// and resolve again to make sure we're looking at the onchain information
const resolvedAgain = await identityClient.resolveDid(did);
console.log(`Updated DID document result: ${JSON.stringify(resolvedAgain, null, 2)}`);
}