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
andX25519
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:
- Authentication: used to specify authentication methods for the DID subject.
- Assertion: can be used for verifiable credential verification.
- 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 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.
Any update to the DID document must be signed using a verification method with capability invocation
relationship to be valid. Removing all capability invocation verification methods
disallows any further updates to the document.
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 referecing 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 adding verification methods and services to a DID Document.
- Rust
- Node.js
loading...
loading...
Creating Identity
The Example above starts by creating an identity using the account.
- Rust
- Node.js
let mut account: Account = Account::builder()
.storage(stronghold)
.create_identity(IdentitySetup::default())
.await?;
let builder = new AccountBuilder({
storage,
});
let account = await builder.createIdentity();
This will create a DID document and publish it to the tangle.
{
"doc": {
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"capabilityInvocation": [
{
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#sign-0",
"controller": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z5k7vzMVuXXj8MJDcfzP2owvc8xKBA6BBsAkFf1GSNu2X"
}
]
},
"meta": {
"created": "2022-04-13T09:27:48Z",
"updated": "2022-04-13T09:27:48Z"
}
}
The created document only contains one verification method with capabilityInvocation relationship. This method is used to sign the DID Document for publication to the Tangle. The signature proves that the publisher of the document is in control over the capability invocation keys and is allowed to create, update or delete the DID Document.
Any future updates to the DID Document in this example will be signed using this verification method. The Account will automatically sign each update with this method so individual updates don't have to be explicitly signed.
Furthermore, it's possible to rotate a capability Invocation key. In this case, the Account will sign next update with a key which was valid in the previous state of the DID Document. Afterwards it will
use the first (oldest) of the remaining capability invocation keys as a default signing method.
Other capability invocation keys can still be explicitly specified to sign an update. These can be set in PublishOptions
.
Note that the Account does not allow removing all capability invocation keys.
Adding Verification Methods
Another verification method can be added to the DID document using the Account:
- Rust
- Node.js
account
.update_identity()
.create_method()
.content(methodcontent::generateed25519)
.fragment("my-next-key")
.apply()
.await?;
await account.createMethod({
content: MethodContent.GenerateEd25519(),
fragment: 'my-next-key',
});
The code above creates a new verification method that includes a newly generated Ed25519 public key,
signs the updated document using the private key of the default capabilityInvocation
verification method
and publishes the document to the tangle.
Since the MethodScope
is not specified, the verification method will be created in the verificationMethod
map. The updated DID Document will look as follows:
{
"doc": {
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"verificationMethod": [
{
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#my-next-key",
"controller": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z2Zthec5siTfxCjPwZUHGDGybKNy9oc3ZYeftvEE2nEL3"
}
],
"capabilityInvocation": [
{
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#sign-0",
"controller": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z5k7vzMVuXXj8MJDcfzP2owvc8xKBA6BBsAkFf1GSNu2X"
}
]
},
"meta": {
"created": "2022-04-13T09:27:48Z",
"updated": "2022-04-13T09:28:06Z"
}
}
Adding Verification Relationships
Verification relationship can be attached to a verification method by referencing its fragment.
- Rust
- Node.js
account
.update_identity()
.attach_method_relationship()
.fragment("my-next-key")
.relationships(vec![
MethodRelationship::CapabilityDelegation,
MethodRelationship::CapabilityInvocation,
])
.apply()
.await?;
await account.attachMethodRelationships({
fragment: 'my-next-key',
relationships: [
MethodRelationship.CapabilityDelegation,
MethodRelationship.CapabilityInvocation,
],
});
This will add CapabilityDelegation
and CapabilityInvocation
relationships to the created verification method with the fragment my-next-key
. The capabilityInvocation
property now has both an embedded and a referenced verification method.
{
"doc": {
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"verificationMethod": [
{
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#my-next-key",
"controller": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z2Zthec5siTfxCjPwZUHGDGybKNy9oc3ZYeftvEE2nEL3"
}
],
"capabilityDelegation": [
"did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#my-next-key"
],
"capabilityInvocation": [
{
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#sign-0",
"controller": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z5k7vzMVuXXj8MJDcfzP2owvc8xKBA6BBsAkFf1GSNu2X"
},
"did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#my-next-key"
]
},
"meta": {
"created": "2022-04-13T09:27:48Z",
"updated": "2022-04-13T09:28:23Z"
}
}
Adding a Service
Similar to verification methods, services can be added to a DID Document.
- Rust
- Node.js
account
.update_identity()
.create_service()
.fragment("my-service-1")
.type_("MyCustomService")
.endpoint(Url::parse("https://example.com")?)
.apply()
.await?;
await account.createService({
fragment: 'my-service-1',
type: 'MyCustomService',
endpoint: 'https://example.com',
});
In JavaScript, the endpoint property type is a string, this must be a valid URL, otherwise an error will be thrown.
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:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"verificationMethod": [
{
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#my-next-key",
"controller": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z2Zthec5siTfxCjPwZUHGDGybKNy9oc3ZYeftvEE2nEL3"
}
],
"capabilityDelegation": [
"did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#my-next-key"
],
"capabilityInvocation": [
{
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#sign-0",
"controller": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z5k7vzMVuXXj8MJDcfzP2owvc8xKBA6BBsAkFf1GSNu2X"
},
"did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#my-next-key"
],
"service": [
{
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#my-service-1",
"type": "MyCustomService",
"serviceEndpoint": "https://example.com/"
}
]
},
"meta": {
"created": "2022-04-13T09:27:48Z",
"updated": "2022-04-13T09:28:34Z"
}
}
Removing a Verification Method
Verification methods and/or their relationships can be removed from the DID Document. The following code removes the verification method that we created previously.
- Rust
- Node.js
account
.update_identity()
.delete_method()
.fragment("my-next-key")
.apply()
.await?;
await account.deleteMethod({ fragment: 'my-next-key' });
{
"doc": {
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"capabilityInvocation": [
{
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#sign-0",
"controller": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "z5k7vzMVuXXj8MJDcfzP2owvc8xKBA6BBsAkFf1GSNu2X"
}
],
"service": [
{
"id": "did:iota:6T4PRHWp7bNsKaBWr1gVtUBQfLaxAKqKGAeJWFBZkMyf#my-service-1",
"type": "MyCustomService",
"serviceEndpoint": "https://example.com/"
}
]
},
"meta": {
"created": "2022-04-13T09:27:48Z",
"updated": "2022-04-13T09:29:03Z"
}
}
Notice that the capabilityDelegation
and verificationMethod
properties are also removed from the DID Document since they became empty after the only verification method they contained and referenced was removed.
Furthermore and similar to deleting verification methods, services can be deleted using account.update_identity().delete_service()...
in Rust and account.deleteService(..)
in JavaScript.
In this example, a message is published to the tangle every time the document is updated. These messages can be unnecessary. Instead, one message can be published that contains all the updates to the DID Document. See the lazy example for Rust and lazy example for JS to learn more about lazy publishing.