The did:vh DID Method -- DID + Verifiable History (Editors Draft)
Light Dark System Issues
The did:vh DID Method -- DID + Verifiable History (Editors Draft)

The did:vh DID Method
v0.1 / Editors Draft
§

Specification Status: Editors Draft

Specification Version: v0.1 (see Changelog)

Source of Latest Draft: https://github.com/swcurran/didvh

Editors:
Stephen Curran
Participate:
GitHub repo
File a bug
Commit history
Related Specifications:
VH-Log — the log mechanism did:vh specialises
did:webvh — the web-located sibling of did:vh

Abstract§

did:vh (DID + Verifiable History) is a DID Method defined as a specialisation of the VH-Log specification. It is the location-independent sibling of did:webvh: a did:vh DID is simply

did:vh:<SCID>

where <SCID> is the self-certifying identifier derived from the DID’s first DID log entry. Unlike did:webvh, a did:vh DID contains no domain name or path, and so is not bound to any location where its DID Log is published.

did:vh constrains VH-Log by specifying:

  • The state object is a W3C DID Document (DIDDoc).
  • The log file is named did.jsonl, and the witness proofs file did-witness.json, as in did:webvh.
  • The DID identifier is the method prefix and the SCID, nothing else.

Because the DID carries no location, sourcing the DID Log is separated from resolving the DID. A did:vh Resolver implements the standard DID Resolution resolve(did, resolutionOptions) function: the client passes the DID Log and witness proofs directly, or by reference in the src option, as resolution options, and the Resolver verifies the log and returns the DIDDoc. How a client sources the DID Log beforehand — getting the log itself or a reference to it, because it is known in context, from a watcher, exchanged peer-to-peer, or from a web location — is described non-normatively, and a DID Log sourced in any of these ways is verified in exactly the same way. The SCID guarantees that the log is the authentic history of the DID, wherever it came from.

Through VH-Log, did:vh provides the same verifiable-history features as did:webvh:

  • A self-certifying identifier (SCID), globally unique and derived from the initial DID log entry, binding the DID to its full history.
  • The ability to resolve the full history of the DID using a verifiable chain of updates to the DIDDoc, from creation to deactivation.
  • DIDDoc updates containing a proof signed by a DID Controller-authorized key.
  • An optional mechanism for publishing pre-rotation key hashes to prevent loss of control of a DID when an active private key is compromised.
  • An optional mechanism for having collaborating witnesses approve updates to the DID before publication.
  • Support for cryptographic agility through versioned specification upgrades and algorithm-identifying formats.
  • An optional mechanism for listing watchers that archive and re-serve the DID’s history.

Separating the identifier from the location of its history gives did:vh properties that a web-located DID cannot have:

  • Permanence. The DID never has to change because the place its history is kept changes. There is no need for a portability mechanism.
  • Location independence. The DID Log can be kept on a web server, with a watcher, in a database, on a distributed ledger, or only in the wallets of the parties to a relationship — and in several such places at once.
  • Peer-to-peer use. A did:vh DID can be used between two parties who exchange their DID Logs directly, in the manner of did:peer [DID-PEER], while still supporting key rotation, pre-rotation and the other VH-Log features.

did:vh is comparable to the did:scid:vh format of the proposed did:scid metamethod, and adopts its src parameter.

Overview§

did:webvh added a verifiable history to did:web: a self-certifying identifier (SCID), a hash-chained log of every version of the DIDDoc, signed updates, pre-rotation, witnesses and watchers. It kept the domain name from did:web in the DID, so that a Resolver can turn the DID into an HTTPS URL and fetch the DID Log.

The domain name does two jobs in did:webvh: it tells a Resolver where to find the log, and it makes the DID a web identifier. It is not needed for security — the SCID and the verifiable history provide that on their own. But it does tie the DID to one location. When that location changes, the DID changes, which did:webvh handles with its optional portability mechanism, at the cost of a DID that is no longer the same string.

Many uses of DIDs need an identifier that never changes: as a permanent account identifier, embedded in content-addressed data, or held in long-lived credentials. Others have no web location at all: DIDs exchanged between two parties, DIDs kept in an organisation’s database, or DIDs anchored on a ledger. did:vh serves those uses by removing the location from the DID:

did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:example.com:dids:issuer
did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ

Everything else is kept as close to did:webvh as makes sense. The DID format, parameters, SCID and entry-hash algorithms, key authorisation and pre-rotation, witnesses, watchers, and DID URL query parameters are those of VH-Log as used by did:webvh. Implementations of did:webvh should be able to support did:vh by replacing the DID-to-HTTPS transformation with the DID Log sources defined here and dropping the web-location-specific features.

The following is a tl;dr summary of how did:vh works:

  1. A DID Controller creates the first DID log entry exactly as in did:webvh, using the DID string did:vh:{SCID} with the {SCID} placeholder, calculates the SCID and replaces the placeholders.
  2. The DID Log (did.jsonl) is a JSON Lines file of DID log entries, each containing a versionId, versionTime, parameters, state (the DIDDoc) and a Data Integrity proof, as defined in VH-Log.
  3. The DID Controller makes the DID Log available wherever it is needed: to the other party in a relationship, to one or more watchers, at one or more web locations, or in any other store. There is no single authoritative location.
  4. A client that needs to resolve did:vh:<SCID> first sources the DID Log: it gets the log itself from context, a watcher or a peer, or a reference to it, such as the web location given in did:vh:<SCID>?src=https://example.com/dids/issuer.
  5. The client calls the Resolver, passing the DID Log (and witness proofs) directly, or by reference in the src option. The Resolver verifies the log with the VH-Log algorithm and returns the DIDDoc. The SCID check ensures the log is the history of that DID, regardless of where it came from.
  6. Updates append entries to the log, which the DID Controller re-distributes to the places the log is kept.

Because a did:vh DID Log has no single authoritative location, a Resolver cannot always be sure it holds the latest version of the log, and a DID Controller could give different parties different extensions of the same log. Watchers and witnesses are the tools did:vh uses to manage those risks; see Log Freshness and Duplicity.

Relationship to did:scid§

The proposed did:scid metamethod defines a family of location-independent DIDs, each using the verification data format of an existing SCID-based DID method. Its did:scid:vh format uses the did:webvh format, and so is comparable to did:vh:

did:scid:vh:1:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ
did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ

did:vh shares the design goals of did:scid — a permanent, portable, self-certifying DID whose history can be kept in any number of locations — and adopts its src parameter for passing the log by reference, as both a resolution option and a DID URL query parameter. The differences are:

  • did:vh is a standalone DID method rather than a format of a metamethod.
  • The version of the specification in use is carried in the method parameter of the DID Log, as in did:webvh, rather than in the DID string.
  • In this version of did:vh, the src parameter carries only a URL: an https URL, a URL with another scheme the Resolver supports (such as ipns), or, as a resolution option to a local Resolver, a loopback http URL or a file: URL for a local directory. did:scid also allows it to name a DID method that stores the log.

Because the DID string is part of the first DID log entry, and so of the input to the SCID, a did:vh, a did:scid:vh and a did:webvh DID can never share a DID Log or an SCID. A DID Controller can link such DIDs using alsoKnownAs.

did:vh DID Method Specification§

Conformance§

As well as sections marked as non-normative, all examples, notes, and informative checklists in this specification are non-normative. Everything else in this specification is normative.

