DID

Persistent decentralized identifiers (as used in atproto)

AT Protocol DIDs

AT Protocol uses Decentralized Identifiers (DIDs) as persistent account identifiers. The core DID standard was developed by the W3C, and describes a framework for compliant identifier systems ("DID methods"), of which several exist. To ensure broad interoperation across the ecosystem, atproto only supports a small number of "blessed" DID methods.

An example DID is did:plc:ewvi7nxzyoun6zhxrhs64oiz.

Blessed DID Methods

Currently, atproto supports two DID methods:

  • did:plc: a self-authenticating DID method developed specifically for use with atproto. See the DID PLC website for more details.
  • did:web: a W3C community draft based on HTTPS (and DNS). The identifier section is a hostname. This method is supported in atproto to provide an independent alternative to did:plc. See usage details below

It is possible that other methods will be supported in the future, but the intention is to keep the "blessed" set as small as possible. It is certainly not the intention to support all or even a significant fraction of all DID methods.

did:web in AT Protocol

The DID Web method is inherently tied to the domain name used, and does not provide a mechanism for migration or recovering from loss of control of the domain name.

In the context of atproto, only hostname-level did:web DIDs are supported: path-based DIDs are not supported. The same restrictions on top-level domains that apply to handles (eg, no .arpa) also apply to did:web domains. The special localhost hostname is allowed, but only in testing and development environments. Port numbers (with separating colon hex-encoded) are only allowed for localhost, and only in testing and development.

DID Identifier Syntax

Lexicon string format type: did

The DID Core specification places syntax restrictions on all DID identifiers, and individual DID methods might have their own more specific syntax. While atproto supports only a small set of DID methods today, it still makes sense to consider the general DID syntax in many parsing and validation contexts.

