The VH (Verifiable History) Log (Editors Draft)
Light Dark System Issues
The VH (Verifiable History) Log (Editors Draft)

Verifiable History Log (VH-Log)
v0.1 / Editors Draft
§

Specification Status: Editors Draft

Specification Version: v0.1 (see Changelog)

Source of Latest Draft: https://github.com/swcurran/VH-Log

Editors:
Stephen Curran
Participate:
GitHub repo
File a bug
Commit history
Relationship to did:webvh:
VH-Log is a generalisation of the log mechanism defined in the did:webvh specification. The goal is that did:webvh, and other specifications, can be defined as specialisations of VH-Log.
Specialisations:
did:vh — the first specialisation
did:webvh — planned; VH-Log was extracted from it

Abstract§

The Verifiable History Log (VH-Log) specification defines a general-purpose, append-only, cryptographically chained log structure for recording the history of a versioned state object. Each entry in the log captures a new version of an arbitrary state object along with the cryptographic proofs that authenticate it. The chain of entries forms a tamper-evident history that any party can independently verify.

VH-Log provides the following features:

  • An append-only log of log entries, each representing a new version of the state object.
  • A Self-Certifying Identifier (SCID) derived from the genesis entry and embedded in the log’s identifier, enabling any verifier to confirm that a given log is the authentic log for that identifier.
  • Cryptographic chaining: each entry’s versionId is derived from a hash of the entry’s content, which includes the previous entry’s versionId (for the genesis entry, the SCID), linking all entries together in a microledger.
  • A parameters mechanism allowing per-entry configuration — such as authorised update keys, next key hashes for key pre-rotation, and witnesses — to evolve over the lifetime of the log.
  • An optional witness mechanism: a threshold of external parties sign each entry before publication, providing additional tamper-evidence beyond the controller’s own proof.
  • An optional watcher mechanism: parties that archive copies of the log and serve them independently of the original publisher, improving resilience and enabling detection of log tampering.
  • A resolution algorithm that, given a log and an optional target version, returns the state object from the entry at or before that version after cryptographically verifying the full chain.

The state type is intentionally unspecified in VH-Log — it is an arbitrary JSON object whose schema is defined by the specialisation that uses VH-Log as its foundation. This makes VH-Log a reusable building block for any system that requires a verifiable, append-only history of a versioned object.

VH-Log was derived from the log mechanism in the did:webvh v1.0 specification, which was iteratively developed through the specification process and two independent implementations to arrive at a stable, proven capability. The generalisation extracts that mechanism from its DID-specific context so it can serve as a reusable foundation for other specifications.

The first specialisation of VH-Log is the did:vh DID Method, in which the state object is a W3C DID Document and the DID is just did:vh:<SCID>. A did:vh DID contains no location, so a client can source its log — get the log itself, or a reference to it — from anywhere: the context in which the DID was received, a watcher, a peer, or a web location. Either is passed to the resolver as DID resolution options, and the log is verified in the same way.

VH-Log was extracted from the did:webvh specification, which is expected to be redefined in a future version as a specialisation of VH-Log, with the state object constrained to a W3C DID Document and the log located via a DID-to-HTTPS transformation.

Overview§

Many digital systems need to track the history of a versioned object — a configuration, an identity document, a credential, a policy — in a way that allows any party to verify that the history has not been tampered with. Existing approaches tend to rely on either a trusted central authority (which introduces a single point of failure and requires ongoing trust in that authority) or a distributed ledger (which introduces significant infrastructure complexity and cost).

VH-Log takes a different approach: the history is recorded as an append-only chain of signed entries, where each entry’s identifier is derived from a hash of its own content including the previous entry’s identifier. This cryptographic chaining makes the log self-verifying — a verifier does not need to trust the party serving the log, only the cryptographic proofs within it.

The following is a summary of how VH-Log works:

  1. A Log Controller creates a log by producing the first log entry, which establishes the initial state object. The SCID for the log is derived from the content of this first entry and embedded in the log’s identifier.

  2. The log is a JSON Lines file containing one log entry per line. Each log entry is a JSON object with the following properties:

    • versionId — a string combining the entry’s version number and a hash of the entry content. The hash input includes the previous entry’s versionId, chaining entries together.
    • versionTime — the time of the entry, as asserted by the Log Controller.
    • parameters — configuration that takes effect from this entry onward, such as the authorised update keys and logVersion.
    • state — the new version of the state object. The type and schema of the state object is defined by the specialisation.
    • proof — one or more Data Integrity proofs signed by an authorised update key.
  3. When creating the first entry, the Log Controller uses the placeholder string {SCID} wherever the SCID will eventually appear, calculates the SCID from the resulting entry, then replaces all placeholders with the calculated SCID before signing and publishing.

  4. To update the log, the Log Controller appends a new log entry, signed by an authorised update key, to the log file and publishes the updated file. If witnesses are configured, the required threshold of witness proofs must be collected and published before the updated log is made available.

  5. A Resolver retrieves the log file from whatever location the specialisation defines, then processes each log entry in order: verifying the cryptographic chain, verifying each entry’s proof, and accumulating parameters. The result is the state object from the latest valid entry, or from the entry at a requested version.

  6. Because each log entry is cryptographically bound to all prior entries via the hash chain, and the log’s identifier is bound to the genesis entry via the SCID, the log can be verified regardless of where it is retrieved from — whether from the original publisher, a watcher, a cache, or any other source.

Specialisations§

VH-Log defines the log structure, cryptographic mechanisms, and resolution algorithm. The state type, the log’s location and resource naming conventions, and any additional constraints or features are left to be defined by a specialisation — a specification that uses VH-Log as its foundation.

For example, the did:vh specification is a specialisation of VH-Log in which:

  • The state is a W3C DID Document.
  • The log’s identifier is the DID did:vh:<SCID>, which contains no location. The specialisation describes the sources from which a client can get the log itself or a reference to it — context, watchers, peer-to-peer exchange, and web locations — and passes either to the resolver as DID resolution options.
  • The log file is named did.jsonl and the witness file is named did-witness.json.
  • Witnesses are identified using did:key DIDs.
  • The method parameter (a did:vh-specific parameter) specifies the did:vh spec version and implies the VH-Log base version.

The did:webvh specification, from which VH-Log was extracted, is closely related and is expected to become a VH-Log specialisation in a future version. It differs from did:vh mainly in placing a domain and path in the DID and locating the log via a DID-to-HTTPS transformation.

A specialisation MAY define additional parameters, additional resource naming conventions, and additional constraints on the state object. A specialisation MUST NOT redefine or contradict the core VH-Log parameters and processing rules defined in this specification.

VH-Log 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.

The VH-Log File§

The log contains a list of log entries, one for each version of the state object. A new log entry is added each time the state object is updated or the parameters that control the generation and verification of the log change.

Each entry is a JSON object consisting of the following properties.

{ "versionId": "", "versionTime": "", "parameters": {}, "state": {}, "proof": [] }

  1. The value of versionId MUST be a string consisting of the version number (starting at 1 and incrementing by one per version), a literal dash -, and the entry hash, a hash calculated across the log entry content. The input to the hash is chosen so as to link each entry to its predecessor in a ledger-like chain. The input to the hash is specified in the Entry Hash Generation and Verification section of this specification.
  2. The value of versionTime MUST be a timestamp in UTC of the entry in ISO8601 format, as asserted by the Log Controller. The timestamp MUST be the time the entry will be retrieved by a witness or Resolver, or before. The timestamp MUST be expressed in whole seconds, with no fractional seconds (for example, 2025-04-01T17:39:50Z). Because each entry’s versionTime must be later than the previous entry’s, consecutive entries are at least one second apart.
  3. The JSON object parameters contains the configurations set by the Log Controller to be used in the processing of current and future log entries. Permitted parameters are defined in the VH-Log Parameters section of this specification.
  4. The JSON object state contains the state object for this version of the log. The type and schema of the state object is defined by the specialisation.
  5. The JSON array proof contains a Data Integrity proof created for the entry and signed by a key authorized to update the log.

Requiring whole-second versionTime values limits a log to at most one entry per second. Some specialisations may need entries more often than that, and others may want a longer minimum interval. Should the minimum interval between entries, and therefore the versionTime precision, be defined by the specialisation, with one second as the default?

After creation, each entry has (per the JSON Lines specification) all extra whitespace removed, a \n character appended, and the result added to the log file for publication.

The default name of the log file is vh-log.jsonl. A specialisation MAY define a different resource name. The location of the log file is defined by the specialisation. The location is used only to find the log; it plays no part in verifying control of the log, which depends only on the SCID, the hash chain and the proofs. A specialisation SHOULD document any assumptions it makes about log location that could affect the uniqueness or verifiability of a log.

Log files and witness files MUST be published and retrieved as described in Publishing and Retrieving Log Resources whenever they are transferred over a network.

Examples of log entries that fail verification. Illustrative and non-exhaustive; the normative rejection criteria are defined in the verification algorithm in this specification.

  1. Duplicate witness IDs. {"threshold": 1, "witnesses": [{"id": "W"}, {"id": "W"}]} — the same witness identifier appears more than once; each witness must be unique.

  2. Pre-rotation active but updateKeys omitted. If entry N-1 commits nextKeyHashes: ["H1","H2"], then entry N must contain an explicit updateKeys whose every member hashes to a value in ["H1","H2"]. An entry N omitting updateKeys (intending to inherit) is invalid.

  3. Wrong cryptosuite on log-entry proof. Rejected even if the signature is structurally valid, because the active logVersion defines the permitted cryptosuite. Verifying only proofPurpose is insufficient.

  4. Unknown logVersion value. An unrecognised value MUST be rejected and MUST NOT be silently downgraded.

Log Operations§

Create§

