Links

ADR 003: DID Resolver

Status

Category
Status
Authors
Alex Tweeddale, Renata Toktar, Ankur Banerjee
ADR Stage
PROPOSED
Implementation Status
In Progress
Start Date
2022-02-22
Last Updated
2022-08-04

Summary

The did:cheqd method ADR defines how DIDs are created and read from ledger. According to the W3C DID Core specification, DID methods are expected to provide standards-compliant methods of DID and DID Document ("DIDDoc") production.
The cheqd DID Resolver is designed to implement the W3C DID Resolution specification for did:cheqd method.

Context

The DID Resolution specification prescribes a defined algorithm with standardised behaviour for expected and unexpected inputs that a conforming DID method must be able to produce.
All conforming DID resolvers implement resolve and resolveRepresentation abstract functions, as defined in the DID Resolution specification.

Resolve function

The resolve function is intended to fetch the abstract form of the DID Document, as stored on the ledger. This abstract/raw form may not necessarily be in JSON/JSON-LD format as the underlying data persistence layer where the DIDDoc is stored for any particular method might use different serialisation/storage formats.
resolve(did, resolutionOptions)
« didResolutionMetadata, didDocument, didDocumentMetadata »
Since cheqd uses the Cosmos SDK blockchain framework, the underlying data storage and retrieval ("resolve") mechanisms used rely on those offered by the Cosmos SDK framework. Cosmos SDK uses Protobuf (Protocol Buffers) encoding for its wire protocol.
Cosmos SDK framework typically provides gRPC/gRPC-Web, JSON-RPC, and REST API endpoints for on-ledger modules and functionality.
For example, did:cheqd:testnet:DAzMQo4MDMxCjgwM can be fetched using the native Cosmos SDK REST API endpoint (or equivalent endpoints). This provides responses that would meet the abstract definition of a resolve function as defined in the DID Core specification.
In case of the cheqd network testnet, an instance of this resolve endpoint through the Cosmos SDK REST API would be api.cheqd.network/cheqd/v1/did/did:cheqd:testnet:97e351e6-2d9d-4314-82ec-e0d12bc5de43 which returns the following response:
{
"did": {
"context": [],
"id": "did:cheqd:testnet:97e351e6-2d9d-4314-82ec-e0d12bc5de43",
"controller": [],
"verification_method": [
{
"id": "did:cheqd:testnet:97e351e6-2d9d-4314-82ec-e0d12bc5de43#key1",
"type": "Ed25519VerificationKey2020",
"controller": "did:cheqd:testnet:97e351e6-2d9d-4314-82ec-e0d12bc5de43",
"public_key_jwk": {
"crv": "Ed25519",
"kty": "OKP",
"x": "q8-CHj4_nIYo8tK5RdjYbXlsTUnwW_i4gIEclps2i2o"
}
}
],
"authentication": ["did:cheqd:testnet:97e351e6-2d9d-4314-82ec-e0d12bc5de43#key1"],
"assertion_method": [],
"capability_invocation": [],
"capability_delegation": [],
"key_agreement": [],
"service": [],
"also_known_as": []
},
"metadata": {
"created": "2022-07-19T08:29:07Z",
"updated": "",
"deactivated": false,
"version_id": "cfe2f51f-8ec5-4fd8-8ab9-61859de879f4",
}
}
As you can see in the response body above, this is the raw Protobuf fetched from the cheqd testnet ledger, marshalled into a JSON form. Crucially, this form has certain deviations from the JSON/JSON-LD production expected in DID Core specification:
  1. 1.
    JSON key names that correlate to DID Core properties are listed in snake_case, rather than camelCase as required. This is because Protobuf standard linting rules require these properties to be defined in snake_case.
  2. 2.
    DID Core properties with empty values are still shown in this JSON, whereas the requirement is to drop them from standards-compliant DIDDoc representations.

Resolve Representation function