The general syntax constraints used to validate generic DID identifiers in atproto are:

  • The entire URI is made up of a subset of ASCII, containing letters (A-Z, a-z), digits (0-9), period, underscore, colon, percent sign, or hyphen (._:%-)
  • The URI is case-sensitive
  • The URI starts with lowercase did:
  • The method segment is one or more lowercase letters (a-z), followed by :
  • The remainder of the URI (the identifier) may contain any of the above-allowed ASCII characters, except for percent-sign (%)
  • The URI (and thus the remaining identifier) may not end in :.
  • Percent-sign (%) is used for "percent encoding" in the identifier section, and must always be followed by two hex characters
  • Query (?) and fragment (#) sections are allowed in DID URIs, but not in DID identifiers. In the context of atproto, the query and fragment parts are not allowed.

DID identifiers do not generally have a maximum length restriction, but in the context of atproto, there is a current hard limit of 2048 characters.

In the context of atproto, implementations do not need to validate percent encoding. The percent symbol is allowed in DID identifier segments, but the identifier should not end in a percent symbol. A DID containing invalid percent encoding should fail any attempt at registration, resolution, etc.

A reasonable starting-point regex for DIDs in the context of atproto is:

// NOTE: does not constrain overall length
/^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/

Examples

Valid DIDs for use in atproto (correct syntax, and supported method):

did:plc:ewvi7nxzyoun6zhxrhs64oiz
did:web:user.example.com

Valid DID syntax (would pass Lexicon syntax validation), but unsupported DID method:

did:method:val:two
did:m:v
did:method::::val
did:method:-:_:.
did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N

Invalid DID identifier syntax (regardless of DID method):

did:METHOD:val
did:m123:val
DID:method:val
did:method:
did:method:val/two
did:method:val?two
did:method:val#two

DID Documents

After a DID document has been resolved, atproto-specific information needs to be extracted. This parsing process is agnostic to the DID method used to resolve the document.

The DID declared in the document (in the id field) should always be verified against what was expected (eg, used for resolution).

The handle claimed by the DID is found in the alsoKnownAs array. Each element of this array is a URI string. Handles will have the URI prefix at://, followed by the handle hostname, with no other URI parts or trailing characters. The first syntaxtually valid handle found in the ordered list is treated as the claimed handle, even if it fails to resolve bi-directionally. Any other handle URIs should be ignored.

It is crucial to validate the handle bidirectionally, by resolving the handle to a DID and checking that it matches the current DID document. See the handle specification for details.

The DID is the primary account identifier in atproto, and an account whose DID document does not contain a valid and confirmed handle can still participate in the atproto ecosystem. Software should be careful to either not display any handle for such account, or obviously indicate that any handle associated with it is invalid.

The public atproto signing key for the account is found under the verificationMethod array, in an object with id ending #atproto, the controller matching the DID itself, and type matching the fixed string Multikey. The first valid atproto signing key in the array should be used, and any others ignored. The publicKeyMultibase field will be the public key in multibase encoding. A legacy format exists with slightly different requirements. See below for details about parsing fields for both formats.

A valid signing key is required for most atproto functionality. An account with no valid key in their DID document is likely broken.

The PDS service network location for the account is found under the service array, with id ending #atproto_pds, and type matching AtprotoPersonalDataServer. The first matching entry in the array should be used, and any others ignored. The serviceEndpoint field must contain an HTTPS URL of server. It should contain only the URI scheme (http or https), hostname, and optional port number, not any "userinfo", path prefix, or other components.

A working PDS is required for most atproto account functionality. An account with no valid PDS location in their DID document is likely broken.

Note that a valid URL doesn't mean the the PDS itself is currently functional or hosting content for the account. During account migrations or server downtime there may be windows when the PDS is not accessible, but this does not mean the account should immediately be considered broken or invalid.

Other protocol and application features may make use of verificationMethod keys and service entries. For example, labeling uses an additional key type, and application-specific "AppView" instances use bespoke service entries.

When parsing the id field in object elements of the service and verificationMethod arrays, implementations should support both relative fragment syntax (eg, "id": "#atproto_pds") and fully-qualified syntax (eg, "id": "did:web:example.com#atproto_pds").

Representation of Public Keys

The atproto cryptographic systems are described in Cryptography, including details of byte and string encoding of public keys.

Public keys in DID documents under verificationMethod, including atproto signing keys, are represented as an object with the following fields:

  • id (string, required): the DID followed by an identifying fragment. Use #atproto as the fragment for atproto signing keys
  • type (string, required): the fixed string Multikey
  • controller (string, required): DID controlling the key, which in the current version of atproto must match the account DID itself
  • publicKeyMultibase (string, required): the public key itself, encoded in "multikey" format

The publicKeyMultibase format for Multikey is the same encoding scheme as used with did:key, but without the did:key: prefix. See Cryptography for details.

Note that there is not yet a formal W3C standard for using P-256 public keys in DID verificationMethod sections, but that the Multikey standard does clarify what the encoding encoding should be for this key type.

Legacy Representation

Some older DID documents, which may still appear in did:web docs, had slightly different key encodings and verificationMethod syntax. Implementations may support these older DID documents during a transition period, but the intention is to require DID specification compliance going forward.

The older verificationMethod for atproto signing keys contained:

  • id (string, required): the fixed string #atproto, without the full DID included
  • type (string, required): a fixed name identifying the key's curve type
    • p256: EcdsaSecp256r1VerificationKey2019 (note the "r")
    • k256: EcdsaSecp256k1VerificationKey2019 (note the "k")
  • controller (string, required): DID controlling the key, which in the current version of atproto must match the account DID itself
  • publicKeyMultibase (string, required): the public key itself, encoded in multibase format (without multicodec, and uncompressed key bytes)

Note that the EcdsaSecp256r1VerificationKey2019 type is not a final W3C standard.

The EcdsaSecp256r1VerificationKey2019 verificationMethod is not a final W3C standard. We will move to whatever ends up standardized by W3C for representing P-256 public keys with publicKeyMultibase. This may mean a transition to Multikey, and we would transition K-256 representations to that type as well.

A summary of the multibase encoding in this context:

  • Start with the full public key bytes. Do not use the "compressed" or "compact" representation (unlike for did:key or Multikey encoding)
  • Do not prefix with a multicodec value indicating the key type
  • Encode the key bytes with base58btc, yielding a string
  • Add the character z as a prefix, to indicate the multibase, and include no other multicodec indicators

The decoding process is the same in reverse, using the curve type as context.

Here is an example of a single public key encoded in the legacy and current formats:

// legacy multibase encoding of K-256 public key
{
    "id": ...,
    "controller": ...,
    "type": "EcdsaSecp256k1VerificationKey2019",
    "publicKeyMultibase": "zQYEBzXeuTM9UR3rfvNag6L3RNAs5pQZyYPsomTsgQhsxLdEgCrPTLgFna8yqCnxPpNT7DBk6Ym3dgPKNu86vt9GR"
}

// preferred multibase encoding of same K-256 public key
{
    "id": ...,
    "controller": ...,
    "type": "Multikey",
    "publicKeyMultibase": "zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF"
}

Usage and Implementation Guidelines

Protocol implementations should be flexible to processing content containing DIDs based on unsupported DID methods. This is important to allow gradual evolution of the protocol ecosystem over time. In other words, implementations should distinguish between at least the distinct cases "invalid DID syntax", "unsupported DID method" and "supported DID method, but specific DID resolution failed".

While longer DIDs are supported in the protocol, a good best practice is to use relatively short DIDs, and to avoid DIDs longer than 64 characters.

DIDs are case-sensitive. While the currently-supported methods are not case sensitive, and could be safely lowercased, protocol implementations should reject DIDs with invalid casing. It is permissible to attempt case normalization when receiving user-controlled input, such as when parsing public URL path components, or text input fields.

Interop test vectors are available from the atproto-interop-tests repository.

Possible Future Changes

The hard maximum DID length limit may be reduced, at the protocol syntax level. We are not aware of any DID methods that we would consider supporting which have identifiers longer than, say, 256 characters.

There is a chance that the set of "blessed" DID methods will slowly expand over time.