Creating a log is done by carrying out the following steps.

  1. Determine the log identifier The log identifier format is defined by the specialisation. The identifier MUST incorporate the SCID once it is calculated. Where the SCID appears in the identifier or in the state object, the placeholder string "{SCID}" MUST be used during the creation process until the SCID is calculated and inserted.

  2. Generate the authorization key pair(s) Authorized keys are authorized to control (create, update, deactivate) the log.

    1. If the log is to use pre-rotation, additional key generation will be necessary to generate the “next” authorization keys and their corresponding pre-rotation hashes.
    2. For each authorization key pair, generate a multikey based on the public key. The multikey representations are placed in the updateKeys property in parameters.
  3. Create the initial state object The initial state object is as defined by the specialisation. Where the SCID is to appear in the state object, the placeholder string "{SCID}" MUST be used.

  4. Generate a preliminary log entry JSON object containing the same JSON properties that will be in the published log entry, but with some values preset, pending calculation of the SCID and entry hash, and without the proof.

    1. The value of versionId MUST be the placeholder literal "{SCID}".
    2. The value of versionTime MUST be a valid UTC ISO8601 date/time string in whole seconds, and the represented time MUST be before or equal to the current time.
    3. The value of the parameters property MUST be a JSON object as defined in the VH-Log Parameters section. All required values in the first entry MUST be present. Where the SCID is referenced in the parameters, the placeholder literal string {SCID} MUST be used.
    4. The value of the state property MUST be the initial state object from the previous step.
  5. Update the preliminary log entry to the initial log entry

    1. Calculate the SCID The preliminary JSON object MUST be used to calculate the SCID as defined in the SCID Generation and Verification section of this specification.
    2. Replace the placeholder {SCID} Treating the preliminary JSON object as a string, perform a literal text replacement of every occurrence of the placeholder {SCID} with the calculated SCID.
      • NOTE: The literal string {SCID} cannot appear as a value anywhere in the first published log entry — it will always be replaced.
    3. Calculate the Entry Hash The updated preliminary JSON object MUST be used to calculate the entry hash as defined in the Entry Hash Generation and Verification section of this specification.
    4. Replace the preliminary versionId value The value of the versionId property MUST be updated with the literal string 1 (for version number 1), a literal -, followed by the entry hash.
    5. Generate the Data Integrity proof A Data Integrity proof MUST be generated over the updated preliminary JSON object using an authorized key in the updateKeys property in parameters and the proofPurpose set to assertionMethod.
    6. Add the Data Integrity proof The proof is added to the JSON object. The resultant JSON object is the initial log entry.
  6. Generate the first JSON Line The log entry MUST be serialised as a JSON Lines entry by removing extraneous white space and appending a newline character. The result is stored as the initial contents of the log file.

    If the Log Controller has opted to use witnesses, the required proofs MUST be collected and published in the witness file before the log file is published. See the Witnesses section.

  7. Publish the log The log file MUST be published at the location defined by the specialisation.

    If there are watchers configured, a webhook is triggered to notify the watchers that a new log is available. See the Watchers section.

Read (Resolve)§

The following steps MUST be executed to resolve the state object for a given log:

  1. Retrieve the log file from the location defined by the specialisation, as described in Retrieving Log Resources.
  2. The log file MUST be processed as described below.

A Resolver MAY use a watcher in addition to, or in place of, retrieving the log from the source. See the Watchers section. A Resolver that retrieves more than one copy of a log, or that has retained a versionId from an earlier resolution of the log, MUST process them as described in Comparing Copies of a Log.

To process the retrieved log file, the Resolver MUST carry out the following steps on each log entry in the order it appears in the file. Every step MUST be performed for every entry; in particular, Data Integrity proof verification (step 2) and entry hash verification (step 3) MUST NOT be skipped for intermediate entries on the grounds that the Resolver only needs the latest state object.

NOTE: A Resolver implementation that caches previously verified state could retrieve only subsequent entries and resume processing after the last verified entry rather than reprocessing the full log, provided the cached state was itself produced by full verification and the cache has not been modified since.

Even without caching state, a Resolver can retain the latest versionId it observes for each log it resolves. Comparing a later copy of the log against that versionId, as described in Comparing Copies of a Log, reveals a fork, truncation or rollback that has happened since the previous resolution.

For each entry:

  1. Update the currently active parameters with the parameters from the entry (if any). The parameters MUST adhere to the VH-Log Parameters section. Continue processing using the now active set of parameters.

  2. The Data Integrity proof in the entry MUST be valid and signed by an authorized key as defined in the Authorized Keys section, and with a proofPurpose set to assertionMethod.

    1. If the Log Controller has opted to use witnesses, Resolvers MUST retrieve and verify the witness file. For details, see the Witnesses section.
  3. Verify the versionId for the entry.

    1. The version number MUST be 1 for the first entry and MUST equal the previous entry’s version number + 1 for each subsequent entry. Gaps MUST terminate resolution.
    2. Exactly one dash - MUST follow the version number; missing or multiple dashes MUST cause rejection.
    3. The entry hash MUST follow the dash, MUST be a valid multihash in the algorithm permitted by the active logVersion, and MUST be verified per Entry Hash Generation and Verification. Verification MUST be performed for every entry.
  4. The versionTime MUST be a valid UTC ISO8601 string with explicit Z (or +00:00); values without a zone designator, or expressing a non-UTC zone, MUST be rejected.

    • The versionTime MUST be in whole seconds. Values with fractional seconds MUST be rejected.
    • The versionTime of every entry MUST be strictly greater than the immediately preceding entry’s. Equal timestamps MUST be rejected.
    • The versionTime of every entry MUST NOT be more than a small, implementation-defined tolerance in the future relative to the Resolver's current time. Resolvers SHOULD use a tolerance of no more than 5 minutes. Entries that exceed this tolerance MUST cause resolution to fail.
  5. When processing the first log entry, verify the SCID according to the SCID Generation and Verification section of this specification.

  6. Get the value of the log entry property state, which is the state object for this version. The specialisation MAY define additional verification steps for the state object.

  7. If Key Pre-Rotation is active (the previously active nextKeyHashes is non-empty), the entry being processed MUST include an explicit parameters.updateKeys and MUST NOT rely on inheritance. The hash of every multikey in parameters.updateKeys (computed per Pre-Rotation Key Hash Generation and Verification) MUST appear in the previous entry’s nextKeyHashes. A key not in the previous entry’s nextKeyHashes MUST cause resolution to terminate.

  8. As each log entry is processed and verified, collect the following information about each version:

    1. The state object.
    2. The versionId of the log entry.
    3. The UTC versionTime of the log entry.
    4. The latest list of active multikey formatted public keys authorized to update the log, from the updateKeys lists in parameters.
    5. If pre-rotation is being used, the hashes of authorized keys that must be used in the updateKeys list of the next log entry.
    6. All other VH-Log processing configuration settings as defined in the parameters object.
  9. If the parameters for any of the versions define that some or all log entries must be witnessed, further verification of the witness proofs must be carried out, as defined in the Witnesses section.

  10. Flag failed verifications appropriately, either invalidating the entire log or marking all entries from the first invalid entry to the end of the log as invalid.

  11. Respond to the resolution request:

    1. If all verifications pass, return the resolved state object.
    2. If the request specifies a target version (by versionId, versionTime or version number, as defined in Selecting a Version), return the state object from the log entry that was active at that version or time — even if later entries are invalid.
    3. If the log or requested version is invalid, return an appropriate error.

Resolvers SHOULD NOT cache a log that fails verification.

Comparing Copies of a Log§

A Resolver that retrieves more than one copy of a log (for example, from the source and from one or more watchers, as requested by the resolution options), or that has retained a versionId from a previous resolution of the log, need not verify every copy in full. Instead, it MUST:

  1. Choose any one copy and fully verify it. If it fails verification, discard it and choose another. The verified copy is the reference copy.
  2. Compare each remaining copy to the reference copy by checking a single versionId in each:
    • If the copy has the same number of entries or fewer: compare the versionId of the copy’s last entry with the reference copy’s versionId for the same version number. If they match, the copy is the same log, possibly out of date, and needs no further verification.
    • If the copy has more entries: compare the versionId of the reference copy’s last entry with the copy’s versionId for the same version number. If they match, the copy extends the reference copy. Verify only the copy’s additional entries, starting from the state reached at the reference copy’s last entry. If they pass verification, the copy becomes the reference copy. If they fail, discard the copy.
    • If the versionId values do not match: the copy diverges from the reference copy. Verify it in full. If it fails verification, discard it. If it passes verification, the log has been forked, and the Resolver MUST fail resolution as described below.
  3. If the Resolver has retained a versionId from a previous resolution of the log, compare it with the reference copy’s versionId for the same version number. The retained versionId was verified when it was first observed, so it needs no further verification.
    • If the reference copy has fewer entries than the retained version number: there is no entry to compare. Every copy is older than the log previously observed, so the log has been truncated or rolled back, or diverges at an entry the Resolver cannot compare. The Resolver SHOULD either fail resolution or report the retained-version-not-found warning described in Resolution Result.
    • If they match: the reference copy is the log previously observed, or an extension of it.
    • If they do not match: the log has been forked, and the Resolver MUST fail resolution as described below. In place of the first differing entry, the report MUST include the retained versionId and the reference copy’s versionId for the same version number.

A copy already found to match the reference copy still matches when a longer copy replaces it, because the longer copy extends the earlier reference copy; earlier comparisons need not be repeated. When every copy and any retained versionId have been compared, the Resolver resolves from the reference copy.

If two or more copies of a log pass verification but diverge, the log has been forked by a party holding authorized keys, and no copy can be relied on. The Resolver MUST fail resolution, including for a requested version that precedes the point of divergence, and MUST report the fork. The report MUST identify each diverging copy (for example, by the location it was retrieved from) and MUST include the version number of the first log entry at which the copies differ and each copy’s versionId for that entry. How the error is returned is defined by the specialisation.

These checks rely on two properties of the log. Each entry hash covers the previous entry’s versionId, so equal versionId values at entry k mean the two copies commit to the same history up to and including entry k. And because a versionId begins with its version number, entry k is always on line k of the log file, so each comparison reads a single known line of each copy rather than searching for the versionId.

Resolution Options§

A request to resolve a log MAY include the following resolution options. How a client of the Resolver specifies them (for example, as query parameters in an identifier URL, or as options passed to a resolution function) is defined by the specialisation. A Resolver given an option it does not support MUST fail resolution rather than ignore the option.

  • versionId, versionTime, versionNumber — Select a target version, as defined in Selecting a Version. At most one of these MAY be given.
  • checkWatchers — A non-negative integer, or the string all. The number of the log’s listed watchers from which the Resolver MUST retrieve a copy of the log. Defaults to 0.
  • extraWatchers — A list of watcher URLs, which need not be listed in the log, from each of which the Resolver MUST retrieve a copy of the log. Defaults to an empty list.
  • minCopies — A positive integer. The minimum number of retrieved copies of the log that MUST match for resolution to succeed. Defaults to 1.

