Domain Linkage
Overview
Domain Linkage can provide proof for a connection between a DID and a domain being controlled by the same entity. This linkage can transfer trust from a domain to a DID and vice verca. For instance, if an entity trusts a domain, it can also trust the linked DID and all documents signed by verification methods included in the DID Document. A use case coud be a verifier that trusts www.example.com
and receives a Verifiable Presentation issued by did:foo:abc
. By did:foo:abc
being linked to www.example.com
, the verifier can trust that the Verifiable Presentation is issued by the same entity controlling www.example.com
.
The DIF has approved a Well Known DID Configuration draft to standardize this connection by introducing the DID Configuration Resource and the Linked Domain Service Endpoint.
DID Configuration Resource
Suppose that a DID did:foo:example
with the following DID Document only containing a verificationMethod
key-1
{
"id": "did:foo:abc",
"verificationMethod": [
{
"id": "did:foo:abc#key-1",
"controller": "did:foo:abc",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "zDShpHKXkcHKHcF8CnGAA1UqyyuEPRNz1XFEuggbWJQSq"
}
]
},
and the domain https://www.example.com
represents the same entity and need be linked to increase trust in the DID.
To establish this linkage, A DID Configuration Resource must be created and made available on the DID Configuration URL. In this case it's https://example.com/.well-known/did-configuration.json
.
The DID Configuration Resource is a JSON-LD object containing verifiable credentials called Domain Linkage Credentials
. Each credential represents a linkage to a single DID.
Note that one DID Configuration Resource
can include multiple Domain Linkage Credentials
effectivaly linking the same domain to multiple DIDs.
In this example, the domain https://www.example.com
needs to be linked to the DID did:foo:abc
. This means that the DID Configuration Resource
will have one Domain Linkage Credential
. This credential must have the following properties:
- Its
type
includesDomainLinkageCredential
. - It includes the DID Configuration context.
- The
credentialSubject
must be the DIDdid:foo:abc
and references the domainhttps://www.example.com
. - The issuer is the DID itself
did:foo:abc
. - It is signed by a key material included in the DID Document, in this case
did:foo:abc#key-1
.
{
"@context": "https://identity.foundation/.well-known/did-configuration/v1",
"linked_dids": [
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://identity.foundation/.well-known/did-configuration/v1"
],
"type": [
"VerifiableCredential",
"DomainLinkageCredential"
],
"credentialSubject": {
"id": "did:foo:abc",
"origin": "https://www.example.com/"
},
"issuer": "did:foo:abc",
"issuanceDate": "2023-02-09T22:14:15Z",
"expirationDate": "2024-02-09T22:14:15Z",
"proof": {
"type": "JcsEd25519Signature2020",
"verificationMethod": "did:foo:abc#key-1",
"signatureValue": "4SvYqo3YoArfW7r7qKfN7RUJdZnBteb166KE4UkX8MNdbp5UW6YbykneAzvjyRmf5EVQ9bnP9cS5sbEPUn2uaAcB"
}
}
]
}
Now this DID Configuration Resource
must be made available on https://example.com/.well-known/did-configuration.json
which establishes the linkage.
Linked Domain Service Endpoint
By having a domain, one can discover what DIDs are linked to it by fetching the DID Configuration Resource
and investigating the Domain Linkage Credentials
. To enable discovery from the other direction, when having a DID and wanting to discover which domains are linked to it, a Linked Domain Service Endpoint can be added to the DID Document. The DID Document from this example will be extended as follows to enable discovery of https://www.example.com
:
{
"id": "did:foo:abc",
"verificationMethod": [
{
"id": "did:foo:abc#key-1",
"controller": "did:foo:abc",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "zDShpHKXkcHKHcF8CnGAA1UqyyuEPRNz1XFEuggbWJQSq"
}
],
"service": [
{
"id": "did:foo:abc#domain-linkage",
"type": "LinkedDomains",
"serviceEndpoint": "https://www.example.com/"
}
]
}
Note that a DID Document can have multiple Linked Domain Services
and each service can link to multiple domains.
Verifying DID and Domain Linkage
As mentioned above, the discovery of the Domain Linkage can happen from either direction. But verifying the linkage in both cases involves only verifying the DID Configuration Resource. The process is as follows:
- Fetch
DID Configuration Resource
fromhttps://www.example.com/.well-known/did-configuration.json
. - Resolve the DID Document of
did:foo:abc
. - Verify the
DID Configuration Resource
and itsDomain Linkage Credential
that referencesdid:foo:abc
.
Here you can learn more about DID Configuration Resource Verification.
Example
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
import { Client, MnemonicSecretManager } from "@iota/client-wasm/node";
import { Bip39 } from "@iota/crypto.js";
import {
Credential,
CredentialValidationOptions,
DIDUrl,
DomainLinkageConfiguration,
DomainLinkageValidator,
Duration,
IotaDID,
IotaDocument,
IotaIdentityClient,
LinkedDomainService,
ProofOptions,
Timestamp,
} from "@iota/identity-wasm/node";
import { IAliasOutput, IRent, TransactionHelper } from "@iota/iota.js";
import { API_ENDPOINT, createDid } from "../util";
/**
* Demonstrates how to link a domain and a DID and verify the linkage.
*/
export async function domainLinkage() {
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).
let { document, keypair } = await createDid(client, secretManager);
const did: IotaDID = document.id();
// =====================================================
// Create Linked Domain service
// ====================================================
let domainFoo = "https://foo.example.com";
let domainBar = "https://bar.example.com";
// Create a Linked Domain Service to enable the discovery of the linked domains through the DID Document.
// This is optional since it is not a hard requirement by the spec.
let serviceUrl: DIDUrl = did.clone().join("#domain_linkage");
let linkedDomainService: LinkedDomainService = new LinkedDomainService({
id: serviceUrl,
domains: [domainFoo, domainBar],
});
document.insertService(linkedDomainService.toService());
let updatedDidDocument = await publishDocument(didClient, secretManager, document);
console.log("Updated DID document:", JSON.stringify(updatedDidDocument, null, 2));
// =====================================================
// Create DID Configuration resource
// =====================================================
// Now the DID Document contains a service that includes the domains.
// To allow a bidirectional linkage, the domains must link to the DID. This is
// done by creating a `DID Configuration Resource` that includes a `Domain Linkage Credential`
// and can be made available on the domain.
// Create the Domain Linkage Credential.
let domainLinkageCredential: Credential = Credential.createDomainLinkageCredential({
issuer: document.id(),
origin: domainFoo,
expirationDate: Timestamp.nowUTC().checkedAdd(Duration.weeks(10))!,
});
// Sign the credential.
domainLinkageCredential = document.signCredential(
domainLinkageCredential,
keypair.private(),
"#key-1",
ProofOptions.default(),
);
// Create the DID Configuration Resource which wraps the Domain Linkage credential.
let configurationResource: DomainLinkageConfiguration = new DomainLinkageConfiguration([domainLinkageCredential]);
// The DID Configuration resource can be made available on `https://foo.example.com/.well-known/did-configuration.json`.
let configurationResourceJson = configurationResource.toJSON();
console.log("Configuration Resource:", JSON.stringify(configurationResource.toJSON(), null, 2));
// Now the DID Document links to the Domains through the service, and the Foo domain links to the DID
// through the DID Configuration resource. A bidirectional linkage is established.
// Note however that bidirectionality is not a hard requirement. It is valid to have a Domain Linkage
// credential point to a DID, without the DID having a service that points back.
// =====================================================
// Verification can start from two different places.
// The first case answers the question "What DID is this domain linked to?"
// while the second answers "What domain is this DID linked to?".
// ====================================================
// =====================================================
// → Case 1: starting from domain
// =====================================================
// Fetch the DID Configuration resource (For example using the Fetch API).
// Note that according to the specs, the DID Configuration resource must exist
// at the origin's root, well-known Resource directory.
const _configurationUrl = `${domainFoo}/.well-known/did-configuration.json")`;
// But since the DID Configuration
// resource isn't available online in this example, we will simply use the JSON.
let fetchedConfigurationResource = DomainLinkageConfiguration.fromJSON(configurationResource);
// Retrieve the issuers of the Domain Linkage Credentials which correspond to the possibly linked DIDs.
// Note that in this example only the first entry in the credential is validated.
let issuers: Array<string> = fetchedConfigurationResource.issuers();
const issuerDocument: IotaDocument = await didClient.resolveDid(IotaDID.parse(issuers[0]));
// Validate the linkage between the Domain Linkage Credential in the configuration and the provided issuer DID.
// Validation succeeds when no error is thrown.
DomainLinkageValidator.validateLinkage(
issuerDocument,
fetchedConfigurationResource,
domainFoo,
CredentialValidationOptions.default(),
);
// =====================================================
// → Case 2: starting from a DID
// =====================================================
const didDocument: IotaDocument = await didClient.resolveDid(did);
// Get the Linked Domain Services from the DID Document.
let linkedDomainServices: LinkedDomainService[] = didDocument
.service()
.filter(service => LinkedDomainService.isValid(service))
.map(service => LinkedDomainService.fromService(service));
// Get the domains included in the Linked Domain Service.
// Note that in this example only the first entry in the service is validated.
let domains: string[] = linkedDomainServices[0].domains();
// Fetch the DID Configuration resource (For example using the Fetch API).
// Note that according to the specs, the DID Configuration resource must exist
// at the origin's root, Well-Known Resource directory.
const __configurationUrl = `${domains[0]}/.well-known/did-configuration.json")`;
// But since the DID Configuration
// resource isn't available online in this example, we will simply use the JSON.
fetchedConfigurationResource = DomainLinkageConfiguration.fromJSON(configurationResource);
// Validate the linkage between the Domain Linkage Credential in the configuration and the provided issuer DID.
// Validation succeeds when no error is thrown.
DomainLinkageValidator.validateLinkage(
didDocument,
fetchedConfigurationResource,
domains[0],
CredentialValidationOptions.default(),
);
}
async function publishDocument(
client: IotaIdentityClient,
secretManager: MnemonicSecretManager,
document: IotaDocument,
): Promise<IotaDocument> {
// Resolve the latest output and update it with the given document.
const aliasOutput: IAliasOutput = await client.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 client.getRentStructure();
aliasOutput.amount = TransactionHelper.getStorageDeposit(aliasOutput, rentStructure).toString();
// Publish the output.
const updated: IotaDocument = await client.publishDidOutput(secretManager, aliasOutput);
return updated;
}