Skip to main content

Well-Known DID Configuration

This is a working draft

The KILT support of the Well-Known DID Configuration uses unpublished specifications and will change in the future.

The Well-Known DID Configuration is implemented as a security measure when setting up the communication session between the dapp and extension. It ensures that the DID the browser extension is communicating to is linked to the domain that is visited by the browser. This rule is currently enforced by the KILT Wallet reference implementation (Sporran Extension), but might be relaxed in the future. The implementation is based on the Well-Known DID Configuration specified by the Decentralized Identity Foundation.

Once a communication session between a dapp and an extension is opened, the extension will query <domain-name>/.well-known/did-configuration.json. This JSON-file must contain a credential presentation that conforms to the Domain Linkage CType.

Set up the Well-Known DID Configuration

For the Well-Known DID Configuration you need to go through the following steps:

  1. Create a full DID
    • You will need the assertionMethodKey a.k.a. attestationKey for signing the credential
    • The authenticationKey is required for signing the transaction
  2. Create a claim
  3. Attest the claim
  4. Create a presentation
  5. Host the presentation on your website at https://<your domain>/.well-known/did-configuration.json

Create a DID

Your dapp needs a DID to identify itself to the extension. If your dapp does not have a DID yet, follow the create a full DID guide. Make sure to create the DID with an assertionMethodKey so that you are able to issue attestations.

Making the claim

After you get a DID, you can make a claim about that DID. The claim has to be based on the Domain Linkage CType, whose definition you can get from the linked GitHub repository, or fetch from the blockchain using the CType's id:

/* eslint-disable @typescript-eslint/no-unused-vars */
import * as Kilt from '@kiltprotocol/sdk-js'

export async function main(): Promise<Kilt.ICType> {
const {
creator,
createdAt,
cType: domainLinkageCType
} = await Kilt.CType.fetchFromChain(
'kilt:ctype:0xb08800a574c436831a2b9fce00fd16e9df489b2b3695e88a0895d148eca0311e'
)

console.log(JSON.stringify(domainLinkageCType, null, 2))

/** Prints the following definition:
{
"$schema": "ipfs://bafybeiah66wbkhqbqn7idkostj2iqyan2tstc4tpqt65udlhimd7hcxjyq/",
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
},
"origin": {
"type": "string"
}
},
"title": "Domain Linkage Credential",
"type": "object",
"$id": "kilt:ctype:0xb08800a574c436831a2b9fce00fd16e9df489b2b3695e88a0895d148eca0311e"
}
*/
return domainLinkageCType
}

The credential is built from the CType, claim contents, and your dapp's unique DID:

import * as Kilt from '@kiltprotocol/sdk-js'

export function main({
domainLinkageCType,
didUri
}: {
domainLinkageCType: Kilt.ICType
didUri: Kilt.DidUri
}) {
const claimContents: Kilt.IClaimContents = {
id: didUri,
origin: 'https://example.com'
}

const claim = Kilt.Claim.fromCTypeAndClaimContents(
domainLinkageCType,
claimContents,
didUri
)
const domainLinkageCredential = Kilt.Credential.fromClaim(claim)

return { domainLinkageCredential }
}

The credential isn't attested yet and is therefore not valid yet.

Self-attesting the credential

A valid credential requires an attestation. Since the website wants to link itself to the DID just created, it has to self-attest the domain linkage credential, i.e., write the credential attestation on chain using the same DID it is trying to link to.

In order to attest the credential we go through the following steps:

  1. calculating the claim hash
  2. creating the attest transaction
  3. authorizing the transaction with your DID
  4. paying for the transaction with a KILT account and submitting it to the chain
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as Kilt from '@kiltprotocol/sdk-js'

export async function main({
didUri,
dappAccount,
assertionMethodKey,
domainLinkageCredential
}: {
didUri: Kilt.DidUri
dappAccount: Kilt.KiltKeyringPair
assertionMethodKey: Kilt.KiltKeyringPair
domainLinkageCredential: Kilt.ICredential
}) {
const api = Kilt.ConfigService.get('api')
const { cTypeHash, claimHash } = Kilt.Attestation.fromCredentialAndDid(
domainLinkageCredential,
didUri
)
const attestationTx = api.tx.attestation.add(claimHash, cTypeHash, null)

// We authorize the call using the attestation key of the Dapps DID.
const extrinsic = api.tx.did.dispatchAs(dappAccount.address, attestationTx)

// Since DIDs can not hold any balance, we pay for the transaction using our blockchain account
const result = await Kilt.Blockchain.signAndSubmitTx(extrinsic, dappAccount)

if (result.isError) {
console.log('Attestation failed')
} else {
console.log('Attestation successful')
}
return result
}

If you want to learn more about attestations you can refer to our concept guide or the cookbook.

Presenting the credential

To use the newly attested credential, we need to derive a presentation from it to host on the dapp website.

import * as Kilt from '@kiltprotocol/sdk-js'

export async function main({
didUri,
assertionMethodKey,
domainLinkageCredential
}: {
didUri: Kilt.DidUri
assertionMethodKey: Kilt.KiltKeyringPair
domainLinkageCredential: Kilt.ICredential
}) {
// We need the KeyId of the AssertionMethod Key. There is only
// one AssertionMethodKey and its id is stored on the blockchain.
const didResolveResult = await Kilt.Did.resolve(didUri)
if (typeof didResolveResult.document === 'undefined') {
throw new Error('DID must be resolvable (i.e. not deleted)')
}
const assertionMethodKeyId = didResolveResult.document.assertionMethod[0].id

const domainLinkagePresentation = await Kilt.Credential.createPresentation({
credential: domainLinkageCredential,
signCallback: async ({ data }) => ({
signature: assertionMethodKey.sign(data),
keyType: assertionMethodKey.type,
keyUri: `${didUri}${assertionMethodKeyId}`
})
})

return { domainLinkagePresentation }
}

The Well-Known DID Configuration specification requires a verifiable credential. For now we have to manually convert our KILT credential into the required format.

import * as Kilt from '@kiltprotocol/sdk-js'

export async function main(
domainLinkagePresentation: Kilt.ICredentialPresentation
) {
const api = Kilt.ConfigService.get('api')

const credentialSubject = {
...domainLinkagePresentation.claim.contents,
rootHash: domainLinkagePresentation.rootHash
}

const encodedAttestationDetails = await api.query.attestation.attestations(
domainLinkagePresentation.rootHash
)
const issuer = Kilt.Attestation.fromChain(
encodedAttestationDetails,
domainLinkagePresentation.claim.cTypeHash
).owner

const issuanceDate = new Date().toISOString()

const claimerSignature = domainLinkagePresentation.claimerSignature
if (!claimerSignature) {
throw new Error('Claimer signature is required.')
}

const proof = {
type: 'KILTSelfSigned2020',
proofPurpose: 'assertionMethod',
verificationMethod: claimerSignature.keyUri,
signature: claimerSignature.signature,
challenge: claimerSignature.challenge
}

const wellKnownDidconfig = {
'@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'
],
issuer,
issuanceDate,
type: [
'VerifiableCredential',
'DomainLinkageCredential',
'KiltCredential2020'
],
credentialSubject,
proof
}
]
}

return wellKnownDidconfig
}

Host the Presentation

Now that you generated a presentation, you need to host it in your web app, so that the extension can query the presentation. The extension will make an HTTP GET request to the following URI, and your dapp must respond with the presentation.

/.well-known/did-configuration.json

How the file is hosted depends on your project setup and is out of scope for this guide.