The checkWatchers, extraWatchers and minCopies options let a caller check the log against other copies, to detect a copy that is out of date, has been tampered with, or has been forked. They apply as follows:

  1. The Resolver retrieves the log from the location defined by the specialisation, and fully verifies it as described in Read (Resolve).
  2. If checkWatchers is greater than 0, the Resolver reads the watchers parameter active at the last entry of the verified copy. If checkWatchers is all, or is not less than the number of listed watchers, the Resolver MUST retrieve a copy from every listed watcher. Otherwise, it MUST retrieve a copy from that many listed watchers, and SHOULD choose them at random, so that which watchers are checked cannot be predicted.
  3. The Resolver MUST retrieve a copy from each watcher in extraWatchers.
  4. Copies (including from extraWatchers) are retrieved from a watcher using the Watcher HTTP API Operations and the SCID of the log: the log file from <WATCHER URL>/log?scid=<SCID> and, if witnesses are active, the witness file from <WATCHER URL>/witness?scid=<SCID>. All retrievals MUST follow Retrieving Log Resources.
  5. All retrieved copies are processed as described in Comparing Copies of a Log, and each is reported in copies in the Resolution Result.
  6. If fewer than minCopies copies have a status of current or behind, the Resolver MUST fail resolution, with an error defined by the specialisation. The copy retrieved in step 1 counts towards minCopies.

A copy that cannot be retrieved, because the watcher is unreachable, does not hold the log, or returns an error, does not by itself fail resolution. It is reported with status unavailable and a copy-unavailable warning, and does not count towards minCopies.

extraWatchers causes the Resolver to fetch from URLs supplied by its caller. A Resolver offered as a service to other parties MAY refuse, or limit the number of, extraWatchers URLs. A URL that is not retrieved for this reason MUST be reported with status unavailable and a copy-unavailable warning stating that it was refused.

These options apply even when a target version is selected, because a forked log fails resolution for every version, as described in Comparing Copies of a Log.

Watchers listed in the log are chosen by the Log Controller. Checking them detects a source that is out of date or has been altered, but may not detect a Log Controller that deliberately gives different parties different logs, since such a Log Controller could list watchers it controls. extraWatchers lets a caller use watchers it chooses itself, such as its own or those run by its ecosystem, which is a stronger check.

Selecting a Version§

A request to resolve a log MAY specify a target version using one of the versionId, versionTime or versionNumber resolution options. A Resolver MUST support selecting by versionId and by versionTime, and SHOULD support selecting by versionNumber. A request MUST NOT specify more than one.

  • By versionId: the log entry whose versionId is exactly equal to the given value.
  • By versionTime: the log entry that was active at the given ISO8601 time — the entry with the latest versionTime that is not later than the given time.
  • By versionNumber: the log entry whose version number — the integer that precedes the - in its versionId — equals the given positive integer.

If no log entry matches, the Resolver MUST return a not-found error, as defined by the specialisation.

Selecting by version number is less safe than selecting by versionId. A versionId includes the entry hash, so it identifies the content of exactly one log entry. A version number identifies an entry only within the copy of the log being resolved: if two diverging copies of a log exist, the same version number can select different state objects in each. Where a reference must identify exactly one version — for example, to record which version of the state object was relied on — versionId is the safer choice.

Resolution Result§

After successful resolution, the following information is available to the caller:

  • versionId — The versionId from the resolved log entry.
  • versionTime — The versionTime from the resolved log entry.
  • created — The ISO8601 timestamp of the log’s first log entry.
  • updated — The ISO8601 timestamp of the log’s last valid log entry.
  • scid — The SCID of the log.
  • deactivated — true if the log has been deactivated, as defined in Deactivate, even when an earlier version was resolved; otherwise false.
  • ttl — The suggested cache duration from the ttl parameter, in seconds.
  • witness — The current witness configuration object, if witnesses are active.
  • watchers — The current list of watcher URLs.
  • copies — If the Resolver retrieved, or attempted to retrieve, more than one copy of the log, a list with one item for each copy, as described below.
  • warnings — A list of conditions found during resolution that did not prevent it, as described below. Empty if there are none.

The “last valid log entry” above refers to the case where resolution references a version that was valid but where later log entries fail verification. If all entries pass verification, the last valid entry is the last entry in the log.

A Resolver that retrieved, or attempted to retrieve, more than one copy of the log, as described in Comparing Copies of a Log and Resolution Options, SHOULD include copies. A Resolver that was given checkWatchers, extraWatchers or a minCopies greater than 1 MUST include copies. Each item describes one copy:

  • source — Where the copy was retrieved from, such as the location of the log file or the URL of the watcher that served it.
  • versionId — The versionId of the copy’s last log entry. Absent if status is unavailable.
  • status — One of:
    • current — The copy’s last log entry is the last entry of the reference copy.
    • behind — The copy matches the reference copy but has fewer entries.
    • invalid — The copy failed verification and was discarded.
    • unavailable — The copy could not be retrieved, or the Resolver declined to retrieve it, as described in Resolution Options.

A Resolver SHOULD include an item in warnings for each of the following conditions. Each item has a code from the list below and a human-readable message, and identifies the copy it concerns by its source, where there is one.

  • copy-behind — A copy has status behind. The message includes how many entries the copy is behind the reference copy.
  • copy-invalid — A copy has status invalid. The message includes the version number of the first log entry that failed verification.
  • copy-unavailable — A copy has status unavailable. The message says why, such as the watcher being unreachable, not holding the log, or the Resolver refusing the URL.
  • invalid-entries — Some log entries at the end of the reference copy failed verification, so resolution used the last valid entry.
  • retained-version-not-found — The reference copy has fewer entries than a versionId retained from a previous resolution of the log, as described in Comparing Copies of a Log.

A specialisation MAY define additional warning codes. How copies and warnings are represented in the specialisation's result format is defined by the specialisation.

Update§

To update a log, a new, verifiable log entry must be generated, witnessed (if necessary), appended to the existing log file, and published:

  1. Make the desired changes to the state object.
  2. Define the parameters JSON object to include any properties that affect the evolution of the log. Any parameters defined override the previously active value; any not included imply the existing values remain in effect. If no changes to parameters are needed, an empty JSON object {} MUST be used.
  3. Generate a preliminary log entry JSON object:
    1. The value of versionId MUST be the value of versionId from the previous log entry.
    2. The versionTime MUST be a ISO8601 format UTC timestamp in whole seconds, strictly greater than the previous entry’s versionTime, and MUST be the time the entry will be retrieved by a witness or Resolver, or before.
    3. The parameters as a JSON object.
    4. The state JSON object set to the new version of the state object.
  4. Calculate the new versionId, including incrementing the version number and using the process in Entry Hash Generation and Verification.
  5. Replace the versionId property with the value from the previous step.
  6. Generate a Data Integrity proof using an authorized key as defined in Authorized Keys, with proofPurpose set to assertionMethod.
  7. If Key Pre-Rotation is being used, the hash of all updateKeys entries MUST match a hash in nextKeyHashes from the previous log entry, as defined in Pre-Rotation Key Hash Generation and Verification.
  8. Add the proof JSON object as the value of the proof property.
  9. Serialise the entry as a JSON Line by removing extra whitespace and appending a \n.
  10. If witnesses are active, collect the threshold of proofs, update and publish the witness file BEFORE publishing the updated log file. See the Witnesses section.
  11. Append the new log entry to the existing log file.
  12. Publish the updated log file at the location defined by the specialisation. If there are watchers configured, trigger webhooks to notify them. See the Watchers section.

A Log Controller MUST only publish a log that extends the log it most recently published: every published log entry is retained, and the new log entry's predecessor is the last published log. A Log Controller MUST NOT publish, or give to any party, two different log entries with the same predecessor.

Deactivate§

Deactivating a log permanently closes it. The Log Controller signals that the log will not be updated again, and no further log entries are valid.

To deactivate a log, the Log Controller MUST publish a log entry whose parameters include "deactivated": true. That entry is the last log entry in the log. Once it has been published:

  • A Resolver MUST treat any log entry after the deactivating entry as failing verification.
  • A Resolver MUST report the log as deactivated, by setting deactivated to true in the Resolution Result. This applies when resolving any version of the log, including versions before the deactivating entry.
  • Whether a Resolver returns the state object of the deactivating entry, or of the version requested, is defined by the specialisation, as is how deactivation is presented in the specialisation's result format.

Because no entry can follow the deactivating entry, there is no need to also remove the authorized keys when deactivating.

A Log Controller can instead stop further updates without deactivating the log, by setting updateKeys to []. The log is not reported as deactivated, and the final state object continues to be returned, but no further entry can be signed. If pre-rotation is active, this takes two log entries: the first to stop pre-rotation by setting nextKeyHashes to [], and the second to set updateKeys to [].

A Log Controller can also remove the published log file. Watchers monitoring a removed log SHOULD continue to cache the last known valid state indefinitely.

VH-Log Parameters§

All log entries contain the JSON object parameters. This object defines the VH-Log processing parameters used by the Log Controller when publishing the current and subsequent log entries. Resolvers MUST use the same parameters to process the log.

The parameters object MUST only include properties defined in the active version of the VH-Log specification, or properties defined by the active specialisation.

General Rules for Parameters§

  • Default Values: When the logVersion parameter sets the version of this specification, any parameter introduced by that version but not explicitly set in the same log entry MUST assume the default value defined in this section.

  • Accumulation: Parameters accumulate across entries. An entry that omits a parameter inherits the previously active value, unless the parameter’s definition requires it to be explicitly set.

  • Activation: All parameters in the first log entry take effect immediately. For a parameter changed in a later entry, the parameter’s definition below, and the section it references, states whether the change applies to the entry that makes it or only from the next entry. For example, a witness parameter that replaces an active witness list takes effect only after the entry has been published, so that entry is witnessed under the previous list (see Witness Lists).

  • Allowed Values: Each parameter is constrained by the data type, structure, and allowed values specified in this section. A non-conformant value MUST cause Resolvers to reject the log entry.

  • Deactivation: Parameters that support deactivation (such as witness or nextKeyHashes) are set to defined values, described below, to indicate they are no longer active.

  • The JSON null value MUST NOT be used to indicate default or deactivated values, as it removes the typing information required for proper interpretation.

