Credential Revocation: Status Lists, Herd Privacy, and the Correlation Problem

Aug 28, 2026

Introduction

An earlier post covered issuing Verifiable Credentials against Bitcoin-anchored DIDs. Issuance is the part that demos well. The part that determines whether a credential system is usable in production is what happens when a credential needs to stop being valid: an employee leaves, a license is suspended, a certification lapses, a credential is discovered to have been issued in error.

Revocation is where a lot of otherwise thoughtful identity architectures quietly fall apart, usually because the revocation mechanism reintroduces exactly the centralization and correlation the rest of the system was designed to avoid. This post works through the status list mechanisms in use, their privacy properties, and the alternatives.

What Revocation Is Not

Two clarifications first, because both misconceptions are common.

Deactivating the issuer's DID does not revoke its credentials. The deactivation discussion covered the durability half: a credential signed by a key that was valid at the time can still verify afterward. That holds provided the verifier resolves the issuer's DID at that historical version and can independently establish when the signature was made. DID Core states both as necessary conditions, and the second is the one people forget. Without a signing time the verifier can trust (a separate anchor, a timestamping service, out-of-band evidence), historical resolution stops being a durability property and becomes a backdating vector for whoever holds the retired key. Where the conditions do hold, the behavior is deliberate: it is what allows a credential to outlive the organization that issued it. If you want credentials to stop verifying, deactivation is not the lever.

Rotating the signing key does not revoke them either, for the same reason and subject to the same conditions. Historical resolution means historical signatures keep verifying. A verifier that treats key rotation as invalidating everything signed by the old key has effectively made every rotation a mass revocation event, which is a defensible fallback where signing times cannot be established and a blunt instrument everywhere else. That fallback is not just defensible: where the state of a DID at signing time cannot be reliably determined, DID Core calls disallowing any historical consideration the only safe course.

Revocation is a separate mechanism that has to be designed in from the start. A credential that carries no status information cannot be revoked later, no matter what the issuer does to their DID.

Bitstring Status List

Which status mechanism you want depends on your credential format. In the W3C Verifiable Credentials world it is Bitstring Status List, which grew out of the earlier StatusList2021 work and became a W3C Recommendation in May 2025. In the JOSE and COSE world (SD-JWT VC, ISO mdoc) it is IETF Token Status List, which as of August 2026 sits in the RFC Editor queue with an intended status of Proposed Standard. The core construction is the same in both: a compressed bitstring, fetched whole, sized for herd privacy. The differences are in encoding and in how a credential references its entry. This post works through the W3C version, and the privacy analysis carries over to the IETF one almost unchanged.

The construction is simple and clever. The issuer maintains a large bitstring where each position corresponds to one issued credential. A bit set to 1 means revoked; 0 means not revoked. The spec then says to "generate a compressed bitstring by using the GZIP compression algorithm on the bitstring and then Multibase-encode the result using base64url (with no padding)," and the result is published inside a Verifiable Credential of its own, signed by the issuer.

Each issued credential carries a credentialStatus entry pointing at the list and its own index:

{
  "credentialStatus": {
    "id": "https://issuer.example/status/3#94567",
    "type": "BitstringStatusListEntry",
    "statusPurpose": "revocation",
    "statusListIndex": "94567",
    "statusListCredential": "https://issuer.example/status/3"
  }
}

A verifier fetches the status list credential, verifies the issuer's signature on it, decompresses the bitstring, and reads bit 94567.

Two properties make this work at scale. Compression is doing a lot of heavy lifting: a bitstring that is almost entirely zeros compresses extremely well, so a list covering hundreds of thousands of credentials with a small number revoked transmits as a few kilobytes. And the check is a single fetch and a bit lookup, with no per-credential request to the issuer.

The specification defines four statusPurpose values, not one: revocation, suspension, refresh, and message. Suspension is reversible and is genuinely useful, and it is often what an organization actually means: a license under review is not the same as a license permanently withdrawn, and collapsing them loses information the verifier may need. message carries richer custom state, and refresh signals that an updated credential is available.

Herd Privacy and Why the List Is Big

The status list specification sets a minimum size of 131,072 entries, "unless a different lower bound is established by a specific ecosystem specification." That number is not arbitrary and it is not about efficiency. It is a privacy parameter, and the spec says so: "This size ensures an adequate amount of group privacy in the average case. If better group privacy is required, the bitstring can be made larger."

The reasoning: when a verifier fetches a status list, the issuer's server learns that someone is checking some credential on that list. It does not learn which one, because the verifier downloads the entire list and does the lookup locally. The credential holder is hidden in the crowd of everyone on the same list. This is herd privacy, and the size of the herd is the size of the list.