The key words MAY, MUST, MUST NOT, NOT REQUIRED, RECOMMENDED, REQUIRED, SHOULD, and SHOULD NOT in this specification are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

Relationship to VH-Log and did:webvh§

did:vh is a specialisation of the VH-Log specification. VH-Log defines the log mechanism underlying did:vh — the log entry structure, cryptographic chaining, SCID, parameters, witnesses, watchers, and the resolution algorithm. This specification defines only what is specific to did:vh, and implementers MUST consult VH-Log for the normative definition of the log mechanism.

did:vh constrains VH-Log by:

  • Specifying the state object as a W3C DID Document (DIDDoc).
  • Naming the log file did.jsonl and the witness proofs file did-witness.json.
  • Defining the DID as did:vh:<SCID>, with no location component.
  • Defining how the DID Log is sourced and passed to a Resolver as did:vh resolution options, including the src option.
  • Using method (e.g., did:vh:1.0) as the did:vh-specific form of the VH-Log logVersion parameter.
  • Defining the DID method identifier, CRUD operations and DID URL handling.

did:vh is deliberately as close to did:webvh as its lack of a location allows. The differences are:

did:webvh did:vh
DID did:webvh:<SCID>:<domain>[:<path>] did:vh:<SCID>
Locating the DID Log DID-to-HTTPS transformation Passed to the Resolver in resolution options, after the client sources it
method values did:webvh:1.0 did:vh:1.0
portable parameter Optional Not used — the DID never changes
Implicit #files and #whois services Derived from the HTTPS location None
Parallel did:web Optional Not applicable

The log format, SCID and entry hash algorithms, key authorisation, pre-rotation, witness identifiers (did:key), witness file format, and the versionId, versionTime and versionNumber DID URL query parameters are the same in both methods.

Target System§

did:vh does not have a single target system. A did:vh DID Log can be held by any system that can store and return a file: a web server, a watcher, a database or registry, a distributed ledger, or the wallets of the parties that use the DID. The SCID and the verifiable history make the DID Log verifiable no matter which of those systems it was obtained from.

Method Name§

The namestring that identifies this DID method is: vh. A DID that uses this method MUST begin with the prefix did:vh:. Per the DID specification, the prefix MUST be in lowercase. The remainder of the DID, after the prefix, is the method-specific identifier.

Method-Specific Identifier§

The did:vh method-specific identifier is the SCID of the DID, and nothing else. The SCID MUST be generated during the creation of the DID from its initial content.

A did:vh DID MUST conform to the following ABNF [RFC5234]:

vh-did = "did:vh:" scid
scid   = 46base58-char
base58-char = %x31-39 / %x41-48 / %x4A-4E / %x50-5A / %x61-6B / %x6D-7A
              ; the base58btc alphabet: 1-9, A-Z except I and O,
              ; a-z except l

A did:vh DID URL follows the generic DID URL syntax of [DID-CORE], with the addition of the optional src query parameter.

A did:vh DID, and DID URLs using the src and versionNumber query parameters and a fragment:

did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ
did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ?src=https://example.com/dids/issuer
did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ?versionNumber=3
did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ#key-1

The DID Log File§

The did:vh DID Log is a VH-Log log file. The log entry structure, JSON Lines serialisation, cryptographic chaining, and verification algorithm are all as defined in VH-Log. This includes VH-Log’s requirement that versionTime values are in whole seconds, with no fractional seconds, as in did:webvh.

The did:vh-specific constraints are:

  • When the log is stored as a file, it SHOULD be named did.jsonl, and MUST be named did.jsonl when stored at a location named by a src reference. Its media type SHOULD be text/jsonl.
  • The witness proofs file, when stored as a file, SHOULD be named did-witness.json, and MUST be named did-witness.json when stored at a location named by a src reference.
  • The state property of each entry MUST contain the DIDDoc for that version of the DID.
  • The parameters property MUST follow did:vh DID Method Parameters.

did:vh-specific examples of log entries that fail verification, in addition to the general examples in VH-Log. Illustrative and non-exhaustive.

  1. state.id is not the DID. The first entry has parameters.scid: "Qm111...", and an entry has state.id: "did:vh:Qm111...?src=https://example.com" or "did:webvh:Qm111...:example.com". Every entry’s state.id must be exactly did:vh:Qm111....

  2. portable parameter present. An entry includes "portable": true. portable is not a did:vh parameter.

  3. did:webvh method value. The first entry has parameters.method: "did:webvh:1.0". A did:vh log must use a did:vh method value.

DID Method Operations§

Create (Register)§

