This specification describes a mechanism for issuers of verifiable credentials to publish indexed lists of compact cryptographic witnesses. Verifiers can use these lists to confirm that a [=verifiable credential=] was genuinely issued by the claimed issuer — as distinct from a credential forged by an attacker who has compromised the issuer's signing key. Because the witness list is itself a verifiable credential, it can be signed with a different algorithm from the protected credentials, including post-quantum signature schemes. This makes the mechanism particularly valuable in scenarios where a conventional signing key has been compromised by a quantum-capable adversary.

This work is complementary to the [[[VC-DI-QUANTUM-RESISTANT]]] work and is motivated by the same threat: compromise of a conventional signing key by a cryptographically relevant quantum computer. Those cryptosuites let issuers secure new proofs with post-quantum signatures; this specification lets an issuer establish post-quantum-backed authenticity for credentials that were, or will be, signed with conventional quantum-vulnerable algorithms, by publishing a witness list that is itself signed with a post-quantum scheme.

The specification defines a data model and algorithms for three usage modes: an implicit mode that piggybacks on an existing [[[VC-BITSTRING-STATUS-LIST]]] without modifying the protected credential; an explicit mode that adds a credentialStatus entry to the protected credential; and a standalone mode for use cases that define their own index allocation.

Introduction

[=Verifiable Credentials=] are often secured with digital signatures over their contents. If the issuer's private signing key is compromised — whether through an infrastructure breach or through advances in cryptanalysis such as the development of a cryptographically relevant quantum computer — an attacker can forge credentials that are indistinguishable from those created by the issuing party using standard signature verification alone.

This specification provides a mechanism similar to [[[VC-BITSTRING-STATUS-LIST]]] that allows an issuer to assert what subset of the credentials issued with their private key material they actually issued. This takes the form of a witness list that the issuer publishes alongside their credentials. For each credential issued, the issuer computes a short cryptographic witness derived from a random seed and the credential's position in the list. A verifier who fetches the list can confirm that the witness of the credential they are verifying matches the witness published by the issuer.

The witness list is itself a verifiable credential and may be signed using a different digital signature algorithm than the credentials on the list. Importantly, the witness list may be signed with a quantum-resistant digital signature, such as those in the [[[VC-DI-QUANTUM-RESISTANT]]] specification. In this way, an issuer can establish quantum-resistant authenticity for credentials issued with conventional and non-quantum-resistant digital signatures.

This specification is complementary to the [[[VC-DI-QUANTUM-RESISTANT]]]. Both are responses to the same threat — compromise of conventional signing keys, in particular by a cryptographically relevant quantum computer. Quantum-resistant cryptosuites address the threat going forward, producing proofs that remain unforgeable in a post-quantum setting. A witness list addresses the installed base and the transition period: it lets an issuer vouch, under a post-quantum signature, for credentials that were already issued — or that must continue to be issued — under conventional signatures, without re-issuing them. The two mechanisms are intended to be used together.

Design Goals

Usage Overview

At [=verifiable credential=] issuance time, an issuer uses the credential data to create a cryptographic witness. A set of these witnesses are combined with a random seed, packed into an array, and published together as a witness list credential. This credential is signed — optionally with a post-quantum algorithm — and published for verifiers to access.

At verification time, a verifier obtains the witness list credential, re-derives the witness for the credential they are verifying using the published random seed and the credential data, and compares it to the value stored at the credential's witness index. A mismatch indicates that the credential was not issued by the claimed party.

A conforming document is any concrete expression of the data model that complies with the normative requirements in Section . A conforming processor is any algorithm realized as software and/or hardware that generates or consumes a conforming document according to the normative requirements in Sections and .

Conforming processors MUST produce errors when non-conforming documents are consumed.

Terminology

The following terms are used throughout this document. Terms defined in [[VC-DATA-MODEL-2.0]] are used with the meanings defined there.