Which means the guarantee degrades exactly as you would expect when the list is small. An issuer who creates a separate status list per credential has built a per-credential phone-home beacon with extra steps: every fetch of that list identifies the credential unambiguously. An issuer with 40 customers on a list has given each of them a 1-in-40 anonymity set.

The minimum does not stop this. It is a floor on the length of the bitstring, not on the number of credentials assigned slots on it, so a per-credential list padded out to 131,072 bits passes the Validate Algorithm unchallenged: the only length check raises an error when the expanded bitstring divided by the status size falls below the minimum, and a mostly empty list clears that bar. The spec treats the construction as an attack a malicious issuer can mount, and points at holder software serving many holders at once as the thing most likely to notice it. Nothing in the verifier's checks catches it.

There is a real operational tension here. Issuers naturally want to segment status lists by credential type, by customer, by issuance batch, by region. Every one of those segmentations shrinks the herd. The privacy-preserving choice is a small number of large lists, which is the opposite of what most data modeling instincts suggest.

What the Issuer Still Learns

Herd privacy hides which credential is being checked. It does not hide that a check occurred.

The issuer's server sees a request: an IP address, a timestamp, a TLS fingerprint, possibly a user agent. Over time that is a meaningful signal. An issuer of professional licenses can observe which employers are running checks and roughly when hiring activity picks up. An issuer of age credentials can see which services are verifying and at what volume. None of this identifies the credential holder directly, but it is real information about the verifier, and verifiers are often the parties with the strongest confidentiality expectations.

There is also a subtler and more serious problem. If the verifier fetches the status list at the moment a holder presents a credential, the timing of that fetch correlates with the presentation. An issuer who can also observe the presentation side, or who colludes with someone who can, learns considerably more than the herd size suggests.

Mitigations exist and they are mostly about breaking the link between presentation and fetch, or keeping the fetch off the issuer's infrastructure:

Cache aggressively. A verifier that fetches each status list on a schedule and serves lookups from cache decouples the fetch from any particular presentation. The cost is staleness, and staleness is exactly what revocation is fighting, so the caching interval is a policy decision that trades revocation latency against correlation resistance. State the interval explicitly rather than letting an HTTP library pick it.

Serve from a CDN or mirror the issuer does not operate. If the status list credential is signed by the issuer, its authenticity does not depend on where it was fetched from, so any mirror works, and fetching from one the issuer does not run keeps the request off the issuer's own infrastructure. This is an issuer-side deployment choice rather than something a verifier picks unilaterally, since the Validate Algorithm has the verifier dereference the statusListCredential URL it was handed. It is straightforward and underused.

Fetch everything. A verifier who pulls all of an issuer's status lists on a schedule, not just the ones they need, reveals nothing through their fetch pattern. Practical when the lists are small, which compression usually makes true.

The Correlation Problem with Selective Disclosure

Now the interaction that undermines a lot of privacy work, and the reason this post exists.

An earlier post covered selective disclosure: proving you are over 18 without revealing your birth date, proving employment without revealing your salary. The point is to minimize what a verifier learns and, importantly, to avoid handing every verifier the same unique value that lets them link your presentations together.

statusListIndex is a unique value.

If a credential's status entry is disclosed as part of every presentation, then every verifier receives https://issuer.example/status/3#94567. That string identifies one credential and therefore one holder. Two verifiers who compare notes can determine they are dealing with the same person, using nothing but the revocation metadata. The status list specification names the hazard itself: id, statusListIndex, and statusListCredential are global identifiers that can be used across verifiers to correlate subjects, and issuers are urged to make status information selectively disclosable where a credential is meant to be presented without correlation. The selective disclosure machinery carefully avoided handing out a correlatable identifier, and the status check handed one out anyway.

This is not a flaw in the status list design so much as a consequence of the shape of the problem. A verifier who must check whether this specific credential is revoked needs something that identifies this specific credential. Status lists provide herd privacy against the issuer while providing essentially none against colluding verifiers.

The available responses each cost something:

Accept it. For many use cases, verifier collusion is not in the threat model. An employer verifying a professional license and a bank verifying the same license are not realistically pooling logs. Say so explicitly in the threat model rather than leaving it implicit.

Disclose the status entry selectively. This is the specification's own suggestion: carry the status information as a selectively disclosable claim, so it is revealed only to verifiers that require a status check rather than handed to everyone by default. It narrows the exposure without removing it, since every verifier who does check still receives the same index.

