Generative DID Maximalism

decentralized identity object-capabilities verifiable data registry

maxi-header.png

Trustless interoperability between a large number of DID methods is impossible. The main reason for this is that the W3C DID specification is trying to achieve way too many things in a single primitive. This article explains why full interoperability currently is extremely hard without relying on centralized intermediaries, and suggests a path forward using a subset of the DID specification. The primary aim of this approach is to enable self-certifying applications, where users do all validation of application state locally.

Why DIDs can’t interoperate

The main aim of the DID specification is to describes how you resolve a DID document from a DID URI. One premise is that the DID URI remains the same while the contents of the DID document can be updated over time. In order to enable interoperability between a wide range of different approaches, the spec left the actual core logic of DIDs, the DID method, completely up to developers. This has some advantages in that it allows a variety of different approaches to emerge, and they have. There are currently more than 167 DID methods registered. The spec does however give a small amount of guidance for how mutability of the DID document should be achived: By using a verifiable data registry (VDR).

Ok, so what’s a verifiable data registry? It is essentially a place where data can be registered and any user can verify at what point in time that data was registered. If we want to truely stay “decentralized” as in Decentralized Identifiers (DIDs), the user must be able to verify this data in a trustless manner. Most commonly DID methods use blockchains as verifiable data registries. Either by notarizing the DID directly on-chain, or registering a set of DIDs in a batch using a single transaction.

Now if you want your application to support many DID methods your software actually needs to run full nodes, and sometimes even archival nodes, on multiple blockchain networks. For some DID methods you also have to run additional indexing software as well. Clearly this quickly becomes a problem for anyone not running beefy server hardware. For this reason some people advocate the use of a universal resolver, but this approach fundamentally trades off decentralization/trustlessness (e.g. most users won’t run it on their laptop). Ideally all users can resolve and verify all DIDs locally.

Why do we need mutability anyway?

Different DID methods can be characterized to have different traits. Purely generative DIDs can be resolved by deterministically generating the DID document from the DID URI, while DIDs with the rotatable keys trait allows the DID controller to add and remove keys from the DID document. However, it also requires the DID method to use a verifiable data registry, e.g. a blockchain, while purely generative DIDs have no such requirements.

But mutability seems to be important? If we want a user to have a stable DID URI over time the ability to add new keys and revoke old ones is crucial. That’s why DID document mutability was one of the main considerations of the DID specification. So are DIDs inherently bound to not interoperate without compromising on decentralization? Fortunately there seems to be a middle way. We can limit ourselves to only use generative DIDs in combinations with something called object-capabilities.

Generative DIDs and OCAPs

Object-capabilities (OCAPs) can enable a cryptographic identifier to delegate permissions to any other identifier. Examples of OCAPs include UCAN and ReCap, which can interoperate using the CACAO encoding. It’s easy to see how we could use generative DIDs and OCAPs together to create the same effect of adding a new verification method to a DID document. We would simply create an object capability that delegates full permission for DID A to DID B. The tricky part is how we can do revocation (and sometimes discovery) of these capabilities. One approach could simply be to sign a message with DID A saying that DID B has been revoked. Anyone who observes this signed message would simply consider anything signed by DID B on behalf of DID A to be invalid. There’s a problem with this approach though. Without a sense of global revocation time, it seem like DID B was in fact never allowed to act on behalf of DID A. Thus we need a trustless way of timestamping these revocations.

Trustless timestamping actually requires a verifiable data registry, so are back at square one? We can’t have interoperability? Not quite. We can actually leave it up to each application to choose how to revoke capabilities. Although ideally there is convergence on one registry for the sanity of end users. We could also specify which registry should be used for revocation inside of our OCAPs, and apps could choose to only accept capabilities for which they can check revocations. An example of such a registry is CapReg, which is a proposed proposed improvement to Ceramic. It is possible to validate a CapReg instance completely trustlessly using only an ethereum light client, following the Ceramic protocol. Ceramic uses the light client friendly CAIP-168 specification for representing timestamp proofs.

Ok, but mutable DIDs that rely on full node blockchain infrastructure allows us to have smart contract logic that decides which public keys can be used for a DID. Do we have to abandon that type of functionality? Not at all! We can encode light client state proofs from blockchains as object capabilities. By solemnly relying on light clients we significantly reduce the amount of work needed to actually run an application and verify the integrity of data produced. To the point where users can feasibly run the software locally, without any trusted intermediary.