witness
A fixed-length sequence of pseudorandom bits derived from a witness seed and a credential hash by computing SHA-256(seedBytes || credentialHash) and taking the most significant witness length bits of the output.
witness list
An ordered sequence of witnesses packed into a contiguous bit array and encoded as a multibase string.
witness list credential
A verifiable credential of type WitnessListCredential whose credentialSubject is of type WitnessList and contains the witness list properties.
witness list entry
An object of type WitnessListEntry that MAY appear in the credentialStatus of a protected verifiable credential in explicit mode, linking the credential to its position in a witness list credential, which it references through an explicit witnessListCredential property.
witness seed
A UUID v4 [[RFC4122]] value that serves as the secret salt for generating all witnesses in a list. The seed MUST be generated using a cryptographically secure random number generator.
witness index
A positive integer (1-based) identifying a credential's position in the witness list.
witness length
The number of bits in each witness. The default value is 128. Larger values decrease the probability of a false positive match; see .
witness count
The total number of witnesses in a witness list.
encoded witnesses
The [[[MULTIBASE]]]-encoded (base64url, prefix u) packed bit array of all witnesses in a witness list, typed as https://w3id.org/security#multibase in the JSON-LD context.
implicit mode
A usage mode in which the protected credential contains no reference to the witness list; the list location and witness index are inferred from the credential's existing credentialStatus entry, and the status list index is reused as the witness index.
explicit mode
A usage mode in which the protected credential contains a witness list entry in its credentialStatus that directly references the witness list credential and witness index.
standalone mode
A usage mode in which the witness index allocation is defined by the application or use case, independent of any status list infrastructure.

Data Model

Witness List Credential

A witness list credential is a verifiable credential whose credentialSubject is of type WitnessList and carries the witness list properties (witnessSeed, witnessLength, witnessCount, and encodedWitnesses) directly. Issuers SHOULD use a separate key pair — and MAY use a post-quantum signature scheme — to sign the witness list credential, since the protection it provides is only meaningful if it can be verified after the ordinary signing key has been compromised.

The type array of the credential MUST include WitnessListCredential.

{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://w3id.org/vc-forgery-defense/v1rc1"
  ],
  "id": "https://issuer.example/witnesses/1",
  "type": ["VerifiableCredential", "WitnessListCredential"],
  "issuer": "did:example:issuer",
  "validFrom": "2024-01-15T00:00:00Z",
  "credentialSubject": {
    "type": "WitnessList",
    "witnessSeed": "26ea4078-968d-4d98-aba7-695610c0dfb6",
    "witnessLength": 128,
    "witnessCount": 131072,
    "encodedWitnesses": "uQYF16SSSbR_LrMZFbFvbGkn8B7ggEZtov3KVL..."
  },
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-rdfc-2022",
    "comment": "MAY use a post-quantum cryptosuite here",
    "verificationMethod": "did:example:issuer#pq-key-1",
    "proofPurpose": "assertionMethod",
    "proofValue": "z..."
  }
}
    

When WitnessListCredentials are distributed by an issuer via a space-constrained medium (such as over the web for large credentials), it is RECOMMENDED that they are CBOR-LD encoded and transmitted as bytes.

Witness List Properties

The credentialSubject of a WitnessList MUST contain the following properties directly.

witnessSeed
REQUIRED. A UUID v4 string [[RFC4122]] used as the witness seed. Implementations MUST generate a new witnessSeed value each time they issue a WitnessListCredential.
witnessLength
REQUIRED. A positive integer specifying the number of bits per witness. MUST be at least 32. SHOULD be at least 128. Default is 128.
witnessCount
REQUIRED. A positive integer specifying the total number of witnesses in the list, determining the expected length of the encoded witnesses value. Noting that group privacy depends on sufficiently large witnessCount, implementations MUST choose a value greater than or equal to 131072.
encodedWitnesses
REQUIRED. A [[[MULTIBASE]]]-encoded string (base64url, prefix u), typed as https://w3id.org/security#multibase, representing the packed bit array of all witnesses. The witness for witness index i occupies bits (i − 1) × witnessLength through i × witnessLength − 1 (inclusive) in most-significant-bit-first order.