The resolveRepresentation abstract function, as defined in DID Core specification, is intended to address concerns similar to the ones highlighted above to product a standards-compliant JSON/JSON-LD representation of a DIDDoc.
resolveRepresentation(did, resolutionOptions)
« didResolutionMetadata, didDocumentStream, didDocumentMetadata »
For example, a resolveRepresentation function could derive a valid standards-compliant representation of did:cheqd:testnet:DAzMQo4MDMxCjgwM from the above resolve function. The response would be similar to the one below containing Resolution Metadata, DIDDoc, and DIDDoc Metadata:
{
"didResolutionMetadata": {
"contentType": "application/did+ld+json",
"retrieved": "2022-08-03T09:52:49Z",
"did": {
"didString": "did:cheqd:testnet:ea2b76cf-a118-403a-8f49-244e56c9dcb8",
"methodSpecificId": "ea2b76cf-a118-403a-8f49-244e56c9dcb8",
"method": "cheqd"
}
},
"didDocument": {
"id": "did:cheqd:testnet:ea2b76cf-a118-403a-8f49-244e56c9dcb8",
"verificationMethod": [
{
"id": "did:cheqd:testnet:ea2b76cf-a118-403a-8f49-244e56c9dcb8#key1",
"type": "Ed25519VerificationKey2020",
"controller": "did:cheqd:testnet:ea2b76cf-a118-403a-8f49-244e56c9dcb8",
"publicKeyMultibase": "z6jVkB274neVf7iJETpMECwznBF8wDe8tpvF4BZLRZgMU"
}
],
"authentication": ["did:cheqd:testnet:ea2b76cf-a118-403a-8f49-244e56c9dcb8#key1"]
},
"didDocumentMetadata": {
"created": "2022-07-19T08:29:07Z",
"versionId": "a0a4f3a3-829d-4188-bc78-8bea6a522037",
"linkedResourceMetadata": [
{
"resourceURI": "did:cheqd:testnet:ea2b76cf-a118-403a-8f49-244e56c9dcb8/resources/44547089-170b-4f5a-bcbc-06e46e0089e4",
"resourceCollectionId": "ea2b76cf-a118-403a-8f49-244e56c9dcb8",
"resourceId": "44547089-170b-4f5a-bcbc-06e46e0089e4",
"resourceName": "DemoResource",
"resourceType": "CL-Schema",
"mediaType": "application/json",
"created": "2022-07-19T08:40:00Z",
"checksum": "7b2022636f6e74656e74223a202274657374206461746122207d0ae3b0c44298",
"previousVersionId": "", // empty string if no previous version, otherwise, resourceId of previous version
"nextVersionId": "" // empty string if no new version, otherwise, resourceId of new version
}
]
}
}

Architecture of DID Resolver for cheqd

As described above, the abstract resolve function is already available for the cheqd ledger via the default Cosmos SDK gRPC/REST API endpoints. Our primary objective with building a DID Resolver for cheqd was to design this resolveRepresentation piece as a standalone component that was not packaged within the cheqd-node ledger code.
This objective has certain advantages:
  • Updates to DID Resolver code can be carried out and released independently of cheqd-node releases. As a consequence, there's no need to go through an on-ledger governance vote, and voting period to make a change to resolveRepresentation.
  • A separate web service module would allow for flexibility in how to handle complex scenarios on DID URL Dereferencing, error code handling for DID URL requests, and safely handling content transport for various media types.
  • Making the DID Resolver a standalone, non-ledger module allows for an operator of this web service to independently scale their service horizontally and vertically.
We explored two architectural patterns for how a DID Resolver could be implemented for the cheqd ledger. The objective here was to explore and provide DID resolution operators multiple approaches for running resolution service, each with their own pros and cons (which are discussed below).
  1. 1.
    "Full" cheqd DID Resolver
    1. 1.
      Since the cheqd-node ledger / Cosmos SDK is written in Golang, this resolver would consist of Golang libraries imported from the existing ledger code. This promotes code reuse.
    2. 2.
      Data would be fetched from the ledger using the gRPC endpoint on a node, which allows it (by default) to take place over an encrypted channel since gRPC uses HTTP/2.
    3. 3.
      Data retrieved would be in the native Protobuf representation as stored on ledger, thus allowing data integrity computations to be made.
  2. 2.
    "Light" cheqd DID Resolver
    1. 1.
      Universal Resolver drivers are designed to be run as Docker containers. A limitation of this approach is that the computation footprint of a compute resource can be quite high, e.g., a Docker container may be 100 MB+ in size and suffer from slow startup times in a "cold-start" scenario.
    2. 2.
      Thus, our "Light" DID Resolver idea was to explore using Cloudflare Workers, a lightweight serverless compute platform. As a comparison, Cloudflare Workers are limited to 1 MB in size and have extremely low cold-start times. (We use Cloudflare Workers in our Cosmos SDK Custom Data API, for example.)
    3. 3.
      Cloudflare Workers can also be deployed outside the Cloudflare service in a Docker container using Miniflare. This could be used to provide a Docker container deployment option for the Universal Resolver did:cheqd driver.
    4. 4.
      However, a limitation of Cloudflare Workers is they do not allow a gRPC request to be made to an external endpoint. This would force the "Light" cheqd Resolver to use the gRPC-Web / REST endpoint resolve implementation to fetch data from the ledger. This could be considered a higher risk profile in terms of data integrity by resolver operators / client applications.
