Start building

Reference implementation in Rust, MIT licensed. Docker images for every component. Worked examples with real hashes you can reproduce.

Five-minute quickstart

Pull the CLI, stand up a local VDR, then create, update, and resolve a DID with full history. Everything runs locally; localhost DIDs resolve over plain HTTP by design, to ease development.

# 1. Pull the CLI and VDR images
docker pull ghcr.io/ledgerdomain/did-webplus-cli
docker pull ghcr.io/ledgerdomain/did-webplus-vdr

# 2. Run a local VDR (see the VDR README for current flags and ports)
docker run -p 8085:80 ghcr.io/ledgerdomain/did-webplus-vdr

# 3. Create a DID against it (see the CLI README for current subcommands)
docker run --network host ghcr.io/ledgerdomain/did-webplus-cli \
  did create http://localhost:8085

# 4. Resolve it, with history
docker run --network host ghcr.io/ledgerdomain/did-webplus-cli \
  did resolve "did:webplus:localhost%3A8085:<your-root-self-hash>?versionId=0"

Building from source instead? Clone the repo and run the data-model tests, which print the structures as they build:

cargo test -p did-webplus-core --all-features -- --nocapture
# full workspace suite:
SQLX_OFFLINE=true cargo test --workspace --all-features

Components

Component What it is Docker image Source
CLI Client-side operations: DID create/update/resolve, signing, verification ghcr.io/ledgerdomain/did-webplus-cli did-webplus/cli
VDR Verifiable Data Registry: serves DID microledgers for its domain ghcr.io/ledgerdomain/did-webplus-vdr did-webplus/vdr
VDG Verifiable Data Gateway: witnessing, archival, resolution at scale ghcr.io/ledgerdomain/did-webplus-vdg did-webplus/vdg
URD Universal Resolver Driver for DIF’s Universal Resolver ghcr.io/ledgerdomain/did-webplus-urd did-webplus/urd

Task guides

A VDR serves did-documents.jsonl files for DIDs under its domain, validating documents on POST (create) and PUT (update) before archiving them. It must validate that each DID’s host, port, and path match its own. Deployment configs, including docker-compose, live in the repo’s deployment directory. Full instructions: VDR README.
A VDG is a Full resolver run as a shared service: it fetches, verifies, and archives every history it touches, serves Thin resolvers, and accepts update notifications from VDRs for constant-time resolution. Its HTTP API is specified (see below). Full instructions: VDG README.
Choose Full or Thin. Full: local archive, offline resolution, fork detection; right for verifying parties with audit obligations. Thin: one HTTP GET to a trusted VDG, browser-friendly, no local state; equivalent in complexity to did:web resolution. Already running DIF’s Universal Resolver? Add the URD container and did:webplus resolves alongside your other methods.
The resolution URL is a plain file path, so any static host works as a VDR for read purposes, including GitHub Pages. The spec’s own repository hosts a resolvable DID this way: did:webplus:ledgerdomain.github.io:did-webplus-spec:uFiANVlMledNFUBJNiZPuvfgzxvJlGGDBIpDFpM4DXW6Bow. Create the DID with the CLI, commit did-documents.jsonl at the mapped path, push. Updates are appends to the same file.
Sign with a fully qualified key id (DID + selfHash + versionId + key fragment) so every signature pins the DID document version current at signing. Verifiers resolve that exact version, offline when archived. See Signature Generation With Witness.

Worked examples

VDG HTTP API

Every VDG MUST offer this API (spec: HTTP API for VDG).

Endpoint Purpose
GET /webplus/v1/fetch/{did}/did-documents.jsonl Fetch a DID’s microledger; supports HTTP range requests for incremental sync. Fetches from the VDR, verifies, and archives on cache miss.
GET /webplus/v1/resolve/{did} Thin resolution: latest DID document, pre-verified.
GET /webplus/v1/resolve/{did}?{query} Thin resolution of a specific document by versionId and/or selfHash.
POST /webplus/v1/update/{did} VDR notifies the VDG of an update; VDG pre-fetches, verifies, archives. Enables constant-time resolution and continuous witnessing.

Resolution options ride on request headers: X-DID-Request-Creation-Metadata, X-DID-Request-Next-Metadata, X-DID-Request-Latest-Metadata, X-DID-Request-Deactivated-Metadata, and X-DID-Local-Resolution-Only (resolve purely from locally known data, no network). Each defaults to false. The accept header is ignored: DID documents have one canonical JCS form.

Building your own implementation?

Two independent implementations have already validated the spec this way. The Python proof of concept was produced against the spec and passed interoperability testing with the Rust reference implementation. The community TypeScript package conformance-tested itself against the published examples, reproducing self-hashes and proofs exactly; follow issue #36 for its current scope and status.

Start with these, in order: validate against example-did-microledger.md and example-hash-function-selection.md; implement the DID document validation rules exactly, including JCS canonical-form equality; then run the interop harness from poc-did-webplus-py against your implementation and the reference VDR.

Test vector coverage continues to expand alongside the implementations; the published examples above are the durable conformance baseline. When your implementation passes, open an issue to get listed on the Implementations page.

Stuck, or shipping something?

Open an issue
Join the community