Witness List Entry

When used in explicit mode, a protected verifiable credential MAY include a witness list entry in its credentialStatus array (or as its sole credentialStatus value).

The type of the entry MUST be WitnessListEntry. Following the pattern of [[[VC-BITSTRING-STATUS-LIST]]]'s statusListCredential property, the entry references its witness list credential through an explicit witnessListCredential property whose value is the URL of that credential. The entry's id, if present, is an OPTIONAL unique identifier for the entry and is not used to locate the list.

witnessListCredential
REQUIRED. A URL that dereferences to the witness list credential for this credential. This property is the analogue of statusListCredential in [[[VC-BITSTRING-STATUS-LIST]]].
witnessIndex
REQUIRED. A positive integer (1-based) specifying the position of this credential's witness in the referenced list. MUST be greater than 0 and MUST NOT exceed witnessCount.
{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://w3id.org/vc-forgery-defense/v1rc1"
  ],
  "id": "https://issuer.example/credentials/1234",
  "type": ["VerifiableCredential", "ExampleDegreeCredential"],
  "issuer": "did:example:issuer",
  "validFrom": "2024-01-15T00:00:00Z",
  "credentialStatus": [
    {
      "id": "https://issuer.example/status/1#94567",
      "type": "BitstringStatusListEntry",
      "statusPurpose": "revocation",
      "statusListIndex": "94567",
      "statusListCredential": "https://issuer.example/status/1"
    },
    {
      "type": "WitnessListEntry",
      "witnessListCredential": "https://issuer.example/witnesses/1",
      "witnessIndex": 94567
    }
  ],
  "credentialSubject": {
    "id": "did:example:holder",
    "degree": {"type": "BachelorDegree", "name": "Computer Science"}
  },
  "proof": { ... }
}
    

Algorithms

Generate Witness List

The following algorithm generates a witness list for a set of N credential hashes. Inputs: an array of N hashes, in order of their desired witnessIndex values, and the witness length b (default 128). Outputs: a witness seed and encoded witnesses.

  1. Generate a UUID v4 value seed using a cryptographically secure random number generator [[RFC4122]].
  2. Let seedBytes be the 16-byte representation of seed obtained by decoding its hex digits (stripping hyphens) as a big-endian byte sequence.
  3. Let data be a zeroed bit array of N × b bits.
  4. For each integer i from 0 to N-1, inclusive:
    1. Let msg be the ith credential hash from the input array.
    2. Let hash be the output of SHA-256 [[FIPS-180-4]] applied to the concatenation seedBytes || msg.
    3. Let w be the most significant b bits of hash.
    4. Write w into data at bit offset (i − 1) × b, most-significant-bit first.
  5. Let encodedWitnesses be the multibase-base64url encoding of data, prepended with the prefix character u [[MULTIBASE]].
  6. Return seed and encodedWitnesses.

Generate Witness List Credential

The following algorithm wraps the output of Generate Witness List into a signed verifiable credential.

  1. Run Generate Witness List with inputs N and b, producing seed and encodedWitnesses.
  2. Construct an unsigned credential with:
    • type including WitnessListCredential
    • credentialSubject.type set to WitnessList
    • credentialSubject.witnessSeed set to seed
    • credentialSubject.witnessLength set to b
    • credentialSubject.witnessCount set to N
    • credentialSubject.encodedWitnesses set to encodedWitnesses
  3. Sign the credential. If protection against key compromise — including quantum key compromise — is required, the issuer SHOULD use a post-quantum signature scheme and a key pair distinct from those used to sign the protected credentials.
  4. Return the signed credential.

Verify a Credential Witness