Both of the architectural patterns above are designed so that a Universal Resolver driver for did:cheqd could be created. The Universal Resolver project aims to provide a common REST API definition for DID Resolution where each DID method can provide a Docker container that with an easy-to-deploy mechanism for the specific DID method.

Full cheqd DID Resolver

The Full cheqd DID Resolver is able to use github.com/cheqd/cheqd-node as a Golang module for send resolve requests to a cheqd node instance to fetch DIDDoc / Resources from the ledger.
Since the Full cheqd DID Resolver is wrapped for usage as a Docker container image using the Universal Resolver specification, the end-to-end sequence diagram for our DID Resolver would look like below:
Full cheqd DID Resolver sequence diagram
Figure 1: "Full" cheqd DID Resolver sequence diagram (editable version)
The Full cheqd DID Resolver is designed to handle requests concurrently, while reducing the risk of large quantities of threads and requests blocking the efficiency of the on-ledger services.

Fetching Protobuf from ledger and converting it to JSON

Since Cosmos SDK SDK encodes data in Protobuf, the DID Resolver "marshalls" them to JSON. The software class diagram below describes how these components/methods are tied together:
Full cheqd DID Resolver class diagram
Figure 2: "Full" cheqd DID Resolver class diagram
Marshalling/unmarshalling requests back-and-forth between Protobuf and JSON is carried out by services in the "Full" DID Resolver
Full cheqd DID Resolver Protobuf <-> JSON marshalling
Figure 3: "Full" cheqd DID Resolver Protobuf <-> JSON marshalling (editable version)

DID URL Resolution / Dereferencing rules

The cheqd DID Resolver complies with the rules and algorithm defined in Decentralized Identifier Resolution (DID Resolution) v0.2. This section clarifies and expands some descriptions specific to cheqd.
If the output of the DID URL dereferencing function contains the didDocumentStream:
  • If the value of the Accept HTTP header is absent or application/did+ld+json (or other media type of a conformant representation of a DID document):
    • The HTTP response status code MUST be 200.
    • The HTTP response MUST contain a Content-Type header. The value of this header MUST be application/did+ld+json (or other media type of a conformant representation of a DID document).
  • The HTTP response body MUST contain the didDocumentStream, in the representation corresponding to the Accept HTTP header.
Since the cheqd DID Resolver APIs are REST APIs, the default Content-Type: application/did+ld+json encoding is used if the Accept header is not explicitly set since it matches the Accept: */* header that most client applications send.

Accept header is application/did+ld+json OR blank OR */*

  • Response HTTP headers
    • Status code 200 OK
    • Content-Type: application/did+ld+json
  • Response HTTP body
    • didDocument / contentStream contains @context section;
    • didResolutionMetadata / dereferencingMetadata contentType field is application/did+ld+json

Accept request HTTP header contains application/ld+json;profile="https://w3id.org/did-resolution"

  • Response HTTP headers
    • Status code 200 OK
    • Content-Type: application/ld+json;profile="https://w3id.org/did-resolution
  • Response HTTP body
    • didDocument / contentStream contains @context section;
    • didResolutionMetadata / dereferencingMetadata contentType field is application/ld+json;profile="https://w3id.org/did-resolution

Accept request HTTP header contains application/did+json

  • Response HTTP headers
    • Status code 200 OK
    • Content-Type: application/did+json
  • Response HTTP body
    • didDocument / contentStream DOES NOT contain @context section;
    • didResolutionMetadata / dereferencingMetadata ContentType field is application/did+json

On-ledger Resource resolution rules

Requests to fetch on-ledger cheqd Resources are considered as a DID URL Dereferencing scenario it uses DID URL paths to lead to a Resource object, rather than a DIDDoc.
On the other hand, on-ledger cheqd Resources metadata requests are handled like DID URL Resolution since the result is a subsection of didDocumentMetadata specific to that resource.
API endpoints related to on-ledger cheqd Resources are described below. All of these request types are GET requests, with POST and PUT requests disallowed.

Get a specific Resource