Figure 1: A map of the tradeoff space.
Legend:
🔴 Pick one
🟡 Mix and match
🟢 Easily support everything

A question that may arise at this point is if there’s actually any benefit to the approach of using generative DIDs + OCAPs + a revocation registry. Isn’t this just mutable DIDs with more steps? First of, in the mutable DID scenario developers are likely going to want to use OCAPs anyway stacking two layers of VDRs on top of each other. By restricting to only use generative DIDs, we can enable a greater amount of composability as demonstrated by Figure 1. We simply keep all VDR functionality at the revocation registry layer and enable composability on the DID and OCAP layers.

A few examples of what this could look like is explored in the DID method explainers below.

Method: did:key

Generative: Yes

OCAPs: UCAN or ReCap

DID Key flawlessly work’s with the methodology described above, because it’s just a plain public key. This makes it really flexible as it can easily be deployed in HSMs, in your frontend code, in your backend code, in IOT devices, etc.

Method: did:pkh

Generative: Yes

OCAPs: ReCap

DID PKH also works flawlessly without any modifications. Most of the time this DID represents a users cryptocurrency wallet. ReCap delegation is used to delegate permissions from the users wallet to a session key (often a Key DID).

Method: did:nft

Generative: No, not currently

OCAPs: ChainProof and ReCap

There’s a live proposal to make NFT DID purely generative. How would this work? Don’t we need a blockchain full node to resolve the owner of the NFT? It turns out that we can create something called a ChainProof, which is an object capability representing a state proof and encode this using CACAO. The ChainProof would provide a proof of the owner of the NFT at a particular blockheight, a ReCap delegation can then be used to delegate write permissions further to a DID Key for example. A ChainProof can be verified by anyone using only a light client of the given blockchain.

Method: did:evm

Generative: No, doesn’t exist yet

OCAPs: ChainProof and ReCap

A DID EVM could be created as a generalization of NFT DID. This method would also use ChainProofs for object capabilities. The method identifier would include a smart contract address and an evm method signature + potential call data.

Method: did:ens

Generative: No, not currently

OCAPs: ChainProof and ReCap

The ENS DID method could be modified to be purely generative. It can be seen as a special case of NFT DID.

Method: did:dns

Generative: No, not currently

OCAPs: DNSProof?

Currently DNS DID relies on resolving DNS records to generate the DID document from that. It could potentially be possible to create a generative DID method where the verification method is simply the DNS name itself, and create a new type of object capability that encodes a DNSSEC proof. Ideally this proof can be independently verified after the fact. There are many centralized services that provide historical data. Ideally we could store the signed records as an OCAPs and only use the centralized services (ideally multiple) to look up historical public keys for root servers.

Method: did:mailto

Generative: No, not currently

OCAPs: DKIMProof?

Similarly to DNS DID, Mailto DID could potentially be reimagined as a generative DID method. The DKIM-signatures could be used as object capabilities, given that we can verify historical signatures.

Method: did:3

Generative: No, not currently

OCAPs: Yes, UCAN or ReCap

The current version of 3 DID is based on a mutable Ceramic stream. However, there’s a new proposed version of 3 DID that is purely generative. This updated version is simply a hash of a CACAO. When used together with CapReg and future approaches to compressing OCAPs with zero-knowledge proofs, 3 DID can enable privacy preserving identity.

Conclusion

As we’ve seen in this article, the mutability of DIDs makes it next to impossible to interoperate many DID methods while keeping our systems trustless (e.g. end users fully verify the VDR of each method). This is due to the fact that many DID methods requires a blockchain full node. Syncing multiple blockchains is simply infeasible for most users which leads to centralized solutions for resolving DIDs. Interoperability is one of the big promises of the DID specification so in order to mitigate this problem we can limit the DID methods we choose to support to purely generative DID methods. By moving mutability functionality into OCAPs (for delegation) and revocation registries (for OCAP revocation), greater composability can be achieved. Builders can mix-and-match these building blocks enabling light-client-only architectures, ultimately making our software more composable while removing trust from intermediaries.


Thanks to Oliver Terbu, Juan Caballero, Aaron Goldman, Wayne Chang, Irakli Gozalishvili, and Michael Sena for feedback on draft versions of this article