A TypeScript library implementing the W3C Bitstring Status List v1.0 specification for privacy‑preserving credential status management in Verifiable Credentials.
Think of the Bitstring Status List as a privacy‑preserving way to check whether a credential has been revoked or suspended. Instead of maintaining a public database of revoked credentials (which would reveal sensitive information), the specification uses a compressed bitstring where each bit represents the status of a credential.
Here's how it works conceptually: imagine you have 100 000 credentials. Rather than listing "credential #1234 is revoked," you create a bitstring where position 1234 contains a bit indicating the status. The entire bitstring is compressed and published, allowing anyone to check a credential's status without revealing which credentials they're checking.
This library provides a complete implementation of the W3C specification with the following capabilities:
- Direct credential access through the
BitManagerclass, which handles low‑level bit operations without requiring explicit entry creation - Compressed storage using gzip compression and base64url encoding, meeting the W3C requirement for minimum 16 KB bitstrings
- Uniform status width where all credentials in a status list use the same number of bits for their status values
- Full W3C compliance including proper validation, minimum bitstring sizes, and status purpose matching
- TypeScript support with comprehensive type definitions for all specification interfaces
pnpm install @4sure-tech/vc-bitstring-status-lists # or npm / yarnLet's walk through creating and using a status list step by step.
import {BitstreamStatusList} from "vc-bitstring-status-lists";
// Create a new status list with 1‑bit status values (0 = valid, 1 = revoked)
const statusList = new BitstreamStatusList({statusSize: 1});
// Set credential statuses directly using their indices
statusList.setStatus(0, 0); // Credential at index 0 is valid
statusList.setStatus(1, 1); // Credential at index 1 is revoked
statusList.setStatus(42, 1); // Credential at index 42 is revokedThe key insight here is that you don't need to "add" entries first. The system automatically handles any credential index you reference, creating the necessary bit positions as needed.
import {createStatusListCredential} from "vc-bitstring-status-lists";
import type {BitstringStatusListCredentialUnsigned} from "vc-bitstring-status-lists";
const statusListCredential: BitstringStatusListCredentialUnsigned =
await createStatusListCredential({
id: "https://example.com/status-lists/1",
issuer: "https://example.com/issuer",
statusPurpose: "revocation",
statusSize: 1, // optional, 1 is default
statusList: statusList, // Optional: pass a preset list — if omitted the library creates an empty (all‑zero) list
validFrom: new Date("2025-07-01"),
validUntil: new Date("2026-07-01"),
});
console.log(
statusListCredential.credentialSubject.encodedList /* → "u…" */
);import {checkStatus} from "vc-bitstring-status-lists";
// A credential that references the status list
const credential = {
"@context": ["https://www.w3.org/ns/credentials/v2"],
id: "https://example.com/credential/456",
type: ["VerifiableCredential"],
issuer: "https://example.com/issuer",
credentialSubject: {
id: "did:example:123",
type: "Person",
name: "Alice",
},
credentialStatus: {
type: "BitstringStatusListEntry",
statusPurpose: "revocation",
statusListIndex: "1", // This credential is at index 1 in the status list
statusSize: 1,
statusListCredential: "https://example.com/status-lists/1",
},
};
const result = await checkStatus({
credential,
getStatusListCredential: async (url) => statusListCredential,
});
console.log(result); // { verified: false, status: 1 }Most issuers first publish an empty status‑list credential when onboarding a new batch of credentials, and only update individual bits as real‑world events (revocations, suspensions, etc.) occur. The library makes this flow trivial.
import {
createStatusListCredential,
BitstreamStatusList,
} from "vc-bitstring-status-lists";
// Omit the `statusList` option → the library creates a zero‑filled list for you
const blankStatusListCredential = await createStatusListCredential({
id: "https://example.com/status-lists/2025-issuer-1",
issuer: "https://example.com/issuer",
statusPurpose: "revocation",
// statusSize defaults to 1
});
// Publish `blankStatusListCredential` at the URL above (IPFS, HTTPS, etc.)Because all bits are 0(valid) by default, you can safely reference the list immediately:
function issueCredential(holderDid: string, listIndex: number) {
return {
"@context": ["https://www.w3.org/ns/credentials/v2"],
type: ["VerifiableCredential"],
issuer: "https://example.com/issuer",
credentialSubject: {id: holderDid},
credentialStatus: {
type: "BitstringStatusListEntry",
statusPurpose: "revocation",
statusListIndex: String(listIndex),
statusSize: 1,
statusListCredential: "https://example.com/status-lists/2025-issuer-1",
},
} satisfies Credential;
}When you need to revoke (or otherwise change) a credential, fetch the current status list, mutate the relevant bit, re‑encode, and re‑publish.
import {BitstreamStatusList} from "vc-bitstring-status-lists";
// 1. Fetch the existing credential (e.g. with `fetch`) and grab the encoded list string
const encoded = blankStatusListCredential.credentialSubject.encodedList;
// 2. Decode into a mutable BitstreamStatusList
const list = await BitstreamStatusList.decode({encodedList: encoded, statusSize: 1});
// 3. Change status → index 123 now revoked
list.setStatus(123, 1);
// 4. Re‑encode and splice back into the credential
blankStatusListCredential.credentialSubject.encodedList = await list.encode();
// 5. Re‑publish the **updated** credential at the same URLThat’s it! The credential you changed will now fail verification, while all others continue to pass.
The specification supports more than just binary states. You can use multiple bits per credential to represent complex status information:
// Create a status list with 4 bits per credential (supports values 0‑15)
const statusList = new BitstreamStatusList({statusSize: 4});
// Set complex status values
statusList.setStatus(0, 12); // Binary: 1100 (could represent multiple flags)
statusList.setStatus(1, 3); // Binary: 0011 (different status combination)
console.log(statusList.getStatus(0)); // → 12Attach human‑readable messages to status codes:
const credential = {
// … other VC properties …
credentialStatus: {
type: "BitstringStatusListEntry",
statusPurpose: "revocation",
statusListIndex: "0",
statusSize: 2, // Required for status values up to 0x2
statusListCredential: "https://example.com/status-lists/1",
statusMessage: [ // "statusMessage MAY be present if statusSize is 1, and MUST be present if statusSize is greater than 1
{id: "0x0", message: "Credential is valid"},
{id: "0x1", message: "Credential has been revoked"},
{id: "0x2", message: "Credential is under review"},
],
},
};A single status list can be re‑used for several independent purposes—e.g. revocation and suspension—by declaring an array in the credential:
const multiPurposeListCredential = await createStatusListCredential({
id: "https://example.com/status-lists/combo",
issuer: "https://example.com/issuer",
statusPurpose: [
"revocation", // bit = 1 ⇒ revoked
"suspension", // bit = 1 ⇒ suspended
"kyc-level", // bit‑pair ⇒ 0=low,1=mid,2=high,3=banned
],
statusSize: 2, // enough bits to hold the largest purpose (kyc-level)
});A credential can then choose which purpose applies to it:
const credentialSuspended = {
credentialStatus: {
type: "BitstringStatusListEntry",
statusPurpose: "suspension",
statusListIndex: "42",
statusSize: 1,
statusListCredential: multiPurposeListCredential.id,
},
};Tip: When you pass an array to
statusPurpose, each purpose gets its own contiguous block of bits in the list. Keep thestatusSizelarge enough for the purpose that needs the most bits.
// Decode a status list from an encoded string
const existing = await BitstreamStatusList.decode({
encodedList: "u…", // from a credential
statusSize: 1,
});
console.log(existing.getStatus(42)); // status of credential 42
existing.setStatus(100, 1); // revoke credential 100
// Re‑encode for publishing
const updatedEncoded = await existing.encode();The library is built around several key components that work together to provide a complete W3C‑compliant implementation:
The BitManager is the foundation that handles all low‑level bit operations. It manages a growing buffer of bytes and provides methods to set and get multi‑bit values at specific
positions. Think of it as a specialized array where you can efficiently pack multiple small integers.
The key insight is that it calculates bit positions mathematically: credential index 42 with a 2‑bit status size occupies bits 84‑85 in the bitstring. This eliminates the need for explicit entry management.
The BitstreamStatusList wraps the BitManager and adds the W3C‑specific requirements like compression, encoding, and minimum size constraints. It ensures that the resulting
bitstring meets the specification's 16 KB minimum size requirement.
The checkStatus function implements the complete verification algorithm, including fetching the status list credential, validating time bounds, checking status purposes, and
extracting the actual status value.
- Minimum bitstring size— Encoded status lists are padded to ≥ 16 KB (131 072 bits)
- Compression— gzip compression as required by the spec
- Base64url encoding— Proper encoding with the required "u" prefix
- Status purpose validation— Credential entries must match one of the list's declared purposes
- Temporal validation—
validFrom/validUntilrespected - Uniform status size— Every credential in a list uses the same number of bits
class BitstreamStatusList {
constructor(options?: { buffer?: Uint8Array; statusSize?: number; initialSize?: number });
getStatus(credentialIndex: number): number;
setStatus(credentialIndex: number, status: number): void;
getStatusSize(): number;
getLength(): number;
encode(): Promise<string>;
static decode(options: { encodedList: string; statusSize?: number }): Promise<BitstreamStatusList>;
static getStatusListLength(encodedList: string, statusSize: number): number;
}Low‑level bit manipulation class (typically used internally).
class BitManager {
constructor(options: { statusSize?: number; buffer?: Uint8Array; initialSize?: number });
getStatus(credentialIndex: number): number;
setStatus(credentialIndex: number, status: number): void;
getStatusSize(): number;
getBufferLength(): number;
toBuffer(): Uint8Array;
}function createStatusListCredential(options: {
id: string;
issuer: string | IIssuer;
statusSize?: number;
statusList?: BitstreamStatusList;
statusPurpose: string | string[];
validFrom?: Date;
validUntil?: Date;
ttl?: number;
}): Promise<BitstringStatusListCredentialUnsigned>;function checkStatus(options: {
credential: CredentialWithStatus;
getStatusListCredential: (url: string) => Promise<BitstringStatusListCredentialUnsigned>;
}): Promise<VerificationResult>;try {
const result = await checkStatus({credential, getStatusListCredential});
if (!result.verified) {
console.log("Verification failed:", result.error?.message);
console.log("Status code:", result.status);
}
} catch (err) {
console.error("Status check failed:", err);
}# Build the library
pnpm run build
# Run tests
pnpm test
# The build outputs both ESM and CommonJS formats
# - dist/index.js (ESM)
# - dist/index.cjs (CommonJS)
# - dist/index.d.ts (TypeScript definitions)This library implements a W3C specification, so contributions should maintain strict compliance with the Bitstring Status List v1.0 specification. When making changes, ensure that:
- All existing tests continue to pass
- New features include comprehensive test coverage
- The implementation remains compatible with the W3C specification
- Type definitions are updated for any API changes
Licensed under the Apache License, Version 2.0.