Creating a did:vh DID follows the Create algorithm defined in VH-Log, with the following did:vh-specific constraints:

  1. The DID string (VH-Log Create step 1) is the literal did:vh:{SCID}, where {SCID} is the placeholder for the SCID calculated later.

  2. The initial state object MUST be a DIDDoc (VH-Log Create step 3). Its top-level id MUST be did:vh:{SCID}. All other absolute references to the DID within the DIDDoc MUST likewise use the placeholder form (e.g., did:vh:{SCID}#key-1). The DIDDoc MAY contain any other content the DID Controller requires.

  3. The parameters (VH-Log Create step 4) MUST follow did:vh DID Method Parameters, including a did:vh method value.

  4. Witnesses. If witnesses are used, the witness proofs MUST be collected into the witness proofs file before the DID Log is distributed.

  5. Distribution (VH-Log Create step 7). Instead of publishing the log at a location derived from the DID, the DID Controller makes the DID Log (and witness proofs file) available through the DID Log sources (see Sourcing the DID Log) that the parties who will resolve the DID will use: giving it to a peer, sending it to watchers, publishing it at one or more web locations, or storing it where the ecosystem expects to find it. A DID Controller MUST make the log available through at least one source that those parties can use.

Read (Resolve)§

A did:vh Resolver implements the resolve function defined in [DID-RESOLUTION]:

resolve(did, resolutionOptions) →
   « didResolutionMetadata, didDocument, didDocumentMetadata »

The did input is the DID itself, did:vh:<SCID>, which says nothing about where the DID Log is. The client of the Resolver therefore passes the DID Log — and, if witnesses are in use, the witness proofs — in resolutionOptions, in one of the two ways defined below: directly, or by reference, in which case the Resolver first retrieves the files. The Resolver then verifies the DID Log and returns the requested version of the DIDDoc.

The Resolver does not discover the DID Log. How a client sources the DID Log — gets the log itself, or a reference to it — before calling resolve is described in the non-normative Sourcing the DID Log section. Once it has the DID Log the client passed, the Resolver retrieves further copies only when the client asks it to, with the checkWatchers and extraWatchers options, to check that the client’s copy is current and has not been forked.

did:vh Resolution Options§

A did:vh Resolver MUST recognise the following options, which pass the DID Log to it. They are did:vh-specific, and are to be registered in [DID-EXTENSION-RESOLUTION].

Option Value Purpose
didLog String The complete DID Log, in JSON Lines format.
didWitness JSON array The witness proofs file content, as defined in VH-Log’s Witness Proofs File section.
src String A reference to the location where did.jsonl (and did-witness.json) are stored. See The src Option.

A did:vh Resolver also supports the options defined in VH-Log’s Resolution Options section, passed in resolutionOptions. versionId and versionTime are defined in [DID-RESOLUTION]; versionNumber, checkWatchers, extraWatchers and minCopies are did:vh-specific, and are to be registered in [DID-EXTENSION-RESOLUTION]. The DID Log passed directly or by reference is the copy from the location defined by the specialisation (VH-Log Resolution Options step 1), and counts towards minCopies.

The options support two ways of giving the Resolver the DID Log:

  • Passing the log directly. The client passes the content of the DID Log in didLog, and of the witness proofs file (if any) in didWitness.
  • Passing the log by reference. The client passes, in src, a reference to the location from which the Resolver retrieves did.jsonl and did-witness.json, as defined in The src Option. In this version of did:vh, a reference is a web location, a location using another URL scheme the Resolver supports, or, for a Resolver local to its client, a local directory or loopback web location.

In addition to VH-Log’s requirements for its options:

  • Exactly one of didLog and src MUST be given.
  • didWitness MAY be given only with didLog.
  • A malformed option, a value that does not conform to the rules for src, or more than one of versionId, versionTime and versionNumber, MUST cause the invalidOptions error.
  • A Resolver MUST support logs passed directly. It MAY decline to retrieve from a src reference, based on its policy (for example, a Resolver that never retrieves from locations chosen by its callers), and then MUST return the featureNotSupported error, except as defined in Web Locations and Local Directories.
  • A VH-Log option the Resolver does not support MUST cause the featureNotSupported error. A Resolver SHOULD support checkWatchers, extraWatchers and minCopies.
  • Each extraWatchers value MUST be an https URL.

Passing the log directly, resolving version 2:

{
  "did": "did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
  "resolutionOptions": {
    "didLog": "{\"versionId\":\"1-Qm...\",...}\n{\"versionId\":\"2-Qm...\",...}\n",
    "didWitness": [ { "versionId": "2-Qm...", "proof": [ ... ] } ],
    "versionNumber": 2
  }
}

Passing the log by reference to a web location:

{
  "did": "did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
  "resolutionOptions": {
    "src": "https://example.com/dids/issuer"
  }
}

Passing the log by reference to a local directory, using a local Resolver:

{
  "did": "did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
  "resolutionOptions": {
    "src": "file:///var/lib/wallet/dids/QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ"
  }
}
The src Option§

The src (“source”) option is a reference to the location where did.jsonl and, if witnesses are active, did-witness.json are stored. A src value MUST be an absolute URL [RFC3986], and its scheme determines the kind of reference:

  • https, or http with a loopback host: a web location, such as https://example.com/dids/issuer or http://localhost:8000/dids/issuer.
  • file: a local directory [RFC8089], such as file:///var/lib/wallet/dids/QmfGEU....
  • Any other scheme: an other scheme reference, such as ipns://k51qzi5uqu5d.../dids/issuer.

A value that is not an absolute URL, or that is not valid for its kind, MUST cause the invalidOptions error.

Whatever the kind of reference, the Resolver MUST retrieve the files the same way: did.jsonl from the location, and, if the active parameters require witnesses, did-witness.json from the same location (for a web location, as defined in Web Locations). If did.jsonl is not found, the Resolver MUST return the notFound error.

Because a location can hold only one did.jsonl, a DID Controller or client storing several did:vh DID Logs in one place SHOULD give each its own location (a URL path or a directory). Using the SCID as the last path segment is a simple way to do so.

A src reference is not authoritative: a DID Controller MAY stop publishing at a web location at any time, and the DID Log found through any src reference may not be the latest. Because src is not part of the DID, the DID does not change when the log is stored elsewhere.

A src reference also says nothing about who controls the DID: a DID Controller can publish a log at any location it can write to, and anyone can put any src value in a DID URL. Resolvers and their clients MUST NOT treat a src reference as evidence of an association between the DID and the owner of the location.

The did:scid src parameter can also name a DID method that stores the verification data (for example, a DID-Linked Resource on a ledger). This version of did:vh supports only URL references. Other kinds of reference, such as DID values or references to other ways of storing a DID Log, may be added in a later version.

Web Locations§

A web location reference is the base URL under which the files are published on a web server, such as https://example.com/dids/issuer.

The following requirements apply to a web location reference:

  • The value MUST be an absolute URL with the https or http scheme, and MUST NOT include a query or fragment component.
  • The value MAY include a port, such as https://example.com:3000/dids.
  • For https, the host MUST be a domain name, not an IP address. A host containing non-ASCII characters (an internationalised domain name) MUST be converted to its ASCII form (A-labels) as defined in IDNA2008 [RFC5891] before retrieval; a host that cannot be converted MUST cause the invalidOptions error.
  • For http, the host MUST be a loopback host: localhost, an IPv4 address in 127.0.0.0/8, or [::1]. An http reference with any other host MUST cause the invalidOptions error. An http reference is a loopback reference, intended for development and testing.
  • After any trailing / is removed, each path segment, once percent-decoded, MUST be non-empty, MUST NOT be . or .., MUST NOT contain /, \ or NUL, and MUST NOT begin or end with whitespace. The checks apply to the decoded form because percent-encoded variants such as %2E%2E, %2F, %5C and %00 would otherwise pass unnoticed. A segment that fails a check MUST cause the invalidOptions error.
  • A loopback reference is subject to the same restrictions as a local directory reference: a Resolver that accepts requests from other systems MUST NOT retrieve from it and MUST return the invalidOptions error, and it MUST NOT be taken from a DID URL. A loopback reference would otherwise let a caller make a Resolver send requests to services on its own host.

To retrieve the DID Log from a web location, the Resolver MUST:

  1. Take the src value, removing any trailing /.
  2. If the URL has no path, append /.well-known/did.jsonl. Otherwise, append /did.jsonl.
  3. Retrieve the DID Log with an HTTP GET (HTTPS for an https reference), following the transport requirements below.
  4. If the active parameters require witnesses, retrieve the witness proofs file from the same URL with the final did.jsonl replaced by did-witness.json.

When retrieving from a web location, a Resolver MUST meet the requirements of VH-Log’s Retrieving Log Resources section, which include HTTPS with server authentication, no automatic redirects, and rejection of IP literals and private addresses. The only exception is a loopback reference, for which the requirements to use HTTPS and to reject loopback hosts do not apply.

This is the same layout as did:webvh, so a web location reference names the same location as the domain and path of a did:webvh DID:

Web location src values and the corresponding DID Log URLs:

https://example.com --> https://example.com/.well-known/did.jsonl

https://example.com/dids/issuer --> https://example.com/dids/issuer/did.jsonl

https://example.com:3000/dids/issuer --> https://example.com:3000/dids/issuer/did.jsonl

https://example.com/users/{SCID} --> https://example.com/users/{SCID}/did.jsonl

http://localhost:8000/dids/issuer --> http://localhost:8000/dids/issuer/did.jsonl

Local Directories§

A local directory reference identifies a directory on the system where the Resolver runs. It is for a Resolver used by a client on the same system — for example, a Resolver used as a library or command-line tool — such as a wallet that keeps the DID Logs it holds in local storage.

  • A local directory reference MUST be a file: URL [RFC8089] with an absolute path, an empty host or the host localhost, and no query or fragment component.
  • To retrieve the files, the Resolver MUST remove any trailing / from the path, and append /did.jsonl for the DID Log and /did-witness.json for the witness proofs file. A file that does not exist or cannot be read is treated as not found.
  • A Resolver MUST treat the content of the files exactly as if it had been passed directly.
  • A Resolver that accepts requests from other systems — for example, over HTTP — MUST NOT read local files, and MUST return the invalidOptions error for a local directory reference.
  • A local directory reference MUST NOT be taken from a DID URL, as defined in Reading did:vh DID URLs. A DID URL can come from an untrusted party; allowing it to name local directories would let such a party make a Resolver read files on its host.

Local directory src values and the corresponding DID Log files:

file:///var/lib/wallet/dids/QmfGEU... --> /var/lib/wallet/dids/QmfGEU.../did.jsonl

file://localhost/home/alice/dids/QmfGEU.../ --> /home/alice/dids/QmfGEU.../did.jsonl

Other Schemes§

A Resolver MAY support src references using other URL schemes, provided the scheme has hierarchical paths through which a base URL names a location holding did.jsonl and did-witness.json — for example, ipns and ipfs URLs naming an IPFS directory. For such a reference:

  • The value MUST NOT include a query or fragment component.
  • The Resolver MUST remove any trailing /, and append /did.jsonl for the DID Log and /did-witness.json for the witness proofs file. The /.well-known/ form used by web locations does not apply.
  • A Resolver that does not support the scheme MUST return the featureNotSupported error.
  • A Resolver MUST apply to the retrieval protections equivalent to those required for web locations, including to any gateway it uses to reach the scheme’s network.

Other scheme src values and the corresponding DID Log locations:

ipns://k51qzi5uqu5d.../dids/issuer --> ipns://k51qzi5uqu5d.../dids/issuer/did.jsonl

ipfs://bafybei.../ --> ipfs://bafybei.../did.jsonl

Some schemes name immutable content. An ipfs URL names a directory by the hash of its content, so any change to did.jsonl or did-witness.json — including witness proofs added after an entry — gives the directory a new identifier. An ipfs reference is therefore a snapshot of the DID Log, useful for archiving but never updated; an ipns name, which the DID Controller repoints to each new directory, can serve the latest log. See Log Freshness and Duplicity.

Resolution Algorithm§

A did:vh Resolver MUST apply VH-Log’s Read (Resolve) algorithm, including its Comparing Copies of a Log and Selecting a Version sections, with the following did:vh-specific rules:

  1. The DID. The did input MUST conform to the ABNF in Method-Specific Identifier; if it does not, return the invalidDid error.

  2. The options. Validate the resolutionOptions as defined in did:vh Resolution Options. If they are invalid, return the invalidOptions error.

  3. Getting the log (VH-Log Read step 1). Get the DID Log according to how it was passed:

    • Directly: the didLog value.
    • By reference: retrieve it as defined in The src Option. If the DID Log is not found, return the notFound error.

    The witness proofs file is needed only if the active parameters require witnesses. When it is, use the didWitness value if the log was passed directly, or retrieve the file from the same src reference as defined in The src Option.

  4. Parameters (VH-Log Read step 1). parameters MUST adhere to did:vh DID Method Parameters.

  5. Witnesses (VH-Log Read step 2.1). Witness proofs MUST be verified as defined in Witnesses.

  6. State (VH-Log Read step 6). For every entry, state.id MUST be exactly did:vh: followed by the scid parameter of the first entry, and that SCID MUST equal the SCID of the did input. This check applies to every entry verified, including those of further copies retrieved with checkWatchers or extraWatchers.

  7. Result. Return the DIDDoc (unless the DID is deactivated, as defined in Deactivate (Revoke)) with the metadata defined in DID Resolution Metadata. A failure that VH-Log defines returns the corresponding error listed there.

DID Resolution Metadata§

As defined in [DID-RESOLUTION], a did:vh Resolver SHOULD return the following DID Document Metadata:

{
  "versionId": "1-QmRRaLXwc6BjBuBPosSupJwEQ8w9f3znP7yfbpGfwcnLr6",
  "versionTime": "2025-01-23T04:12:36Z",
  "created": "2025-01-23T04:12:36Z",
  "updated": "2025-01-23T04:12:36Z",
  "scid": "QmPEQVM1JPTyrvEgBcDXwjK4TeyLGSX1PxjgyeAisdWM1p",
  "deactivated": false,
  "ttl": "3600",
  "witness": { ... },
  "watchers": [ ... ]
}

The items are taken from the VH-Log Resolution Result, as follows:

“Active” means as set by the last valid DID log entry. That is the last entry in the DID Log unless later entries fail verification, in which case it is the last entry before the first failure.

ttl and the witness threshold are strings, not integers, because [DID-RESOLUTION] does not permit integers in DID metadata; a client converts them to integers before use. updated, and the values marked active, reflect the DID Log passed to the Resolver, which may not be the latest (see Log Freshness and Duplicity).

The copies and warnings items of the VH-Log Resolution Result describe the resolution rather than the DID, so they are returned in the didResolutionMetadata, not the DID Document Metadata. A Resolver given checkWatchers, extraWatchers or minCopies MUST include copies, and SHOULD include warnings when there are any, with the structure and values defined in VH-Log:

{
  "contentType": "application/did+json",
  "copies": [
    {
      "source": "https://example.com/dids/issuer",
      "versionId": "3-QmRRaLXwc6BjBuBPosSupJwEQ8w9f3znP7yfbpGfwcnLr6",
      "status": "current"
    },
    {
      "source": "https://watcher.example.org",
      "versionId": "2-QmPEQVM1JPTyrvEgBcDXwjK4TeyLGSX1PxjgyeAisdWM1p",
      "status": "behind"
    },
    {
      "source": "https://watcher.example.net",
      "status": "unavailable"
    }
  ],
  "warnings": [
    {
      "code": "copy-behind",
      "source": "https://watcher.example.org",
      "message": "Copy is 1 entry behind the reference copy."
    },
    {
      "code": "copy-unavailable",
      "source": "https://watcher.example.net",
      "message": "Watcher did not respond."
    }
  ]
}

When a DID resolution error occurs, the error field MUST be included in the didResolutionMetadata, as defined in [DID-RESOLUTION], and Resolvers SHOULD include problemDetails following [RFC9457]. The following error values MUST be used. The first four are from [DID-EXTENSION-RESOLUTION]; logForked and insufficientCopies are did:vh-specific, and are to be registered there.

  • invalidDid — The DID is malformed, or the DID Log passed to the Resolver (or the requested version) fails verification.
  • invalidOptions — A resolution option is malformed or not permitted, including a local directory or loopback reference passed to a Resolver that accepts requests from other systems.
  • notFound — The DID Log was not found through the src reference, the requested version does not exist, or the resource referenced by a DID URL was not found.
  • featureNotSupported — The Resolver declined to retrieve from a src reference, does not support the scheme of a src reference, or does not support versionNumber, checkWatchers, extraWatchers or minCopies.
  • logForked — Two copies of the DID Log both pass verification but diverge, as defined in VH-Log’s Comparing Copies of a Log section. The problemDetails MUST include the fork report that section requires.
  • insufficientCopies — Fewer than minCopies copies of the DID Log were retrieved and matched.
Reading did:vh DID URLs§

The src, versionId, versionTime and versionNumber options can also be given as DID URL query parameters, such as did:vh:<SCID>?src=https://example.com/dids/issuer&versionNumber=2. didLog and didWitness are resolution options only, and MUST NOT be used as DID URL query parameters.

When a did:vh DID URL is dereferenced, the DID URL dereferencer (the client of the Resolver) decides which of these parameters to pass to resolve as resolution options:

  • It SHOULD pass a versionId, versionTime or versionNumber DID URL parameter as the resolution option of the same name. These select a version as defined in VH-Log’s Selecting a Version section.
  • A src DID URL parameter MUST be percent-encoded as required for a query parameter value by [RFC3986] (Section 3.4 and, for characters that conflict with delimiters such as & and =, Section 2.2), and a DID URL MUST NOT include more than one. The dereferencer MUST percent-decode it once and validate it as defined in The src Option, and MUST return the invalidDidUrl error if it is not valid.
  • A src DID URL parameter MUST NOT be a local directory or loopback reference. The dereferencer MUST return the invalidDidUrl error for such a reference, and MUST NOT pass it to the Resolver.
  • The dereferencer MAY apply its own policy to a valid src value — for example, using it only if it names a location the dereferencer trusts, or ignoring it in favour of a copy of the DID Log it already has. If it uses the src value, it passes it as the src resolution option, or retrieves the files itself and passes them directly in didLog and didWitness.

The src parameter never changes which version is returned. As a DID URL query parameter, src is equivalent to the src parameter of did:scid when its value is an https URL.

Update (Rotate)§

Updating a did:vh DID follows the Update algorithm defined in VH-Log, with the following did:vh-specific constraints:

  • State changes (VH-Log Update step 1): Changes are made to the DIDDoc. The top-level id MUST remain did:vh:<SCID>.
  • Parameters: parameters MUST follow did:vh DID Method Parameters.
  • Witnesses: If witnesses are active, the DID Controller MUST collect the threshold of witness proofs, and make the updated witness proofs file available, before distributing the updated DID Log.
  • Distribution: The DID Controller SHOULD make the updated DID Log available through every DID Log source where it previously made the log available: updating each web location, notifying watchers (see Watchers), and sending it to peers.
Applying Peer-to-Peer Updates§

When DID Logs are exchanged peer-to-peer, each party holds the only copies of the other’s log, and an update may carry only the new entries. A party that receives new DID log entries for a DID Log it holds MUST apply them as follows:

  1. Skip any received entry whose versionId is identical to that of an entry it already holds. If a received entry has the same version number as a held entry but a different versionId, the update is duplicitous and MUST be rejected.
  2. Append the remaining entries, in order, to a copy of the held DID Log.
  3. Resolve the DID with that copy as didLog, the received witness proofs file (or, if none was received, the held one) as didWitness, and the versionId of the last appended entry as versionId.
  4. If resolution succeeds, replace the held DID Log and witness proofs file with the ones used in step 3. Otherwise, reject the update and keep the held files unchanged.

A party SHOULD keep a rejected update as evidence of duplicity.

No second copy of the log is needed to detect an update that does not extend the held log. The entry hash of each entry is calculated with the versionId of the previous entry as input, so an appended entry verifies only if it continues from the last held entry. Requesting the versionId of the last appended entry in step 3 ensures that resolution fails, rather than returning an earlier version, if any appended entry is invalid.

Deactivate (Revoke)§

A did:vh DID is deactivated as defined in VH-Log's Deactivate section, by adding "deactivated": true to the parameters of a new DID log entry. Once a DID is deactivated, a Resolver MUST NOT return the DIDDoc and MUST include "deactivated": true in the DID Document Metadata, as defined in [DID-CORE]. Prior versions MAY be resolved with the version query parameters, and the metadata for them MUST include "deactivated": true.

As defined in VH-Log’s Deactivate section, a DID Controller can instead set updateKeys to [] without setting deactivated, so that the final DIDDoc is still returned but cannot be updated.

Unlike did:webvh, removing a published DID Log does not effectively deactivate a did:vh DID, because copies may be held by watchers, peers, and other sources. A DID Controller that wants to retire a DID SHOULD deactivate it explicitly and distribute the final log, so that all sources report the deactivation. See Log Freshness and Duplicity for why a Resolver may not see the deactivation immediately.

Sourcing the DID Log§

This section is non-normative.

A did:vh Resolver resolves the DID Log it is given. Before calling resolve, the client must therefore source the DID Log: it either obtains the DID Log and witness proofs themselves, which it passes directly in didLog and didWitness, or learns a reference to where they are stored, which it passes in src. In this specification, “source” covers both outcomes, while “obtain” and “retrieve” mean getting the files themselves. How the client sources the log depends on how it received the DID. Each place it can source the DID Log from is a DID Log source; this section describes the common ones. They can be combined, and a DID Log sourced from any of them is verified by the Resolver in exactly the same way.

Known in Context§

In many uses, a client already has the DID Log, or knows where to get it, because of the context in which it received the DID. For example:

  • A log sent with the DID. A protocol that carries a DID — such as a credential or presentation exchange — also carries the DID Log and witness proofs, or a web location for them. The client passes them directly, or passes the web location by reference in src.
  • An ecosystem DID host. An ecosystem requires its participants to publish their did:vh DID Logs to a registry or database it operates. The client retrieves the files from the host and passes them directly, or passes the host’s web location by reference in src.
  • A previously resolved log. The client kept the files from an earlier resolution, and passes them directly, or by reference to the local directory where it keeps them — perhaps after checking another source for newer entries.
  • A DID URL with a src parameter. The client received a DID URL such as did:vh:<SCID>?src=https://example.com/dids/issuer, and uses the location as described in Reading did:vh DID URLs.

From Watchers§

A watcher retrieves, verifies, archives and re-serves DID Logs, indexed by SCID. Because a did:vh DID is its SCID, a client that knows of a watcher holding the DID — for example, one its ecosystem uses — can retrieve:

  • the DID Log with GET <WATCHER URL>/log?scid=<SCID>, and
  • if witnesses are active, the witness proofs file with GET <WATCHER URL>/witness?scid=<SCID>.

These are the operations defined in VH-Log’s Watcher HTTP API Operations. The client then passes the files directly, or stores them in a local directory and passes that by reference in src.

Instead of retrieving from watchers itself, a client can pass the copy it has and ask the Resolver to check it against watchers, using the checkWatchers option for watchers listed in the log and the extraWatchers option for others. See did:vh Resolution Options.

Once a client has resolved the DID, the watchers metadata lists the watchers the DID Controller has chosen. A client can use them to check for newer entries, for example when the copy it holds may be out of date. A client is not limited to those watchers: any party can run its own watcher, or use one its ecosystem operates, and send it the DID Logs it relies on. See Log Freshness and Duplicity.

Peer-to-Peer Exchange§

Two or more parties can use did:vh DIDs with one another by exchanging DID Logs directly, in the manner of did:peer [DID-PEER], without publishing them anywhere. Unlike did:peer, the DIDs keep all the VH-Log features, including key rotation and pre-rotation. Each party keeps the other’s DID Log (and witness proofs file, if witnesses are active), and its client passes them directly, or by reference to the local directory where it keeps them.

Initial exchange. The DID Controller gives the other parties the complete DID Log from the first entry, and the witness proofs file if witnesses are active.

Updates. When the DID is updated, the DID Controller sends each party either the complete DID Log or just the new DID log entry. Sending just the new entry keeps updates small, and works when each party receives every update in turn; a party that has missed an update cannot apply it, and needs the complete DID Log. If the new entry must be witnessed, the DID Controller also sends the complete current witness proofs file; otherwise that is optional. The protocol used to send updates is outside the scope of this specification.

Applying an update. A party receiving new entries applies them as defined in Applying Peer-to-Peer Updates. Because each entry hash chains to the previous entry, this rejects any entry that does not continue from the log the party holds, without needing a second copy of the log.

A did:vh DID used only peer-to-peer can be created for each relationship, so that no identifier is shared between relationships.

From a Web Location§

A client that learns a web location where the DID Controller publishes the DID Log can pass it by reference in the src option, or retrieve did.jsonl and did-witness.json itself and pass them directly. A client can learn such a location from a src DID URL query parameter, from an alsoKnownAs entry in a previously resolved DIDDoc (see Advertising DID Log Sources), or from the context in which it received the DID.

Advertising DID Log Sources§

A DID Controller can tell others where current copies of the DID Log are kept:

  • The watchers parameter lists watchers that archive the log.
  • The DIDDoc alsoKnownAs property can include did:vh DID URLs for the same DID with a src parameter, one for each location where the DID Log is published (a web location, or another scheme such as ipns), as in did:scid. Each location listed this way is expected to hold the same DID Log, although some may briefly lag others while an update is distributed.
"alsoKnownAs": [
  "did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ?src=https://example.com/dids/issuer",
  "did:vh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ?src=https://backup.example.org/{SCID}"
]

Both are available only after the DID has been resolved at least once, so they serve to check for newer entries rather than to source the log in the first place.

Log Freshness and Duplicity§

A did:vh DID Log has no single authoritative location, so a client faces two risks that did:webvh handles by trusting the DID’s web location:

  • Staleness. A source may hold an older, shorter copy of the log, and so miss updates such as a key rotation or deactivation. A Resolver verifies the log it is given, but cannot know whether newer entries exist elsewhere.
  • Duplicity. A DID Controller, or an attacker holding its keys, could create two different extensions of the same log and show different ones to different parties.

For two verified copies of a DID Log for the same DID, log B extends log A if B contains every entry of A, in the same positions, with the same versionIds. Since each versionId includes a hash chained to the previous entry, it is enough to check that B contains an entry with the versionId of A’s last entry, at the same position.

A client that needs the current version of a DID can reduce the risk of staleness by sourcing the log from a source expected to be current — such as a watcher listed in the log — rather than relying only on a cached copy or a copy supplied by the party presenting the DID. The ttl metadata indicates how long the DID Controller expects a copy to stay current.

A client that obtains copies from more than one source can compare them. If one extends the other, the longer is the more current. If neither extends the other, the log is duplicitous: the client should not rely on any version after the last entry the copies have in common, and may keep the conflicting entries as evidence and report them, for example to watchers. A client can also have the Resolver make these comparisons, with the checkWatchers, extraWatchers and minCopies resolution options; the Resolver then fails with logForked if the log has been forked.

Witnesses and watchers address duplicity as described in VH-Log’s Witnesses and Watchers sections: witnesses are expected to approve only an entry that extends their own copy of the log, but no one else can confirm that they do, while watchers detect conflicting copies directly, but only the copies they see. For did:vh these matter more than for did:webvh, because there is no web location to act as the reference copy. A relying party that runs its own watcher, or uses one its ecosystem operates, gets a check that is independent of the DID Controller; extraWatchers lets it name such watchers when resolving. DID Controllers of did:vh DIDs that are relied on by many parties are encouraged to use witnesses and watchers, and parties relying on such DIDs are encouraged to use watchers they trust.

As VH-Log’s Selecting a Version section notes, selecting a version with versionNumber is less safe than with versionId. If the log is duplicitous, did:vh:<SCID>?versionNumber=3 can resolve to a different DIDDoc for different parties, while did:vh:<SCID>?versionId=3-Qm... resolves to one DIDDoc or fails.

DID Method Processes§

did:vh DID Method Parameters§

did:vh uses the VH-Log parameters mechanism, including its general rules. The parameters scid, updateKeys, nextKeyHashes, witness, watchers, deactivated, and ttl are used exactly as defined in VH-Log, with one did:vh-specific constraint: witness id values MUST be did:key DIDs (see Witnesses). The parameters object MUST NOT include any other properties, except those listed below.

The parameters property in the first DID log entry:

{
  "method": "did:vh:1.0",
  "scid": "{SCID}",
  "updateKeys": [
    "z82LkqR25TU88tztBEiFydNf4fUPn8oWBANckcmuqgonz9TAbK9a7WGQ5dm7jyqyRMpaRAe"
  ],
  "nextKeyHashes": [
    "enkkrohe5ccxyc7zghic6qux5inyzthg2tqka4b57kvtorysc3aa"
  ]
}
  • method: The did:vh form of VH-Log’s logVersion parameter, with the same rules: it MUST appear in the first entry, MAY appear in later entries to upgrade to a later version, and a value that is not exactly one of the acceptable values MUST cause resolution to terminate. logVersion itself MUST NOT be used. Acceptable values:
    • did:vh:1.0 — corresponds to VH-Log vh-log:1.0, and permits the same hash algorithm (SHA-256 only) and Data Integrity cryptosuite (eddsa-jcs-2022 [DI-EDDSA-V1.0] only) for both log-entry and witness proofs.

did:vh does not use the did:webvh portable parameter. An entry that includes it MUST be rejected.

Cryptographic Agility§

did:vh inherits cryptographic agility from VH-Log unchanged, with the method parameter in the role of logVersion.

SCID Generation and Verification§

The SCID is generated and verified exactly as defined in VH-Log’s SCID Generation and Verification section. The preliminary log entry is the one described in Create (Register), in which the {SCID} placeholder appears in the DID string did:vh:{SCID} wherever it is used, and in parameters.scid.

Entry Hash Generation and Verification§

The entry hash is generated and verified exactly as defined in VH-Log’s Entry Hash Generation and Verification section.

Authorized Keys and Pre-Rotation§

The authorized keys mechanism is as defined in VH-Log’s Authorized Keys section, and key pre-rotation as defined in its Pre-Rotation Key Hash Generation and Verification section. The proof cryptosuite MUST be one permitted by the active method parameter; Resolvers MUST NOT accept a structurally valid signature using any other cryptosuite.

A did:vh log has no location whose control an attacker would also need in order to publish an update (see No Authoritative Location), so DID Controllers SHOULD use pre-rotation.

Witnesses§

did:vh uses the witness mechanism defined in VH-Log’s Witnesses section, with the same identifier and key rules as did:webvh:

  • Each witness id MUST be a did:key DID whose key is compatible with a cryptosuite permitted by the active method. A witness parameter containing any other id MUST be rejected when the parameter is validated.
  • A witness proof’s verificationMethod MUST be a did:key DID URL of the form did:key:<multibase>#<multibase>, with the two multibase values byte-for-byte equal. The verification key MUST be recovered only by decoding the did:key; Resolvers MUST NOT resolve the witness DID or consult any other key store.
  • The witness proofs file is did-witness.json, with the data model defined in VH-Log’s Witness Proofs File section.

The witness proofs file reaches the Resolver in the same way as the DID Log: passed directly in didWitness, or retrieved from the location the src reference names. When passing the files directly, a client can obtain the witness proofs file from a different DID Log source than the DID Log — for example, from a watcher's /witness operation.

Witnesses matter more for did:vh than for did:webvh. With no single location for the log, an attacker holding the update keys can distribute an update through any source; with witnesses active, the update also needs a threshold of witness proofs. DID Controllers SHOULD use witnesses for DIDs relied on by many parties. Their value against duplicity depends on how far the witnesses are trusted, as described there.

Watchers§

did:vh uses the watcher mechanism defined in VH-Log’s Watchers section, including its HTTP API, which the did:webvh Watcher OpenAPI definition describes. For did:vh, watchers are also a primary DID Log, as described in From Watchers. Any party can run a watcher for a did:vh DID, whether or not it is listed in the watchers parameter. When resolving a did:vh DID, Resolvers MUST return the active list of watchers in the DID Document Metadata.

VH-Log’s notification operation, POST <WATCHER URL>/log?id=<identifier>, asks the watcher to retrieve the latest log from its location. A did:vh DID has no location, so for did:vh:

  • POST <WATCHER URL>/log?id=<DID URL>, where the DID URL includes a src query parameter, asks the watcher to retrieve the log (and witness proofs file) from that location. As for any DID URL, the src value cannot be a local directory or loopback reference.
  • POST <WATCHER URL>/log?id=<DID>, where there is no src parameter, MUST carry the complete DID Log as the request body, with media type text/jsonl. If witnesses are active, the DID Controller MUST first send the witness proofs file with POST <WATCHER URL>/witness?id=<DID>, with media type application/json.

In both cases the watcher MUST fully verify the log, MUST confirm that it extends any copy the watcher already holds (see Log Freshness and Duplicity), and indexes it by SCID.

Security Considerations§

This section is non-normative.

This section follows the guidelines in [RFC3552] and aligns with the [DID-CORE] requirements in DID Core 7.3. The security considerations in VH-Log’s Security Considerations section apply to did:vh unchanged, except as noted below. The sections below cover only what differs for did:vh. The requirements referred to here are defined in the normative sections of this specification and of VH-Log.

No Authoritative Location§

A did:webvh Resolver treats the DID’s web location as the authoritative place to find the current DID Log. A did:vh DID has no such location. The integrity of any copy of the log is fully protected by the SCID, the hash chain and the proofs, so a Resolver can safely accept a log from any source. What a Resolver cannot know from the log alone is whether it is complete — whether newer entries exist.

The resulting threats, and their mitigations, are:

  • Withholding and truncation. A source, or the party presenting a DID, supplies an older copy of the log, for example one from before a key rotation or deactivation. A Resolver verifies the log it is given but cannot detect this. Mitigation: clients that need the current version source the log from a source expected to be current, such as a listed watcher, and never treat the absence of newer entries at one source as proof that none exist. See Log Freshness and Duplicity.
  • Duplicity (split view). A DID Controller, or an attacker with its update keys, creates two different extensions of one log and gives them to different parties. Mitigation: clients that obtain copies from several sources compare them and do not rely on versions past the point of divergence; peers apply updates so that non-extending entries are rejected; witnesses are expected to refuse an entry that does not extend the log they hold, although others cannot verify that they do; watchers — including ones run by relying parties — detect and can report conflicting logs.
  • Compromised keys and no location to reclaim. In did:webvh, control of the web location is a second factor an attacker who steals the update keys also needs in order to publish. A did:vh log has no such location, so an attacker with the update keys can distribute a valid update through any source. Mitigation: DID Controllers are expected to use pre-rotation (see Authorized Keys and Pre-Rotation), and to use witnesses for DIDs relied on by many parties, so that an update also needs the approval of a threshold of witnesses (see Witnesses).

The src Option and Other Client-Supplied Values§

The src resolution option, and a src DID URL query parameter, are supplied by the client or by whoever gave it the DID URL, and so are untrusted input. The same is true of a didLog value, which is why it is verified in full:

  • Server-side request forgery. A web location src value can name any host. Resolvers apply to web location retrievals the transport requirements in VH-Log’s Retrieving Log Resources section (see Web Locations), including rejecting IP literals and not following redirects automatically. A Resolver can also decline to retrieve from src references at all (see did:vh Resolution Options), or allow only locations permitted by its configuration. The same hardening applies to a client retrieving from a web location or a watcher itself. The one exception is a loopback (http) reference, which is accepted only by a Resolver local to its client and never from a DID URL; see Web Locations. A Resolver supporting other schemes applies equivalent protections to those retrievals and to any gateway it uses.
  • Stale or wrong logs. A web location may serve an old copy of the log, or a log for a different DID. The first is covered under No Authoritative Location; the second is detected by the SCID check that the Resolution Algorithm applies to every entry.
  • Local file and service access. A local directory or loopback src reference is accepted only by a Resolver local to its client; a Resolver that accepts requests from other systems never reads local files or retrieves from loopback hosts, and a src DID URL parameter is never a local directory or loopback reference. A local Resolver reads only did.jsonl and did-witness.json from the named directory. See Local Directories.
  • Misleading association. The domain in a src value says nothing about who controls the DID. A DID Controller can publish a log at any location it can write to, and anyone can put any src value in a DID URL. For this reason, Resolvers and their clients do not treat a web location as evidence of an association between the DID and the owner of the domain (see The src Option).

Endpoint Authentication§

Retrievals from web locations and from watchers over HTTP use HTTPS with server authentication, as described in VH-Log’s Endpoint Authentication section. The only exception is a retrieval from a loopback (http) reference by a Resolver local to its client. As with all sources, a DID Log is verified by its content, not by the authentication of the server that provided it.

Peer-to-Peer Use§

When DID Logs are exchanged peer-to-peer, each party holds the only copies of the other’s log, and an update may carry only the new entry. With no other copy to compare against, the hash chain is what protects a party: it applies an update as defined in Applying Peer-to-Peer Updates, so that entries that do not continue from the log it holds are rejected, and keeps a rejected update as evidence of duplicity.

Post Quantum Attacks§

As defined in VH-Log’s Post Quantum Attacks section. Pre-rotation is especially valuable for did:vh, for the reason given under No Authoritative Location.

Resolver Validation Checklist§

This checklist extends VH-Log’s Resolver Validation Checklist with did:vh-specific points. It does not restate or replace the normative requirements.

  • DID syntax: the did input matches the did:vh ABNF.
  • Resolution options: didLog is a string, didWitness (only with didLog) a JSON array; a src value is an absolute URL with no query or fragment that is either a valid web location — https with a domain name host (or http with a loopback host), an optional port, and valid path segments — a valid local directory — a file: URL with an absolute path and an empty or localhost host — or a URL with another scheme, which returns featureNotSupported if the Resolver does not support it; at most one version option; malformed options return invalidOptions. A didLog value is verified in full, like any other copy. Local directory and loopback references are accepted only by a Resolver local to its client, and never from a DID URL.
  • Parameters: method is an acceptable did:vh value; logVersion and portable are absent.
  • SCID and identity: every entry’s state.id is exactly did:vh: plus the first entry’s parameters.scid, which equals the SCID in the DID being resolved.
  • Witnesses: as defined in Witnesses — did:key witness identifiers, keys recovered only from the did:key, body and fragment multibase values equal.
  • Passing the log: exactly one of didLog and src; didWitness only with didLog; a declined src returns featureNotSupported.

Privacy Considerations§

This section is non-normative.

This section addresses the privacy considerations in alignment with [RFC6973] Section 5, and aligns with the [DID-CORE] requirements in DID Core 7.4. The privacy considerations in VH-Log’s Privacy Considerations section apply to did:vh unchanged, except as noted below.

Surveillance§

Retrieving a DID Log reveals the Resolver's interest in the DID to the source it retrieves from: the host of a web location, or a watcher. Because did:vh lets a Resolver choose its sources, a Resolver can reduce this by using a cached copy, a watcher it trusts, or a copy supplied by the party presenting the DID, and by not retrieving web locations chosen by others. A Resolver or client can also retrieve through privacy-enhancing network tools, such as Tor or a VPN, so that the source does not learn the network address of the party interested in the DID.

Correlation and Identification§

A did:vh DID contains no domain name, so it does not by itself link its controller to a domain owner. src values and alsoKnownAs entries can reintroduce such a link, so DID Controllers that want to avoid it are advised not to publish them.

For peer-to-peer use, a DID Controller can create a separate did:vh DID for each relationship, so that the relationships cannot be correlated by identifier.

Right to Erasure (GDPR Art. 17)§

A did:vh DID Log is likely to be held in several places — by watchers, at web locations, and by peers. A DID Controller cannot remove every copy itself. The watcher deletion operation defined in VH-Log can be used to request removal from watchers; how such requests are handled is best defined by ecosystem governance.

Definitions§

VH-Log
The Verifiable History Log specification defines a general-purpose, append-only, cryptographically chained log structure for recording the verifiable history of a versioned state object. did:vh is a specialisation of VH-Log. See the VH-Log specification.
specialisation
As defined in VH-Log: a specification that uses VH-Log as its foundation. did:vh is a specialisation of VH-Log in which the state is a W3C DID Document (DIDDoc) and the DID contains only the SCID.
state
As defined in VH-Log: the versioned object recorded in each log entry. In did:vh, the state is the DIDDoc for that version of the DID.
Data Integrity
As defined in VH-Log: the W3C specification of mechanisms for ensuring the authenticity and integrity of structured digital documents using digital signatures and other cryptographic proofs.
Decentralized Identifier
Decentralized Identifiers (DIDs) [DID-CORE] are a type of identifier that enable verifiable, decentralized digital identities. A DID refers to any subject (e.g., a person, organization, thing, data model, abstract entity, etc.) as determined by the controller of the DID.
DID Controller
The entity that controls (creates, updates, deactivates) a given DID, as defined in [DID-CORE].
DIDDoc
A DID Document as defined by [DID-CORE] — the document returned when a DID is resolved.
DID Log
The VH-Log log for a did:vh DID: a list of DID log entries, one added for each update of the DID.
DID Log Entry
A JSON object in a DID Log that defines an authorized version of the DIDDoc and the parameters in effect. The first entry establishes the DID and version 1 of the DIDDoc.
DID Log source
A place from which a client sources the DID Log of a did:vh DID — that is, gets either the log itself or a reference to it — before passing it to a Resolver: the context in which the DID was received, a watcher, a peer, or a web location. See Sourcing the DID Log.
DID Method
The mechanism by which a particular type of DID and its associated DID document are created, resolved, updated, and deactivated, defined in a DID method specification. This document is the DID method specification for did:vh.
did:key
did:key, as described in the W3C CCG specification, is a DID method that derives a DID document directly from a single encoded public key, requiring no registry or network interaction to resolve. did:vh uses did:key DIDs to identify witnesses.
Entry Hash
As defined in VH-Log: a hash over a DID log entry (excluding its proof) that chains the entry to its predecessor, and is included in the entry’s versionId.
ISO8601
As defined in VH-Log: a date/time expressed using the ISO8601 Standard.
JSON Lines
As defined in VH-Log: lines of JSON with whitespace removed, separated by newlines, as described at https://jsonlines.org/.
parameters
As defined in VH-Log: the configuration in each DID log entry that controls how the DID Controller generates entries and how Resolvers process the DID Log.
Pre-Rotation
As defined in VH-Log: a technique by which the controller of a key commits to the key it will rotate to next, without revealing it, protecting against an attacker who learns the current private key.
Resolver
As defined in VH-Log: a party that obtains and verifies a DID Log to produce the current or a historical DIDDoc.
self-certifying identifier
As defined in VH-Log: an identifier derived from the hash of the first DID log entry, generated with the placeholder {SCID} wherever the SCID is to appear. A did:vh DID is did:vh: followed by its SCID.
watcher
As defined in VH-Log: a party that retrieves, verifies, archives and re-serves DID Logs, indexed by SCID. For did:vh, watchers are also a primary DID Log source.
witness
As defined in VH-Log: a party that verifies a DID log entry before it is distributed and, if it approves, returns a Data Integrity proof. did:vh witnesses are identified by did:key DIDs.

References§

DI-EDDSA-V1.0
Data Integrity EdDSA Cryptosuites v1.0. Dave Longley; Manu Sporny; 2024-12-08. Status: W3C Technical Recommendation.
DID-CORE
Decentralized Identifiers (DIDs) v1.0. Manu Sporny; Amy Guy; Markus Sabadello; Drummond Reed; 2022-07-19. Status: REC.
DID-EXTENSION-RESOLUTION
DID Resolution Extensions Registry. The Decentralized Identifier Working Group (W3C); 2024-11-19. Status: W3C Technical Report.
DID-PEER
Peer DID Method Specification. Daniel Hardman; Stephen Curran; Sam Curren; 2025-05-20. Status: DIF Draft v1.0.
DID-RESOLUTION
Decentralized Identifier Resolution (DID Resolution) v1. Stephen Curran; Joe Andrieu; 2026-09-02. Status: W3C Editor's Draft.
RFC2119
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner; 1997-03. Status: Best Current Practice.
RFC3552
Guidelines for Writing RFC Text on Security Considerations. E. Rescorla; B. Korver; 2003-07. Status: Best Current Practice.
RFC3986
Uniform Resource Identifier (URI): Generic Syntax. T. Berners-Lee; R. Fielding; L. Masinter; 2005-01. Status: Internet Standard.
RFC5234
Augmented BNF for Syntax Specifications: ABNF. D. Crocker, Ed.; P. Overell; 2008-01. Status: Internet Standard.
RFC5891
Internationalized Domain Names in Applications (IDNA): Protocol. J. Klensin; 2010-08. Status: Proposed Standard.
RFC6973
Privacy Considerations for Internet Protocols. A. Cooper; H. Tschofenig; B. Aboba; J. Peterson; J. Morris; M. Hansen; R. Smith; 2013-07. Status: Informational.
RFC8089
The "file" URI Scheme. M. Kerwin; 2017-02. Status: Proposed Standard.
RFC8174
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba; 2017-05. Status: Best Current Practice.
RFC9457
Problem Details for HTTP APIs. M. Nottingham; E. Wilde; S. Dalal; 2023-07. Status: Proposed Standard.

did:vh Version Changelog§

  • Version 0.1 (Pre-Draft)
    • Initial draft. Defines did:vh as a specialisation of VH-Log, derived from did:webvh with the domain and path removed from the DID.
    • Defines resolution as the DID Resolution resolve(did, resolutionOptions) function, with did:vh resolution options for the two ways of passing the log: directly (didLog, didWitness) or by reference (src, aligned with did:scid, naming an https web location, a location using another supported URL scheme such as ipns, or, for a local Resolver, a loopback http location or a local directory), plus the version options.
    • Describes, non-normatively, how a client sources the DID Log (the log itself or a reference to it) before resolution — known in context, from watchers, peer-to-peer exchange (with updates sent as the complete log or just the new entry), and from a web location — and how to manage freshness and duplicity.
    • Removes the did:webvh features that depend on a web location: the DID-to-HTTPS transformation, the portable parameter, the implicit #files and #whois services, and parallel did:web publishing.