Specialisation-defined Parameters§

A specialisation MAY define additional parameters beyond those listed in this section. Specialisation-defined parameter names MUST NOT conflict with VH-Log parameter names. A VH-Log Resolver that does not implement a particular specialisation MUST ignore unrecognised parameters rather than failing.

An example of the parameters property in the first log entry:

{
  "logVersion": "vh-log:1.0",
  "scid": "{SCID}",
  "updateKeys": [
    "z82LkqR25TU88tztBEiFydNf4fUPn8oWBANckcmuqgonz9TAbK9a7WGQ5dm7jyqyRMpaRAe"
  ],
  "nextKeyHashes": [
    "enkkrohe5ccxyc7zghic6qux5inyzthg2tqka4b57kvtorysc3aa"
  ]
}

The following lists the parameters, their data types, and enumerated values.

  • logVersion: The version parameter. Specifies the version of the specification to be used for processing the log. Each value defines the cryptographic algorithms permitted for the current and subsequent log entries. A specialisation MAY designate a parameter of its own, with its own values, as the version parameter in place of logVersion, as described below. For example, did:webvh uses method, with values such as did:webvh:1.0.
    • MUST appear in the first log entry and MUST be one of the acceptable values enumerated below, or as defined by the specialisation for a designated version parameter.
    • Resolvers MUST reject any value that is not exactly one of the acceptable values for the version(s) of this specification, or of the specialisation, that the Resolver supports. Unknown values MUST NOT be silently downgraded, defaulted, or ignored — resolution MUST terminate.
    • If not present in later entries, the previous value continues to be active.
    • MAY appear in later entries to upgrade the spec version. A change to a lower version than currently active MUST be rejected.
    • A specialisation’s version typically covers its own rules as well as the log’s, so each value of a designated version parameter identifies a version of the specialisation that implies a VH-Log version. When a specialisation designates its own version parameter:
      • Every reference to logVersion in this specification, and every rule for it, applies to the designated parameter.
      • logVersion MUST NOT appear in the log, and Resolvers MUST reject a log entry that includes it.
      • The specialisation MUST define the acceptable values of the designated parameter, and for each value MUST state the VH-Log version it implies and the cryptographic algorithms it permits. These MUST NOT include algorithms not permitted by the implied VH-Log version.
      • Moving to a later value MUST NOT imply a lower VH-Log version.
    • Acceptable values defined by this specification:
      • vh-log:1.0
        • Permitted hash algorithms: SHA-256 [RFC6234] (multihash code 0x12) only. Any other algorithm MUST cause resolution to terminate.
        • Permitted Data Integrity cryptosuites for both log-entry proofs and witness proofs: exactly eddsa-jcs-2022 [DI-EDDSA-V1.0]. Resolvers MUST verify the proof’s cryptosuite property; an absent, mismatched, or non-conformant cryptosuite MUST cause the entry to be rejected. Verifying only proofPurpose is insufficient.
  • scid: The SCID value for the log.
  • updateKeys: A JSON array of multikey formatted public keys whose corresponding private keys are authorized to sign log entries that update the log. See the Authorized Keys section for details.
    • MUST appear in the first log entry and MAY appear in subsequent entries.
    • If not present in later log entries, the previous value continues to apply.
    • A key from the active updateKeys array MUST be used to authorize each log entry, where active is defined as follows:
      • In the first log entry, the active updateKeys is the one defined in that entry.
      • In all other entries without Key Pre-Rotation active, the active updateKeys is that of the most recent prior log entry.
      • In all other entries with Key Pre-Rotation active, the active updateKeys is that of the current log entry.
    • updateKeys SHOULD be set to an empty array [] when deactivating the log. See the Deactivate section for details.
  • nextKeyHashes: A JSON array of strings that are hashes of multikey formatted public keys that MAY be added to the updateKeys list in the next log entry. At least one entry of nextKeyHashes MUST be added to the next updateKeys list.
    • The process for generating the hashes and additional details are defined in Pre-Rotation Key Hash Generation and Verification.
    • If not set in the first log entry, defaults to an empty array ([]).
    • If not set in other entries, retained from the most recent prior value.
    • Once set to a non-empty array, Key Pre-Rotation is active. While active, both nextKeyHashes AND updateKeys MUST be present as explicit properties in every subsequent log entry until pre-rotation is deactivated (by setting nextKeyHashes to []). A subsequent entry that omits updateKeys MUST be rejected by Resolvers, even if the omission would otherwise inherit the previous value. Inheritance of updateKeys is never permitted while pre-rotation is active.
    • While Key Pre-Rotation is active, every multikey in the current entry’s updateKeys MUST have its hash in the previous entry’s nextKeyHashes.
    • A Log Controller MAY include extra hashes in nextKeyHashes that are not subsequently used. Unused hashes are ignored.
    • MAY be set to an empty array ([]) to deactivate pre-rotation.
  • witness: A JSON object declaring the set of witnesses and threshold number of witness proofs required to update the log. For details, see the Witnesses section.
    • Defaults to {} if not set in the first log entry.
    • If not set in other entries, retained from the most recent prior value.
    • If updated from {}, the change is immediately active and the corresponding log entry MUST be witnessed.
    • MAY be set to {} to indicate witnesses are not (or no longer) being used. If witnesses are active when set to {}, that log entry MUST be witnessed.
  • watchers: An optional entry whose value is a JSON array containing a list of URLs ([RFC9110]) identifying watchers for the log. See the Watchers section.
    • Defaults to [] if not set in the first log entry.
    • If not set in other entries, retained from the most recent prior value.
    • MAY be set to [] to indicate watchers are not (or no longer) being used.
  • deactivated: A JSON boolean indicating whether the log has been deactivated. See the Deactivate section.
    • Defaults to false if not set in the first log entry.
    • If set to true, the log is deactivated, and the entry that sets it MUST be the last log entry in the log.
  • ttl: An unsigned integer indicating how long, in seconds, a Resolver should cache the resolved state before refreshing. Analogous to the TTL parameter in DNS [RFC2181]. Range 0 to 2^31.
    • Defaults to 3600 (1 hour) if not set in the first log entry.
    • If set to 0, the log should not be cached.

Cryptographic Processes§

The Log Operations use the following cryptographic processes when generating and verifying log entries.

Cryptographic Agility§

VH-Log is designed to support cryptographic agility — the ability to adapt to evolving cryptographic algorithms and suites over time without breaking compatibility or requiring global coordination.

Cryptographic agility is achieved through the following mechanisms:

  • Self-describing cryptographic formats: All cryptographically generated data (e.g., hashes and signatures) use formats that encode the algorithm used. Hashes use the Multihash format, and signatures use Data Integrity Proofs, allowing verifiers to determine the algorithm from the data itself.

  • Specification versioning via the logVersion parameter: Each log entry may include a logVersion parameter specifying the version of the specification in use. This parameter is required in the initial log entry and may be updated in later entries to adopt newer versions.

  • Version-specific algorithm policies: Each version of this specification defines the permitted cryptographic algorithms and suites, constraining what Log Controllers may use and limiting verification requirements on Resolvers.

  • Response to cryptographic vulnerabilities: If flaws are identified in a permitted algorithm, a new version of this specification will be released. Log Controllers may then rotate to the newer version by updating the logVersion parameter.

SCID Generation and Verification§

The self-certifying identifier (SCID) is a required parameter in the first log entry and is a hash of the log’s inception event.

Generate SCID§

To generate the SCID for a log, the Log Controller MUST execute the following function:

base58btc(multihash(JCS(preliminary log entry with placeholders), <hash algorithm>))

Where:

  1. The preliminary log entry with placeholders is the pre-publication JSON object of what will become the first log entry. The placeholder is the literal string “{SCID}”.

    • The versionId entry, which MUST be {SCID}.
    • The versionTime entry, which MUST be a string that is the current time in UTC ISO8601 format, in whole seconds.
    • The complete parameters for the initial log entry as defined by the Log Controller, with the placeholder wherever the SCID will eventually be placed.
    • The state JSON object with the initial state object, with the placeholder {SCID} wherever the SCID will eventually be placed.
  2. JCS is an implementation of the JSON Canonicalization Scheme [RFC8785]. It outputs a canonicalized representation of its JSON input.

  3. multihash is an implementation of the multihash specification. Its output is a hash of the input using the <hash algorithm>, prefixed with a hash algorithm identifier and the hash size.

  4. <hash algorithm> is the hash algorithm used by the Log Controller. The hash algorithm MUST be one permitted by the active logVersion.

  5. base58btc is an implementation of the base58btc function. Its output is the base58 encoded string of its input.

Verify SCID§

To verify the SCID of a log being resolved, the Resolver MUST execute the following process:

  1. Extract the first log entry and use it for the rest of the steps.
  2. Extract the scid property value from the parameters in the first log entry.
  3. Determine the hash algorithm used by the Log Controller from the multihash scid value. The hash algorithm MUST be one permitted by the active logVersion.
  4. Remove the data integrity proof property from the log entry.
  5. Replace the versionId property value with the literal "{SCID}".
  6. Treat the resulting log entry as a string and do a text replacement of the scid value from Step 2 with the literal string {SCID}.
  7. Use the result and the hash algorithm (from Step 3) as input to the function defined in the Generate SCID section above.
  8. The output string MUST match the scid extracted in Step 2. If not, terminate the resolution process with an error.

Entry Hash Generation and Verification§

The entry hash follows the version number and dash character - in the versionId property in each log entry. Each entry hash is calculated across its log entry, excluding the Data Integrity proof. The versionId used in the input to the hash is a predecessor value to the current log entry, ensuring that the entries are cryptographically chained together in a microledger. For the first log entry, the predecessor versionId is the SCID (itself a hash), while for all other entries it is the versionId from the previous log entry.

Generate Entry Hash§