The following algorithm verifies that a given credential's witness is valid. Inputs: the resolved witness list credential, a credential hash, and the witness index i.

  1. Verify all proofs on the witness list credential. If any proof fails, return an error.
  2. Let subject be the witness list credential's credentialSubject.
  3. Let seed be subject.witnessSeed.
  4. Let b be subject.witnessLength.
  5. Let N be subject.witnessCount.
  6. If i < 1 or i > N, return an error indicating the index is out of range.
  7. Let encodedWitnesses be subject.encodedWitnesses.
  8. Verify that encodedWitnesses begins with the character u. If not, return an error.
  9. Let data be the result of base64url-decoding the substring of encodedWitnesses after the first character.
  10. Extract bits at offset (i − 1) × b through i × b − 1 from data, most-significant-bit first. Let this be storedW.
  11. Let seedBytes be the 16-byte representation of seed obtained by decoding its hex digits (stripping hyphens) as a big-endian byte sequence.
  12. Let msg be the input credential hash.
  13. Let hash be SHA-256 [[FIPS-180-4]] of seedBytes || msg.
  14. Let computedW be the most significant b bits of hash.
  15. If storedW equals computedW, return valid. Otherwise return invalid.

Usage Modes

Implicit Mode

In implicit mode, the protected credential is unmodified. No reference to the witness list appears in the credential itself. The verifier locates the witness list using out-of-band information — typically derived from the credential's existing credentialStatus entry — and reuses the status list index as the witness index.

This mode REQUIRES that:

// Protected credential has:
//   credentialStatus.statusListCredential = "https://issuer.example/status/1"
//   credentialStatus.statusListIndex      = "94567"

// Verifier discovers witness list URL from issuer metadata:
// GET https://issuer.example/.well-known/witness-lists
// -> { "https://issuer.example/status/1":
//      "https://issuer.example/witnesses/1" }

// Witness index = 94567 (same as status list index)
// Run: Verify a Credential Witness with i = 94567
    

Explicit Mode

In explicit mode, the protected credential contains a witness list entry in its credentialStatus. The entry's witnessListCredential property gives the witness list credential URL, and witnessIndex specifies the 1-based position in the list. No discovery mechanism is required.

Explicit mode MAY coexist with other credentialStatus entries (such as a BitstringStatusListEntry for revocation). If both are present, witnessIndex need not equal the status list index.

Verification in Explicit Mode

  1. Locate a credentialStatus entry of type WitnessListEntry.
  2. Let the witness list credential URL be the value of the entry's witnessListCredential property.
  3. Dereference that URL to obtain the witness list credential.
  4. Run Verify a Credential Witness using the value of witnessIndex as i.

Standalone Mode

In standalone mode, the witness list is used independently of any status list infrastructure. The allocation of witness indices to credentials is defined by the specific application or use case. The witness list credential MAY be referenced from credentials, application-layer records, or external registries — the reference mechanism is out of scope for this specification.

Security Considerations

Quantum Key Compromise

A quantum-capable adversary can recover the private key corresponding to a conventional (e.g., ECDSA) public key. If the issuer has signed the witness list credential with a post-quantum signature scheme, a verifier can still trust the witness list even after the signing key for the credentials on it has been broken, provided the post-quantum key remains secure.

Issuers SHOULD sign the witness list credential with a post-quantum signature scheme whenever the credentials it covers are of significant value or long validity.

Seed Confidentiality

The witness seed appears in the witness list credential's credentialSubject and is visible to any party who obtains the witness list credential. This is by design — verifiers need the seed to recompute witnesses. Issuers who wish to restrict verification to a specific set of verifiers SHOULD limit distribution of the witness list credential accordingly.

False Positive Rate

A forged credential at index i passes witness verification with probability 2b, where b is the witness length. At the default of b = 128, this probability is approximately 2.9 × 10−39.

Implementations MUST NOT accept values of b less than 32. A minimum of 64 is RECOMMENDED for credentials of significant value.

Witnesses SHOULD be computed in a way that is generally collision resistant, but they MUST be computed in a way that is second-preimage resistant.

Witness List Integrity

Verifiers MUST verify the proof on the witness list credential before trusting its contents. Verifiers SHOULD cache resolved witness list credentials for their validity period to reduce network exposure and denial-of-service risk.

Issuer Information Leaks