Returns the Resource data/payload stored on ledger for specified resource. HEAD request type is also allowed for this endpoint since it can be used for HTTP content negotiation phase by client applications. In this case, only the HTTP Response headers are returned without the body.
Endpoint: /1.0/identifiers/<did>/resources/<resource_id>
  1. 1.
    Request HTTP headers
    1. 1.
      Accept should allow */* or match the mediaType of the Resource
    2. 2.
      Accept-Encoding may be allowed compression methods (e.g., gzip, compress, etc.)
  2. 2.
    Response HTTP headers
    1. 1.
      Status code 200 OK
    2. 2.
      Content-Type should be set to mediaType> of the Resource
    3. 3.
      Content-Encoding should be set to a valid content compression method in Accept-Encoding and response compressed accordingly
    4. 4.
      Content-Length should be set to the Resource size, in decimal bytes
  3. 3.
    Response HTTP body
    1. 1.
      Resource, encoded correctly according to the mediaType

Get metadata for a specific resource

Return metadata for a specified resource. This is effectively a portion of the DIDDoc Metadata block.
Endpoint: /1.0/identifiers/<did>/resources/<resource_id>/metadata

Get metadata for all Resources linked to a DID

Returns metadata for all Resources directly linked to/controlled by a DID. This is effectively the full linkedResourceMetadata section in DIDDoc Metadata block.
Endpoint: /1.0/identifiers/<did>/resources/all
Alternative endpoints
  1. 1.
    /1.0/identifiers/<did>/resources/
    1. 1.
      Status code 301
    2. 2.
      Redirect to /resources/all
  2. 2.
    /1.0/identifiers/<did>/resources
    1. 1.
      Throw an invalidDidUrl error

Error handling

The DID Resolution specification defines an algorithm for how invalid DID URL Resolution/Dereferencing errors should be handled. The cheqd DID Resolver aims to implement all of these scenarios, with the correct HTTP Response status codes based on the specific error encountered.

DID Resolution errors

The DID resolution output should always conform to the following format: ( didResolutionMetadata, didDocument, didDocumentMetadata )
If the resolution is unsuccessful, the DID resolver should return the following result:
  • didResolutionMetadata contains "error" : "<Error message>"
  • didDocument: null
  • didDocumentMetadata: []

DID URL Dereferencing errors

The DID dereferencing output should always conform to the following format: ( dereferencingMetadata, contentStream, contentMetadata )
  • dereferencingMetadata contains "error" : "<Error message>"
  • contentStream: null
  • contentMetadata: []

Decision

Given the drawbacks associated with a Light cheqd DID Resolver being unable to send gRPC requests to a cheqd node instance, the decision was taken to (initially) implement the Full cheqd DID Resolver architecture. Future work might separately consider and design a Light DID Resolver profile that can work with Cloudflare Workers while also allowing deployment through Docker.

Universal Resolver driver for did:cheqd

Compiled packages/binaries for the Full cheqd DID Resolver will be made available as a Docker container image with standardised Docker Compose configuration as defined in the Universal Resolver driver development guide to provide an easy mechanism for DID Resolution operators to incorporate did:cheqd as one of the supported DID methods they handle.

Configuration properties for Universal Resolver driver

  1. 1.
    Operators should be able to configure gRPC endpoints for mainnet as as testnet (or any other cheqd network namespaces) so that a single DID Resolver instance can resolve requests to any network. (E.g., grpc.cheqd.netfor mainnet, grpc.cheqd.network for testnet)
  2. 2.
    For each supported cheqd network namespace (e.g., mainnet, testnet), operators should be able to define one or more upstream cheqd node gRPC endpoints. (E.g., additional fallback endpoints besides grpc.cheqd.net for mainnet.) In case any one of the upstream endpoints is unavailable, this allows the DID Resolver to try a different upstream gRPC endpoint for resiliency.
  3. 3.
    Operators should be able to define whether their gRPC pull/request is secure (default, carried out over HTTP.2 with TLS handshake) or insecure (optional, equivalent to a grpcurl -plaintext connection downgrade). This accommodates scenarios where gRPC endpoints may not necessarily be equipped to handle TLS handshakes, since the default Cosmos SDK / cheqd node configuration endpoints do not have TLS certificates defined.

Consequences

Backwards Compatibility

  1. 1.
    Not applicable, since this would be the first release of cheqd DID Resolver

Positive

  1. 1.
    Full cheqd DID Resolver reuses existing code to handle some parts of resolve and representing DIDDocs.
  2. 2.
    Third-party applications that want to implement their own DID Resolver implementations for cheqd can consume the current implementation as a Golang module (e.g., import "github.com/cheqd/cheqd-did-resolver/services").
  3. 3.
    Universal Resolver driver implementation provides a standardised way of incorporating did:cheqd into Universal Resolver instances that support multiple DID methods.

Negative

  1. 1.
    Use of a custom DID URL path (e.g., /1.0/identifiers/{did}/resources/{resource-id} can be non-standard). Efforts should be made to see if this can implemented instead using DID URL queries according to the Trust over IP Foundation "DID URL Resource Parameter" specification.

Neutral

  1. 1.
    Lack of a Light cheqd DID Resolver, at least initially, might not give an easy and computationally-cheap alternative to running Docker containers.

References