To generate the required hash for a log entry, the Log Controller MUST execute the process base58btc(multihash(JCS(entry), <hash algorithm>)) given a preliminary log entry as the string entry, where:

  1. JCS is an implementation of the JSON Canonicalization Scheme ([RFC8785]). Its output is a canonicalized representation of its input.
  2. multihash is an implementation of the multihash specification. Its output is a hash of the input using the <hash algorithm>, prefixed with a hash algorithm identifier and the hash size.
  3. <hash algorithm> is the hash algorithm used by the Log Controller. The hash algorithm MUST be one permitted by the active logVersion.
  4. base58btc is an implementation of the base58btc function. Its output is the base58 encoded string of its input.

The following is an example of a preliminary log entry processed to produce an entry hash. As this is a first entry, the input versionId is the SCID of the log.

{"versionId": "QmdmPkUdYzbr9txmx8gM2rsHPgr5L6m3gHjJGAf4vUFoGE", "versionTime": "2025-04-01T17:39:50Z", "parameters": {"witness": {"threshold": 2, "witnesses": [{"id": "witness-1"}, {"id": "witness-2"}, {"id": "witness-3"}]}, "updateKeys": ["z6MkgzBDcBFV3sk4ypPE5YXMZHmS213A3HpYY2LmcVKV15jr"], "nextKeyHashes": ["QmZreDcjvWEpyRFznQeExWNCsvMLk5i59AcRJJuQC8UodJ"], "logVersion": "vh-log:1.0", "scid": "QmdmPkUdYzbr9txmx8gM2rsHPgr5L6m3gHjJGAf4vUFoGE"}, "state": {"id": "example:QmdmPkUdYzbr9txmx8gM2rsHPgr5L6m3gHjJGAf4vUFoGE"}}

Note: the above example uses placeholder witness identifiers. A specialisation defines the required format for witness identifiers.

Resulting entry hash: QmQ6FJ4fk2xheSSQoEjVpTgx9AQPKhJgtR9hn1nr4EeCrZ

Verify The Entry Hash§

To verify the entry hash for a given log entry, a Resolver MUST execute the following process:

  1. Extract the versionId in the log entry, and remove from it the version number and dash prefix, leaving the entry hash.
  2. Determine the hash algorithm from the entry hash, which is a multihash. The hash algorithm MUST be one permitted by the active logVersion.
  3. Remove the Data Integrity proof from the log entry.
  4. Set the versionId in the entry object to be the versionId from the previous log entry. If this is the first entry, set the value to the SCID of the log.
  5. Calculate the hash string as base58btc(multihash(JCS(entry), <hash algorithm>)), where:
    1. entry is the data from the previous step.
    2. JCS is an implementation of the JSON Canonicalization Scheme ([RFC8785]).
    3. multihash is an implementation of the multihash specification.
    4. <hash algorithm> is the hash algorithm from Step 2.
    5. base58btc is an implementation of the base58btc function.
  6. Verify that the calculated value matches the extracted entry hash from Step 1. If not, terminate the resolution process with an error.

Authorized Keys§

Each entry in the log MUST include a Data Integrity proof where, at minimum:

  1. type is DataIntegrityProof,
  2. cryptosuite MUST be one permitted by the active logVersion.
  3. proofPurpose is assertionMethod,
  4. verificationMethod resolves to a multikey that appears verbatim in the active updateKeys.

Resolvers MUST reject an entry whose proof fails any check. A structurally-valid signature over a different cryptosuite than allowed by the active logVersion MUST NOT be accepted.

Log Controllers, witnesses and Resolvers MUST generate and verify Data Integrity proofs (for both log and witness proofs) as defined by the specification of the cryptosuite in use.

Private keys, and any other secret material such as random seeds, used by Log Controllers and witnesses MUST be kept in secure storage and MUST NOT appear in the log, the witness file, or any other published resource.

The authorized verification keys are the multikey-formatted public keys in the active updateKeys list from the parameters property. Any of the authorized verification keys may be referenced in the Data Integrity proof.

For the first log entry, the active updateKeys is the one defined in that first entry.

A Resolver MUST verify that the key used for signing each log entry is one from the list of active updateKeys. If not, terminate the resolution process with an error.

The active updateKeys for subsequent entries depends on whether Pre-Rotation is active:

No Key Pre-Rotation§

For all subsequent entries, the active list is the most recent updateKeys before the log entry to be verified. Thus, each log entry is signed by the keys from the previous entry.

Pre-Rotation Active§

For all subsequent entries, the active list is the updateKeys from the current log entry to be verified. Thus, each log entry is signed by the keys from the current entry.

Pre-Rotation Key Hash Generation and Verification§

Pre-rotation requires a Log Controller to commit to the authorization keys that will be used in the next log entry, without exposing those actual public keys. The purpose is that if the currently authorized keys are compromised, the attacker cannot rotate to new keys they control, because the next keys were already committed. Assuming the attacker has not also compromised the committed key pairs, they cannot rotate the authorization keys without detection.

A Log Controller MAY include the parameter nextKeyHashes with a non-empty list in any log entry to activate pre-rotation.

A Log Controller may turn off pre-rotation by setting nextKeyHashes to [] (empty array). If there is an active set of nextKeyHashes at the time, pre-rotation requirements remain in effect for that log entry. The subsequent log entry MUST use the non-pre-rotation rules.

To create a hash to be included in the nextKeyHashes array, the Log Controller MUST execute the following process for each possible future authorization key:

  1. Generate a new key pair. The key type MUST be compatible with the cryptosuites permitted by the active logVersion.
  2. Generate a multikey representation of the public key.
  3. Calculate the hash string as base58btc(multihash(multikey)), where:
    1. multikey is the multikey representation from Step 2.
    2. multihash is an implementation of the multihash specification.
    3. <hash algorithm> is the hash algorithm used by the Log Controller, which MUST be one permitted by the active logVersion.
    4. base58btc is an implementation of the base58btc function.
  4. Insert the calculated hash into the nextKeyHashes array within the parameters property.
  5. The generated key pair SHOULD be safely stored so that it can be used in the next log entry.

A Log Controller MAY include extra hashes in nextKeyHashes that are not subsequently used. Unused hashes are ignored.

After rotating from a pre-rotation public key, the corresponding private key SHOULD be treated as spent and securely destroyed. A Log SHOULD NOT reuse a pre-rotation key once its public key has been revealed in the log. Such reuse does not make the log invalid, and Resolvers are NOT REQUIRED to detect it, but a Resolver MAY warn when it does.

When processing other than the first log entry where pre-rotation is active, a Resolver MUST:

  1. For each multikey in the updateKeys property in the parameters of the log entry, calculate the hash using the algorithm permitted by the active logVersion.
  2. The resultant hash MUST be in the nextKeyHashes array from the previous log entry. If not, terminate the resolution process with an error.
  3. A new nextKeyHashes list MUST be in the parameters of the log entry currently being processed. If not, terminate the resolution process with an error.

Witnesses and Watchers§

Witnesses and watchers are optional external parties that strengthen the guarantees a log provides: witnesses approve each log entry before it is published, and watchers hold and re-serve independent copies of the log.

Witnesses§

The witness process provides a way for collaborators to work with the Log Controller to “witness” the publication of new versions of the log. This specification defines the technical mechanism for using witnesses. Governance and policy questions about when and how to use the technical mechanism are outside the scope of this specification.

Witnesses can prevent a Log Controller from updating or removing versions of a log without detection by the witnesses. With both the Log Controller's authorization key(s) and the log’s hosting location compromised, a malicious actor might be able to take control of the log by rewriting its history. By adding witnesses to monitor and approve each version update, a malicious actor cannot rewrite the previous history without having compromised a sufficient number of witnesses as well.

This protection depends on witnesses behaving as this specification requires — in particular, approving only an entry that extends their own copy of the log (see Witnessing a Log Entry Update). A witness proof shows only that a witness approved an entry; a Resolver cannot confirm how the witness reached that decision. How far witnesses are trusted is therefore a matter for the governance of the ecosystem. Watchers provide a way to detect conflicting logs that does not depend on that trust; see Watchers.

Witness Lists§

The list of witnesses that approve log updates is defined in the witness parameter, as described in the VH-Log Parameters section. After the first witness parameter has been set to other than {} (empty object), and while there are active witnesses, a threshold of the active witnesses must provide valid proofs associated with each log entry before the log entry can be published. If a log entry contains a replacement witness list, that new list becomes active AFTER the entry has been published.

Witness Identity and Key Format§

The format of witness identifiers and the method for deriving the verification key from a witness identifier are defined by the specialisation. The specialisation MUST specify an identifier format from which the witness verification key can be derived without requiring external resolution, in order to maintain the tamper-evident properties of the log.

The witness Parameter§

The witness element in a parameters object has the following data structure:

"witness": {
  "threshold": n,
  "witnesses": [
    {
      "id": "<witness identifier>"
    }
  ]
}

where:

  • threshold: a positive integer (JSON number, no fractional part, value ≥ 1) that MUST be attained or surpassed by the count of distinct verified witness approvals for a log entry to be considered approved. The threshold MUST be between 1 and the number of distinct witnesses[].id values, inclusive. A witness parameter where threshold is missing, non-integer, < 1, or > count(distinct ids) MUST be rejected; Resolvers MUST NOT silently coerce a malformed witness to “no witnesses” — they MUST terminate resolution with an error.
  • witnesses: the array of witnesses that MUST be non-empty, with each entry including the field:
    • id: (required) the identifier of the witness. The format is defined by the specialisation. Each id MUST be unique within the array (compared byte-for-byte after Unicode NFC normalisation). Each id contributes at most one approval to threshold counting, regardless of how many proofs are attributed to it.
Witness Threshold Algorithm§

The use of the threshold (rather than requiring all witnesses) prevents faulty witnesses from blocking publication. To determine if the threshold has been met, participants MUST:

  1. Verify each Data Integrity proof in the witness file for the relevant versionId independently.
  2. Attribute each verified proof to a witness id from the active witnesses list. Proofs that cannot be attributed MUST be discarded.
  3. Form the set of distinct attributed id values. The threshold check applies to the size of this set, not to the raw proof count.
  4. If |set| ≥ threshold, the update is “witnessed”; otherwise resolution MUST terminate with an error.
The Witness Proofs File§

Proofs from witnesses are placed into a separate file from the log file. The default name of the witness file is vh-log-witness.json. A specialisation MAY define a different name. The media type of the file SHOULD be application/json.

