Status lists (server side)
A W3C Bitstring Status List is a long string of bits, one per credential, published as a signed credential of its own. A standard VC verifier that does not speak to Ethereum can fetch the list named in a credential's credentialStatus entry and read one bit to learn whether the credential is revoked or suspended.
In Certichain the chain is the source of truth for status. The server never stores status: it reads the chain, derives the bits, signs the list with a publisher key, and serves it. Anyone can rebuild the same bits from the chain and compare.
The concept and the holder and verifier side are in Status lists flow and Verifiable credential. This page covers the server.
The route answers 503 CONFIG_MISSING "Status lists are not available on this deployment." until the deployment owner sets STATUS_LIST_SIGNER_KEY, NEXT_PUBLIC_STATUS_LIST_BASE_URL and NEXT_PUBLIC_STATUS_LIST_PUBLISHER. The browser adds status list entries to exported VCs only when both public names are set, so set all three together.
The route
GET /api/status-lists/{chainId}/{registry}/{issuer}/{purpose}/{list}
| Segment | Meaning |
|---|---|
chainId | Decimal chain id |
registry | The set's CredentialRegistry address |
issuer | The issuer key address whose credentials the list covers |
purpose | revocation or suspension |
list | List number, 0 to 999,999 |
One issuer has two lists per block of 131,072 credentials: one for revocation, one for suspension.
How a list is built
Issuer state
readIssuerState (src/lib/server/status-lists/chain-state.ts) reads, for one issuer:
- The issuer's credentials in issuance order (block number, then log index). Only credentials in blocks at least 64 confirmations deep are counted (
CONFIRMATIONSinwiring.ts), so a chain reorganisation cannot shift positions. - The latest
StatusChangedevent of each credential. A credential with no status history is a chain state error (503), never a guess. - Whether the issuer is suspended, read from
IssuerSuspensionChangedof the key now in charge. Key retirements (IssuerRetired) are followed up to 8 steps to find that key.
Whether an address was ever an issuer is checked from IssuerAdded events before any of this, so the server does not scan the chain for random addresses.
Bits
deriveList (src/lib/vc/status/derive.ts):
| Status on chain | Revocation bit | Suspension bit |
|---|---|---|
| Active | 0 | 0 |
| Suspended | 0 | 1 |
| Revoked | 1 | 0 |
| Superseded | 1 | 0 |
| Any status, issuer suspended | as above | 1 |
Expiry is not a bit; verifiers read validUntil from the credential.
A credential's position is its index in the issuer's issuance order: list floor(index / 131072), bit index mod 131072 (listPosition). It never moves once assigned. List 0 always exists for a known issuer; a higher list number beyond the issuer's credentials is 404 "There is no such status list for this issuer."
The bits are gzip-compressed and written as multibase base64url without padding (a u prefix), as the Bitstring Status List format asks (encodeList in src/lib/vc/status/bitstring.ts). The gzip header is written the same way on every platform, so the same bits give the same encoded list.
The list credential
buildListCredential (src/lib/vc/status/list-credential.ts) wraps the list:
| Field | Value |
|---|---|
type | VerifiableCredential, BitstringStatusListCredential |
id | The list's URL under NEXT_PUBLIC_STATUS_LIST_BASE_URL |
issuer | did:pkh of the publisher address |
validFrom | Build time |
validUntil | Build time plus 600 seconds |
credentialSubject | { id: "<url>#list", type: "BitstringStatusList", statusPurpose, encodedList, ttl: 300000 } |
proof | A DataIntegrityProof holding an EIP-712 signature by the publisher over the registry, issuer, purpose, list number, the keccak256 hash of encodedList, validFrom and validUntil, in the domain of that set's registry |
The publisher is a separate key from any issuer. A verifier must pin the publisher address (NEXT_PUBLIC_STATUS_LIST_PUBLISHER) instead of trusting whoever signed the list; see Interop and SDK tests for how an independent library is set up to do that.
Contract sets
StatusListRouter (router.ts) holds one StatusListService per contract set in the manifest and picks it by the registry segment. A list for a registry that is not listed is 404. Each set's lists cover only that set's credentials.
Caching and limits
| What | How long |
|---|---|
| "Was this address an issuer", issuer state, built lists | STATUS_LIST_CACHE_SECONDS (0 to 300, default 300), at most 512 entries each |
| HTTP caching | Cache-Control: public, max-age=60 |
| List validity | validUntil 600 seconds after build, ttl 300,000 ms |
So a status change on chain shows up in a served list after the 64 confirmations plus up to the cache time. Verifiers that need a faster answer read the chain directly; the verify page and the API do.
Each IP address may fetch 600 lists per hour. Without a shared store each server instance counts on its own.
Answers
| Status | Code | When |
|---|---|---|
| 200 | The list credential, with Access-Control-Allow-Origin: * | |
| 400 | BAD_REQUEST | Malformed path |
| 404 | NOT_FOUND | Unknown chain, registry, issuer or list |
| 429 | RATE_LIMITED | IP budget used up |
| 503 | CONFIG_MISSING | Off, or the setup failed |
| 503 | UPSTREAM_ERROR | "The status list could not be built right now. Try again shortly." with Retry-After: 30 |
OPTIONS answers 204 with Access-Control-Allow-Origin: *. Failures are logged by error name only.
Configuration
| Name | Rule |
|---|---|
STATUS_LIST_SIGNER_KEY | 0x plus 64 hex. A funds-free key used only for lists. Server only. |
NEXT_PUBLIC_STATUS_LIST_PUBLISHER | Must equal the key's address, or the setup fails |
NEXT_PUBLIC_STATUS_LIST_BASE_URL | https, or http for localhost and 127.0.0.1 only; no credentials, query or fragment |
STATUS_LIST_CACHE_SECONDS | 0 to 300, default 300 |
NEXT_PUBLIC_NETWORK_ID, RPC names | The chain to read |
The monitor's signers check compares the key's address with the published one; see Monitoring endpoints.
Source files
src/app/api/status-lists/[...path]/route.tssrc/lib/server/status-lists/(config.ts,wiring.ts,router.ts,service.ts,chain-state.ts,handler.ts,ttl-cache.ts)src/lib/vc/status/(location.ts,derive.ts,bitstring.ts,list-credential.ts,entry.ts)
Tests
src/lib/server/status-lists/*.test.tssrc/lib/vc/status/*.test.tsinterop/test/status-list.test.mjsreads the lists with an independent verifier library- E2E:
e2e/journeys/status-lists.journey.ts