Using witnessCount exactly sized to how many issued [=verifiable credentials=] the list covers leaks information about issuer processes in two ways:

To mitigate the first leak, issuers SHOULD use fixed witnessCount values, such that across all witness lists the total number of witnesses does not change, with arbitrary data witness placeholders being replaced by witnesses that correspond to VCs over time.

This is sufficient to mitigate the first leak - however, an attacker still could track how many witnesses are "real" and how many VCs are being issued by tracking what witness values change over time. To address this, issuers MUST regenerate the random witness seed each time they update a WitnessListCredential.

List Replacement and Rollback

An attacker who can replace the published witness list credential with a modified version can bypass the anti-forgery mechanism. Issuers MUST publish witness list credentials over integrity-protected channels and SHOULD use short validity periods combined with frequent re-issuance.

Issuer Repudiation via Witness Replacement

The issuer is the sole author of the witness list credential, and is REQUIRED to regenerate the witness seed each time the list is updated (see ). An issuer can therefore repudiate a credential it previously issued by publishing a new witness list credential in which that credential's witness is absent or altered: a verifier who recomputes the witness against the latest list obtains a mismatch and rejects an otherwise genuine credential. Unlike forgery, this behavior is available to the legitimate issuer and cannot be prevented by the mechanism alone, because every published list is a validly signed credential.

Relying parties that require evidence of issuance to survive later repudiation SHOULD retain (cache or archive) the witness list credentials they relied upon, together with their proofs and validity periods. Because each witness list credential is itself a signed verifiable credential with a validFrom value, a credential that verified against a list published before the repudiation can still be shown to have been genuine as of that earlier list — the cached, signed list is durable evidence that the issuer attested to the credential at that time. Independent archival, notarization, or transparency-log witnessing of published lists strengthens this guarantee by making the issuer's published history non-repudiable.

Privacy Considerations

Group Privacy and List Size

The witnessCount value reveals the maximum number of credentials in a batch. Issuers MUST use large set sizes (at least 131072 entries) so that individual holders cannot be identified by their position in the list. All slots in such a set must be filled, whether or not the hash used to create the witness corresponds to a verifiable credential.

Correlation Risk

Fetching the witness list credential creates a network request that may reveal to the host that verification is occurring. Verifiers SHOULD cache witness list credentials and MAY use privacy-preserving network transports when fetching lists. Issuers SHOULD serve witness lists through CDNs or content-addressed systems to reduce correlation capability.

JSON-LD Context

The examples in this document refer to the v1rc1 version of a `vc-forgery-defense` JSON-LD context. This context is reproduced below for reference.

{
  "@context": {
    "@protected": true,
    "id":   "@id",
    "type": "@type",

    "WitnessListCredential": "https://w3id.org/vc-forgery-defense#WitnessListCredential",
    "WitnessList":           "https://w3id.org/vc-forgery-defense#WitnessList",
    "WitnessListEntry":      "https://w3id.org/vc-forgery-defense#WitnessListEntry",

    "witnessSeed": {
      "@id": "https://w3id.org/vc-forgery-defense#witnessSeed"
    },
    "encodedWitnesses": {
      "@id":   "https://w3id.org/vc-forgery-defense#encodedWitnesses",
      "@type": "https://w3id.org/security#multibase"
    },
    "witnessLength": {
      "@id":   "https://w3id.org/vc-forgery-defense#witnessLength",
      "@type": "http://www.w3.org/2001/XMLSchema#integer"
    },
    "witnessCount": {
      "@id":   "https://w3id.org/vc-forgery-defense#witnessCount",
      "@type": "http://www.w3.org/2001/XMLSchema#integer"
    },
    "witnessIndex": {
      "@id":   "https://w3id.org/vc-forgery-defense#witnessIndex",
      "@type": "http://www.w3.org/2001/XMLSchema#integer"
    },
    "witnessListCredential": {
      "@id":   "https://w3id.org/vc-forgery-defense#witnessListCredential",
      "@type": "@id"
    }
  }
}