The data model for the witness file is:

[
  {
    "versionId": "1-Qmba111111...",
    "proof": [{ ... }, { ... }]
  },
  {
    "versionId": "2-Qzmb222222...",
    "proof": [{ ... }, { ... }]
  }
]

Where:

The method for deriving the witness verification key from the witness identifier and for verifying witness proofs is defined by the specialisation.

A valid proof from a witness carries the implication that ALL prior log entries are also approved by that witness. To maintain a manageable witness file size, the Log Controller SHOULD remove older proofs for published log entries, keeping only the latest proof for each witness.

To eliminate the race condition in publishing the log file and witness file, when a new log entry is being added, witness proofs MUST be added to the witness file and that file published BEFORE publishing the updated log file. As a result, Resolvers may find proofs for unpublished log entries in the witness file. Resolvers MUST ignore proofs with versionIds not in the log file.

To avoid unnecessary clutter in the witness file, array entries without proofs (containing only the versionId) SHOULD be removed.

A witness file MUST NOT contain proofs for two different versionIds with the same version number. When adding proofs to the witness file, a Log Controller MUST reject any proof for a versionId that conflicts in this way with the log it is publishing.

Witnessing a Log Entry Update§

The following process is used to witness a log entry update:

  • The Log Controller prepares the full log entry (including the proof element) for the new version, and shares it with the active witnesses. The specification leaves to implementers how the log entry data is provided to the witnesses.
  • Each witness MUST hold its own copy of the published log file prior to witnessing, and MUST confirm that the controller-supplied candidate entry verifies as the next entry to that log.
  • Each witness MUST independently verify the candidate entry using every step in Read (Resolve). Any failure MUST cause the witness to refuse approval.
  • A witness MUST NOT approve more than one log entry with the same predecessor.
  • Each witness determines (based on the governance of the ecosystem) if they approve of the update.
  • If the verification is successful and approval is granted, the witness creates and sends to the Log Controller a Data Integrity proof signed using the witness’s key. The specification leaves to implementers how witness proofs are sent to the Log Controller.
  • The Log Controller MUST add the proof to the record for the applicable versionId in the witness file.
    • The Log Controller MAY publish the updated witness file as new proofs are added.
    • The Log Controller MUST publish the updated witness file after a threshold of proofs have been received and before the witnessed log file is published.

A Resolver can verify that a threshold of witnesses signed an entry, but not that each witness held its own copy of the log or checked the entry against it. The requirements on witnesses above define correct behaviour; whether a given witness meets them is a matter of trust in that witness.

Verifying Witness Proofs During Resolution§

A Resolver MUST verify that all log entries that have active witnesses have a threshold of approving witnesses. Resolvers MUST:

  1. Complete all non-witness verifications of the log file before processing any witness proof. Witness verification MUST NOT substitute for entry-hash or signature verification.
  2. Retrieve the witness file.
  3. For each entry in the witness file, confirm its versionId matches a versionId present in this log file. Non-matching entries, including those for future entries, MUST be discarded.
  4. Verify enough proofs to meet the threshold for all entries requiring witnessing.
  5. For each entry requiring witnessing, confirm a threshold of verified, distinct-witness proofs whose versionId matches the current or any later published entry. Otherwise, terminate with an error.

Watchers§

Watchers are components found in some trust ecosystems that monitor logs on behalf of clients for various purposes, such as:

  • Caching verified logs: Storing verified state objects to facilitate efficient resolution.
  • Ensuring persistence: Maintaining access to logs even after removal by the Log Controller.
  • Detecting inconsistencies: Identifying malicious behavior by the Log Controller, such as republishing altered logs, or giving different parties conflicting logs (duplicity). A watcher does this by retrieving the log from each source it knows of and checking that each copy extends the copy it holds. This is the most reliable way to detect duplicity, because it does not depend on trusting the Log Controller or the witnesses, but it costs a retrieval and verification of the log on each check, and detects only the copies the watcher sees. A network of watchers can reach consensus independently of witnesses.

Any party may set up a watcher for a log, without the involvement of the Log Controller and without being listed in the log — for example, a relying party that wants a check independent of the Log Controller. A Log Controller may also opt to collaborate with specific watchers by publishing their URIs in the parameters of log entries. It is outside the scope of this specification how a Log Controller requests a watcher to monitor a log or how a watcher requests inclusion in the log.

A watcher SHOULD check that each copy of a log it retrieves extends the copy it holds, and SHOULD report any divergence it detects between copies.

The governance of watchers is out of scope for this specification, which defines only the technical mechanisms for notifying and querying watcher services.

Publishing Watcher URLs§

VH-Log provides a mechanism for notifying Resolvers about configured watchers. The watchers parameter lists URIs identifying the log’s watchers.

When resolving a log, Resolvers MUST provide the active list of watchers in the resolution result metadata, as noted in the Read (Resolve) section.

If a watchers entry is included in a log entry, it replaces the active set of watchers.

If a new watcher is added after a log has existed for some time, the Log Controller SHOULD notify the new watcher about previously created log resources.

Watchers do not need to be listed in the log. Watchers can operate independently of the Log Controller by polling for updates. Log Controllers MAY send notifications to watchers that they are aware of but do not list in the log.

Watcher Endpoints and Behavior§

A watcher is a web server accessible via HTTP that MUST support the following capabilities:

  • Client Requests:
    • Retrieve the log file for a given SCID.
    • Retrieve the witness file for a given SCID.
    • Retrieve a resource for a given SCID and resource path.
  • Notifications (typically from the Log Controller):
    • Notify the watcher about a new log entry.
    • Notify the watcher about a new or updated log resource.
    • Request removal of a given SCID from the watcher's cache.
    • Request removal of a given resource from the watcher's cache.
Watcher HTTP API Operations§

The following HTTP API operations define the interaction between watchers and other components:

  • GET <WATCHER URL>/log?scid=<SCID>: Returns the latest log file for the given SCID.
  • POST <WATCHER URL>/log?id=<log identifier>: Notifies the watcher of a log update, prompting retrieval of the latest log and witness files. The watcher is expected to index the log using its SCID.
  • POST <WATCHER URL>/log/delete?scid=<SCID>: Notifies the watcher that the given <SCID> should be deleted from its cache. The body MAY contain a Data Integrity proof from the requester for the Watcher to use when deciding on the legitimacy of the request. The Watcher will act (or not) according to its governance. This endpoint may be used to fulfill a “right to be forgotten” request.
  • GET <WATCHER URL>/witness?scid=<SCID>: Returns the latest witness file for the given SCID.
  • GET <WATCHER URL>/resource?scid=<SCID>&path=<resourcePath>: Retrieves the requested resource.
  • POST <WATCHER URL>/resource?scid=<SCID>&path=<resourcePath>: Notifies the watcher of a new or updated resource.
  • POST <WATCHER URL>/resource/delete?scid=<SCID>&path=<resourcePath>: Notifies the watcher that the given resource should be deleted from its cache.

Publishing and Retrieving Log Resources§

This section applies whenever a log file, witness file, or other log resource is published to, or retrieved from, a network location. A specialisation defines where those locations are; this section defines how they are served and fetched.

Publishing Log Resources§

  1. Log resources MUST be served over HTTPS, with the server authenticated by TLS server authentication. Plain HTTP MUST NOT be used, except for testing or non-production deployments.
  2. Self-signed TLS certificates SHOULD NOT be used in production.
  3. So that a Resolver running in a web browser can retrieve it, the HTTP response for the log file MUST include the header Access-Control-Allow-Origin: *.
  4. A Log Controller that serves log resources through intermediaries such as CDN caches or load balancers MUST ensure that those intermediaries do not serve stale or altered log resources.

Retrieving Log Resources§

A Resolver fetches resources from a location derived from a log identifier it does not control, which makes it a potential Server-Side Request Forgery (SSRF) vector. When retrieving log resources, a Resolver MUST:

  1. HTTPS only. Reject any URL scheme other than https, including after a redirect, and validate the server’s TLS certificate. Certificate validation MUST NOT be disabled by default.
  2. No automatic redirects. Not automatically follow HTTP 3xx responses when fetching log files or witness files. If following redirects is offered as an opt-in, every check in this list MUST be re-applied to each redirect target.
  3. Case-insensitive percent-decoding. Normalise the case of percent-encoded octets, per [RFC3986] §2.1, before making any allow or deny decision. For example, rejecting %3A but accepting %3a is non-compliant.
  4. Re-validate after decoding. Apply all host and path checks to the decoded values. Percent-encoded IP literals and traversal sequences (such as %2E%2E and %2e%2e) MUST be rejected after decoding.
  5. IP-literal and private-address rejection. Reject IPv4 and IPv6 literal hosts both (a) after percent-decoding the host taken from the log identifier, and (b) after DNS resolution. By default, deny loopback (127.0.0.0/8, ::1), private (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7) and link-local (169.254.0.0/16, fe80::/10) addresses. Opt-ins for development MAY be provided, but MUST be off by default.
  6. No localhost in production. Not issue requests to localhost, 127.0.0.0/8, ::1, or names resolving to them, except through a testing opt-in that is off by default.
  7. Response size cap. Enforce a maximum body size for both log and witness files, terminating the transfer if it is exceeded regardless of any declared Content-Length. The Content-Length header SHOULD be checked before the body is read, and its absence treated as grounds for a stricter cap or for rejection. A cap of 5 MiB is RECOMMENDED as a default.
  8. Operation timeout. Enforce a wall-clock timeout on the complete fetch-and-verify operation for a single resolution. A timeout of 30 seconds is RECOMMENDED as a default.

Security Considerations§

This section is non-normative.

This section follows the guidelines in [RFC3552]. It describes the threats to VH-Log and how the requirements in this specification address them. The requirements themselves are defined in the normative sections referenced below.

Threats and Attacks§

