Decentralized Identifiers (DIDs) per W3C DID Core 1.0

Files
README.md

did #

Decentralized Identifiers for OCaml.

A Decentralized Identifier (DID) is a URI of the form did:<method>:<method-specific-id> that names a subject, such as a person or a service. The method decides who answers for the name. A did:web identifier rests on its domain's own HTTPS server. A did:plc identifier resolves through the PLC directory run by Bluesky, a central service. A client can still check that directory's answer, because each identifier is the hash of its signed genesis operation and every later update is signed by a rotation key, so a client that checks the operation log can tell a forged document from a real one. Resolving an identifier yields a DID document that lists the subject's verification keys and service endpoints; ATProto (Bluesky), for example, finds an account's data server this way. This library implements the syntax and document model of W3C DID Core 1.0, with resolvers for did:web and did:plc in separate packages.

Packages #

  • did: DID syntax and the document data model, with no I/O.
  • did-web: the did:web resolver, over HTTPS.
  • did-plc: the did:plc resolver, which queries a PLC directory (https://plc.directory by default). It checks the identifier's syntax but neither the genesis hash nor the operation log, so it trusts the directory's answer.
  • did-resolve: one resolve function that dispatches to did-web or did-plc by method.

Installation #

Install with opam:

$ opam install did did-web did-plc did-resolve

If opam cannot find the packages, they may not yet be released in the public opam-repository. Add the overlay repository, then install them:

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install did did-web did-plc did-resolve

Usage #

Syntax #

Parse a DID and extract its method and method-specific id:

# let did = Did.of_string_exn "did:plc:z72i7hdynmk6r22z27h6tvur" in
  (Did.method_name did, Did.method_specific_id did)
- : string * string = ("plc", "z72i7hdynmk6r22z27h6tvur")

Method names must be lowercase, while the method-specific id keeps its case:

# Did.of_string "did:Web:example.com"
- : (Did.t, Did.error) result =
Error (`Msg "invalid character 'W' in DID method name at offset 4")

URL construction #

The URL of a did:web document is a pure function of the DID. The specification maps the method-specific id to an HTTPS path, reading : as a path separator:

# Did_web.url_of_did (Did.of_string_exn "did:web:example.com")
- : (string, Did.error) result =
Ok "https://example.com/.well-known/did.json"
# Did_web.url_of_did (Did.of_string_exn "did:web:example.com:users:alice")
- : (string, Did.error) result =
Ok "https://example.com/users/alice/did.json"

Resolution #

The did:web and did:plc resolvers run under Eio:

let resolve_web env did =
  Eio.Switch.run @@ fun sw ->
  match
    Did_web.resolve
      ~sw ~clock:(Eio.Stdenv.clock env) ~mono:(Eio.Stdenv.mono_clock env) ~net:(Eio.Stdenv.net env) did
  with
  | Ok doc -> Fmt.pr "resolved: %a@." Did.Document.pp doc
  | Error e -> Fmt.pr "failed: %a@." Did_web.pp_error e

Service lookup #

A DID document indexes its service endpoints by id (a fragment) and by type. ATProto names an account's data server #atproto_pds:

# let did = Did.of_string_exn "did:web:example.com" in
  let doc =
    Did.Document.v
      ~service:[{
        id = "#atproto_pds";
        type_ = ["AtprotoPersonalDataServer"];
        service_endpoint = "https://pds.example.com";
      }]
      did in
  Option.map
    (fun (s : Did.Document.service) -> s.service_endpoint)
    (Did.Document.service_by_id doc "#atproto_pds")
- : string option = Some "https://pds.example.com"

Licence #

ISC. See LICENSE.md.