Per-verifier credentials. Issue a distinct credential, with a distinct status index, to each verifier relationship. Now there is nothing to correlate. The cost is issuance volume and a much more complex issuer, and the issuer now knows exactly which verifiers the holder deals with, which relocates the privacy problem rather than removing it.

Short-lived credentials. Skip revocation entirely. Issue credentials valid for hours or days, and re-issue as needed. A credential that expires quickly does not need a revocation check, so there is no index to correlate on. This is a genuinely good answer where it fits, and it is increasingly the default in high-volume flows. The costs are that the holder must be able to reach the issuer regularly, which breaks offline use, and the issuer sees a re-issuance request every period, which is its own correlation channel and a much richer one.

Cryptographic accumulators. Membership can be proven in zero knowledge, so a holder demonstrates their credential is in the non-revoked set without revealing which member they are. This is the technically satisfying answer and it removes the correlation handle properly. The costs are real: more complex cryptography, witness update mechanics that the holder has to keep current as the accumulator changes, and considerably less mature tooling than a bitstring and a GZIP call.

There is no free option. The right pick depends on whether your threat model includes colluding verifiers, whether your holders can be online, and how much cryptographic complexity your implementation can carry.

Where Bitcoin Anchoring Helps, and Where It Does Not

Given that this is a Bitcoin-focused series, the obvious question is whether anchoring helps.

It helps with one specific thing: equivocation. A status list is a signed document served from a URL, and an issuer can serve different versions to different parties, or quietly rewrite history so a credential appears never to have been revoked. Anchoring a commitment to each published status list gives you a tamper-evident record of what the issuer published and when. A verifier can then check not just that the list is signed, but that this list is the one committed to at that time. For high-value credentials where the issuer is not fully trusted, that is worth having.

It does not help with the privacy problems. Anchoring a hash on Bitcoin does nothing about the issuer observing fetches or about statusListIndex being a correlation handle. Those are properties of the status-check interaction, and moving a commitment on-chain leaves the interaction unchanged.

It also is not free. Every status list update becomes an on-chain commitment with a fee attached, which pushes toward batching updates and therefore toward slower revocation propagation. Anchoring is a good fit for credentials where revocation is rare and disputes are costly, and a poor fit for credentials that are revoked constantly.

Practical Guidance

If you are designing revocation for a credential system now:

  • Decide whether you need revocation at all. Short validity windows genuinely remove the problem for online use cases and should be the first thing considered rather than the fallback.
  • If you need it, use whichever status list matches your credential format: Bitstring Status List for W3C Verifiable Credentials, Token Status List for SD-JWT VC and mdoc. Both are well specified, and the herd privacy design is sound within its stated limits.
  • Use few, large lists. Resist segmenting by customer or credential type unless you have a specific reason that outweighs the shrinking anonymity set.
  • Assign status list indexes randomly rather than sequentially. The spec puts a SHOULD on random assignment so that a position cannot easily leak inferences such as how recently the credential was issued or how large the group is, which is much of what the herd was supposed to hide.
  • Distinguish suspension from revocation in the data model even if today's policy only uses one. Retrofitting the distinction later is painful.
  • Set the verifier cache interval deliberately and write down the reasoning, because that number is your revocation latency and it is also your correlation resistance. The data model has a slot for it: a status list credential can carry an optional ttl in milliseconds, with no default assumed if it is absent, and publishers SHOULD align HTTP Cache-Control with it. Publish the number rather than leaving verifiers to guess.
  • Serve status lists from a CDN you do not operate. The Generate Algorithm puts a SHOULD on publishing in a way that does not track who retrieves the list, and names a CDN not operated by the issuer as one route there, alongside Oblivious HTTP. A CDN you run yourself moves the access logs, it does not delete them.
  • Document whether verifier collusion is in your threat model. If it is, budget for accumulators or per-verifier issuance, because status lists alone will not get you there.

Conclusion

Revocation is where credential systems reveal what they actually optimize for. Bitstring Status List, and Token Status List alongside it, are good designs that solve the issuer-side privacy problem well and the verifier-collusion problem not at all, and knowing which of those you face is most of the decision.

The pattern worth internalizing is that revocation is not a feature you add to a credential system afterward. It shapes the credential format, the issuance flow, the verifier's network behavior, and the privacy properties of every presentation. Design it alongside issuance or you will find yourself choosing between a system that cannot revoke and one that leaks.

Next week: the other durability problem in a btcr2 deployment. Anchored commitments last forever on Bitcoin. The sidecar data that explains them does not, unless someone takes responsibility for it.

Jintek LLC