The following classes of attack apply to all log operations:

  • Eavesdropping — Log files, witness files and other log resources are served over HTTPS (see Publishing and Retrieving Log Resources). The verifiability of VH-Log means that tampering with the contents of individual log entries is detectable; TLS protects against passive observation and other network-based risks.

  • Replay attacks — A Resolver verifies the whole log, including monotonic progression of versions and times and the chain of entry hashes, and rejects any log that does not verify, as defined in Read (Resolve).

  • Message insertion, deletion, and modification — Each log entry and witness proof is integrity-protected by a cryptographic signature. Any change to a log entry causes verification to fail, as defined in Read (Resolve).

  • Truncation or withholding of log entries — An attacker (or misconfigured intermediary) could serve an older-but-valid prefix of the log, truncating newer entries and presenting a stale state. Mitigations include:

    • Resolver cache-and-compare: a Resolver that remembers the latest versionId it has seen for a log can detect a truncated copy (see Comparing Copies of a Log).
    • Witness verification: where Log Controllers use witnesses, Resolvers verify that every entry is properly witnessed, and ignore witness proofs for unpublished or truncated entries (see Verifying Witness Proofs During Resolution).
    • Multi-source resolution: a client can ask a Resolver to retrieve the log from watchers and compare the copies, using the checkWatchers, extraWatchers and minCopies resolution options. Copies that are behind are reported in the Resolution Result.
    • End-to-end TLS: while signatures detect per-entry tampering, TLS reduces opportunities for active truncation in transit.
  • Denial of Service (DoS) and amplification — A malicious or compromised log server could attempt to exhaust resolver resources by serving pathologically large log files, or by holding a connection open and streaming log entries indefinitely. This risk is most acute for resolvers that encounter previously-unseen logs on behalf of their clients.

    • The requirement that each entry’s versionTime be strictly greater than the previous one, and not in the future, constrains log length — a log file with implausibly rapid versionTime progression is likely malformed. However, this does not fully prevent the attack, as a patient attacker can pace log entry generation to match valid versionTime increments.

    • The size cap and timeout required in Retrieving Log Resources bound the cost of a single resolution. A declared Content-Length lets a resolver reject an oversized log before processing begins, but cannot itself be relied on; the presence of HTTP range request support (Accept-Ranges: bytes) is a useful signal that a server is serving a static resource rather than generated content. Resolvers are also advised to apply rate limits to resolution requests, particularly for previously-unseen logs, and concurrency limits on simultaneous resolution operations.

    • What counts as “excessively large” or “repeated” is best established through community or ecosystem governance. These mitigations are standard HTTP service hardening practices and are not unique to VH-Log.

  • Man-in-the-middle (MitM) — HTTPS and signature verification of log entries and witness proofs protect against undetected modification of individual entries. Withholding and truncation are addressed above. TLS also authenticates the server and reduces opportunities for active interference.

  • Conflicting parallel updates / split view — Multiple authorized updates from the same parent entry can produce divergent logs. This specification requires Log Controllers to publish only logs that extend their previously published log (Update), witnesses to approve at most one child of any entry (Witnessing a Log Entry Update), and the witness file to contain no conflicting proofs (The Witness Proofs File). Resolvers that remember the latest version they have seen can detect older branches, and Resolvers given more than one copy fail resolution when the copies diverge (Comparing Copies of a Log). Watchers report divergence across sources (Watchers). Because no other party can verify that a witness has followed these rules, detecting split views ultimately relies on comparing copies of the log from multiple sources, whether by watchers, which any party may run, or by a Resolver asked to check them. Watchers listed in the log are chosen by the Log Controller, so the extraWatchers resolution option, naming watchers the caller chooses, gives the stronger check against a Log Controller showing different logs to different parties.

  • Server-Side Request Forgery (SSRF) — A resolver acts as an HTTP client for a location derived from an untrusted log identifier, and, with the extraWatchers resolution option, for watcher URLs supplied by its caller. A Resolver offered as a service can refuse or limit extraWatchers. The transport checks in Retrieving Log Resources are standard SSRF defences. They are stated normatively because common implementations have been found vulnerable to at least one of: following redirects, accepting IP literals, case-sensitive percent-decoding, and checking values before rather than after decoding.

  • Other attacks — The VH-Log verification process mitigates downgrade attacks on cryptographic algorithms and prevents poisoning of log or witness files, since unauthorized changes fail signature verification. Verification does not, however, address availability. Operational measures such as Watchers and well-known web techniques can improve resilience.

Residual Risks§

Residual risks include:

  • Compromise of the web hosting infrastructure serving the log resources.
    • While this can impact access to the log file and associated files, it does not compromise the integrity of the log entries themselves, nor the verifiability of the log.
  • Compromise of Log Controller private keys.
    • A Log Controller can mitigate this risk through the use of pre-rotation keys, treating each revealed pre-rotation key as spent and never reusing it. Reuse of a revealed key is not invalid, but it reduces the containment that pre-rotation provides.
    • Other good security practices also help, such as using hardware security modules (HSMs) or secure enclaves for key storage, enforcing strong access controls, maintaining secure backups of critical keys, and performing regular key rotations.
  • Weaknesses in underlying cryptographic algorithms after deployment.
  • Misconfiguration of cache control or TTL values.
  • Implementation errors in resolvers or Log Controllers.
  • Resolvers operating in contexts where they may encounter large numbers of previously-unseen logs — such as open verification services — face elevated resource exhaustion risk. Such resolvers are advised to implement rate limiting and monitoring for abusive resolution patterns, with the ability to block offending sources.

Integrity Protection and Update Authentication§

All log operations (create, update, deactivate) are integrity-protected by the cryptographic verification of log entries. Update authentication is provided by verifying the Log Controller's proof(s) against the active updateKeys in the log parameters (see Authorized Keys).

Because a VH-Log log and its entries are self-certifying, they can be verified and trusted regardless of how they are retrieved — directly from the host, via a cache, through a CDN, from a watcher, or via a trusted resolver service. The verification process ensures authenticity and integrity independent of the transport channel.

Authentication Characteristics§

The authentication of log updates is based on possession of the private keys associated with the update and pre-rotation keys. The security of the log therefore depends on the strength of these keys, their secure storage, and the cryptographic algorithms used.

Unique Assignment of Logs§

In VH-Log, uniqueness of a log is based on the self-certifying identifier (SCID) generated at the inception of the log. The SCID is cryptographically bound to the Log Controller's keys and ensures that no two independently created logs can have the same identifier.

The location of the log (as defined by the specialisation) is used solely for discovery of the log file and associated files; it is not used for verification of log control (see The VH-Log File).

Endpoint Authentication§

Log resources are served over HTTPS with TLS server authentication (see Publishing and Retrieving Log Resources). While the verifiability of VH-Log ensures that any tampering with the contents of individual log entries is detectable, TLS provides additional protection against active network attacks (including truncation or withholding) and authenticates the server providing the log resources.

Network Topology§

VH-Log relies on web infrastructure and does not require peer-to-peer networking. CDN caches and load balancers in front of a log host can serve stale data; Publishing Log Resources requires Log that use them to prevent this.

Cryptographic Protection§

The following data is protected:

  • Log entries — Signed by the Log Controller's keys.
  • Witness proofs — Signed by witness keys.

These signatures provide integrity and update authentication but not confidentiality; log entries are public. Private keys and other secret material must never be exposed in the log (see Authorized Keys).

Signature Implementation§

VH-Log uses standard Data Integrity proof mechanisms for signing log entries and witness proofs, as defined in the cryptographic suite used. The cryptosuites permitted for each logVersion are listed in VH-Log Parameters.

Cross-Origin Resource Sharing (CORS)§

To support resolution by client applications running in a web browser, the log file must be accessible from any origin. Publishing Log Resources requires the Access-Control-Allow-Origin: * header for this reason.

Post Quantum Attacks§

VH-Log’s Key Pre-Rotation approach provides enough flexibility for “post-quantum safety.” Implementers are advised to consult current guidance on pre-rotation keys and post-quantum cryptography.

Implementation Hygiene§

The following practices are not unique to VH-Log but represent recurring failure points observed across implementations. Implementers are advised to treat these as baseline hygiene.

  • Dependency currency. Keep cryptographic and HTTP dependencies on supported, patched versions. Run a vulnerability scanner on every build.
  • Filesystem permissions. Create private keys and secret-bearing configuration files with owner-only permissions (e.g., 0600 on POSIX). Do not read secret material from the current working directory or other untrusted locations by default.
  • CLI tools. Do not print private keys, mnemonics, or other long-lived secrets to stdout/stderr or shell history. Where display is necessary, prompt before printing and offer a file-output alternative with restrictive permissions.
  • HTTPS certificate validation. Never disable certificate validation by default (this is required for resolvers in Retrieving Log Resources).
  • Error messages. Avoid including internal stack traces, file paths, or library versions in error details returned to clients.

Resolver Validation Checklist§

The following checklist maps normative resolver requirements to concrete validation points, and is intended to assist conformance test suite authors and implementers auditing their own resolver implementations. It does not restate or replace the normative requirements in this specification.

Transport: HTTPS only; no auto 3xx; reject IP literals before & after percent-decoding; reject private/loopback/link-local DNS resolutions in production; normalize percent-encoding case; validate path segments after decoding (., .., /, \, NUL, leading/trailing whitespace); enforce max response size with Content-Length first; enforce a wall-clock timeout.

Log structure: unbroken 1, 2, 3, ... version sequence; strictly increasing UTC ISO8601 versionTime; every versionTime ≤ now (bounded skew); entry hash chain verified for every entry; logVersion is an explicitly supported value (never silently downgraded).

SCID & identity: first entry’s parameters.scid is the genesis self-hash; SCID never changes.

Keys & proofs: every proof has type: DataIntegrityProof, the cryptosuite and proofPurpose required by the active logVersion; verificationMethod key in active updateKeys; under pre-rotation, updateKeys explicit in every entry and every key hashes to a value in previous nextKeyHashes.

Witnesses: threshold is positive integer ≤ count of distinct witnesses[].id; all witnesses[].id distinct; threshold met by counting verified proofs from distinct witness identifiers, not total proof count; each accepted proof’s versionId corresponds to an entry in the log file being verified; proofs verified using the method defined by the specialisation.

Failure modes: unknown parameter values, malformed witness, hash algorithm mismatch, and cryptosuite mismatch all fail resolution — never silently coerced.

Privacy Considerations§

This section is non-normative.

This section addresses privacy considerations in alignment with [RFC6973] Section 5.

Surveillance§

VH-Log publishes logs to publicly accessible HTTPS endpoints. While the contents of the log are generally intended to be public, the timing, frequency, and correlation of updates can be observed and may reveal operational patterns or associations.

Resolution of a log also exposes the resolver’s network activity to DNS providers and web servers, which could be used for tracking. Controllers and resolvers can use privacy-enhancing technologies such as VPNs, TOR, or trusted universal resolver services to reduce this risk.

Stored Data Compromise§

Log data is stored on web servers. A compromise of the hosting infrastructure could allow tampering with log resources. HTTPS and cryptographic signatures protect integrity, but confidentiality is not provided.

Correlation§

The use of a static log identifier and public log entries can enable correlation of activities over time. Log Controllers are advised to avoid embedding personal identifiers or unnecessary information in state objects.

Identification§

Logs are public and can be linked to real-world identities through the hosting location. Entities that require anonymity need to consider whether VH-Log is appropriate for their use case.

Right to Erasure§

A Log Controller can delete published data as described in Deactivate, but watchers monitoring a removed log are expected to keep caching its last known state. Complete erasure therefore depends on watcher behaviour, and the process for it is best defined by the governance of the ecosystem — for example, through the watcher deletion operation in Watcher HTTP API Operations.

Secondary Use§

Information published in the log may be repurposed by third parties. Log are advised to minimise the publication of data that could be used for purposes beyond the intended use.

Disclosure§

All data in the log is publicly accessible, so sensitive data does not belong in it.

Exclusion§

VH-Log does not require controller ownership of any specific infrastructure. The specialisation defines the hosting location, and controllers may publish logs on web-hosting platforms that serve static files over HTTPS. This reduces barriers to participation.

Residual exclusion risks remain: access to such platforms typically requires an account and compliance with provider terms of service; platforms might impose geoblocking, payment requirements, or content restrictions; and accounts can be suspended. Log Controllers are advised to keep the ability to republish or mirror log resources under alternative hosts (including using watchers), and to document a transition plan so that participants are not locked out if a hosting provider becomes unavailable. The verifiable history of the log ensures that it can be verified regardless of the source.

Definitions§

base58btc
Applies [DRAFT-MSPORNY-BASE58-03] to convert data to a base58 encoding. Used in VH-Log for encoding hashes for SCIDs and entry hashes.
Data Integrity
W3C Data Integrity is a specification of mechanisms for ensuring the authenticity and integrity of structured digital documents using cryptography, such as digital signatures and other digital mathematical proofs.
Entry Hash
A VH-Log entry hash is a hash generated using a formally defined process over the input data of a log entry, excluding the Data Integrity proof. The input data includes the versionId of the predecessor entry, ensuring that all entries are cryptographically chained together. The generated entry hash is included in the versionId of the log entry and MUST be verified by a Resolver.
ISO8601
A date/time expressed using the ISO8601 Standard.
JSON Canonicalization Scheme
[RFC8785] defines a method for canonicalizing a JSON structure such that it is suitable for verifiable hashing or signing.
JSON Lines
A file of JSON Lines, as described on the site https://jsonlines.org/. In short, JSONL is lines of JSON with whitespace removed and separated by a newline, convenient for handling streaming JSON data or log files.
Log
A VH-Log log is an append-only JSON Lines file containing one log entry per line, recording the full history of a versioned state object. The log is identified by the SCID derived from its genesis entry.
Log Controller
The entity that controls (creates, updates, and deactivates) a given log. The Log Controller holds the private keys corresponding to the authorised update keys listed in the log's parameters.
Log Entry
A log entry is a JSON object in a VH-Log log that records one version of the state object. Each entry contains a versionId, versionTime, parameters, state, and proof. The genesis entry establishes the initial state and the SCID; subsequent entries record updates.
logVersion
The VH-Log version parameter. It identifies the version of the specification in use and defines the permitted cryptographic algorithms (hash algorithm and Data Integrity cryptosuite) for the current and subsequent log entries. Required in the first log entry. A specialisation may designate its own parameter in its place, such as the method parameter of did:webvh and did:vh, whose values identify a version of the specialisation and the VH-Log version it implies. Where this specification refers to logVersion, it means the designated parameter if there is one. See VH-Log Parameters.
multibase
A specification for encoding binary data as a string using a prefix that indicates the encoding.
multikey
A verification method that encodes key types into a single binary stream that is then encoded as a multibase value.
multihash
Per the [MULTIFORMATS], multihash is a specification for differentiating instances of hashes. Software creating a hash prefixes data to the hash indicating the algorithm used and the length of the hash, so that software receiving the hash knows how to verify it. Although multihash supports many hash algorithms, for interoperability, Log Controllers MUST only use the hash algorithms defined as permitted by the active logVersion.
next key hashes
The nextKeyHashes parameter: an array of hashes of multikey formatted public keys that the Log Controller commits to using as update keys in the next log entry, enabling the Pre-Rotation feature.
parameters
VH-Log parameters are a defined set of configurations in each log entry that control how the Log Controller generated the entry and how the Resolver must process the log. Parameters accumulate across entries: an entry that omits a parameter inherits the previously active value. The use of parameters allows for the controlled evolution of log handling over the lifetime of the log.
Pre-Rotation
A technique for a controller of a cryptographic key to commit to the public key it will rotate to next, without exposing that actual public key. It protects against an attacker who gains knowledge of the current private key from being able to rotate to a new key known only to the attacker.
Resolver
A party that retrieves and processes a VH-Log log to produce the current or historical state object. The Resolver verifies the cryptographic chain, Data Integrity proofs, and SCID of the log.
self-certifying identifier
An identifier derived from the initial content of the log such that an attacker could not create a different log with the same identifier. The input for a VH-Log SCID is the genesis log entry with the placeholder {SCID} wherever the SCID is to be placed.
specialisation
A specification that uses VH-Log as its foundation by defining the state object type, the log’s location and resource naming conventions, and any additional constraints or features. A specialisation MUST NOT redefine or contradict the core VH-Log parameters and processing rules.
state
The versioned object recorded in each log entry. The type and schema of the state object is defined by the specialisation. In VH-Log itself, state is an arbitrary JSON object.
threshold
An algorithm that defines when a sufficient number of witnesses have submitted valid Data Integrity proofs for a log entry such that it is approved and can be published. Details are in the Witness Threshold Algorithm section.
update key
Multikey-formatted public keys whose corresponding private keys are authorised to sign log entries that update the log. The active set of update keys is defined by the updateKeys parameter.
versionId
The identifier of a log entry, consisting of the entry’s version number (a positive integer starting at 1), a literal dash -, and the entry hash of the entry. The versionId links each entry to its predecessor and is used to identify specific versions during resolution.
watcher
Watchers are entities that monitor logs for changes or updates on behalf of their clients. Watchers maintain a historical cache of log versions and verify that the Log Controller is consistently following the prescribed evolution process. By ensuring integrity and traceability, watchers help foster trust among clients who rely on up-to-date and authentic log information. Watchers provide endpoints for retrieving log information and receiving webhooks notifying the watcher about updates and deletion requests. Any party may run a watcher; it need not be listed in the log.
webhook
A webhook is a mechanism that enables real-time communication between systems by sending HTTP callbacks (typically POST requests) to a specified URL when an event occurs. Webhooks are commonly used for event-driven integrations and automation. Although webhooks are an implementation pattern rather than a formal standard, best practices are documented in [RFC8030].
witness
Witnesses are participants in the process of approving a new log entry before it is published. A witness receives from the Log Controller a log entry ready for publication, verifies it according to this specification, and approves it according to the governance of the ecosystem. If the verification and approval are positive, the witness returns a Data Integrity proof attesting to that result. The identity format and key type of witnesses is defined by the specialisation.

References§

DI-EDDSA-V1.0
Data Integrity EdDSA Cryptosuites v1.0. Dave Longley; Manu Sporny; 2024-12-08. Status: W3C Technical Recommendation.
DRAFT-MSPORNY-BASE58-03
The Base58 Encoding Scheme. S. Nakamoto; Manu Sporny; 2021-03-31. Status: Internet Draft.
MULTIFORMATS
Multiformats. Juan Benet; Manu Sporny; 2024-02-21. Status: Internet Draft.
RFC2119
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner; 1997-03. Status: Best Current Practice.
RFC2181
Clarifications to the DNS Specification. R. Elz; R. Bush; 1997-07. Status: Proposed Standard.
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.
RFC6234
US Secure Hash Algorithms (SHA and SHA-based HMAC and HKDF). D. Eastlake 3rd; T. Hansen; 2011-05. Status: Informational.
RFC6973
Privacy Considerations for Internet Protocols. A. Cooper; H. Tschofenig; B. Aboba; J. Peterson; J. Morris; M. Hansen; R. Smith; 2013-07. Status: Informational.
RFC8030
Generic Event Delivery Using HTTP Push. M. Thomson; E. Damaggio; B. Raymor, Ed.; 2016-12. Status: Proposed Standard.
RFC8174
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba; 2017-05. Status: Best Current Practice.
RFC8785
JSON Canonicalization Scheme (JCS). A. Rundgren; B. Jordan; S. Erdtman; 2020-06. Status: Informational.
RFC9110
HTTP Semantics. R. Fielding, Ed.; M. Nottingham, Ed.; J. Reschke, Ed.; 2022-06. Status: Internet Standard.

VH-Log Version Changelog§

The following lists the substantive changes in each version of the specification.

  • Version 0.1
    • Initial pre-draft. Extracted and generalised the log mechanism from the did:webvh v1.0 specification, removing DID-specific content and making the following generalisations:
      • state is defined as an arbitrary JSON object rather than a DIDDoc.
      • The log file resource name (vh-log.jsonl) and witness file resource name are generalised; specialisations may define their own names.
      • The did:webvh method parameter is generalised as the version parameter, logVersion (vh-log:1.0), which defines the spec version and permitted cryptographic algorithms. A specialisation may designate its own parameter in its place, as did:webvh and did:vh do with method.
      • Witness identity and key format are specialisation-defined rather than requiring did:key DIDs.
      • The DID-to-HTTPS transformation, DID URL resolution, /whois, and parallel did:web publishing are removed as DID-specific concerns.
      • Portability is removed as a DID-specific concern.
      • Specialisation-defined parameters are explicitly supported.