npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vess-id/status-list

v0.3.0

Published

IETF OAuth Status List implementation for TypeScript/Node.js (JWT & CWT)

Downloads

389

Readme

@vess-id/status-list

IETF OAuth Status List implementation for TypeScript/Node.js, supporting both JWT and CWT formats.

npm version License

Overview

This library implements the IETF OAuth Status List specification for managing credential status information in a privacy-preserving, space-efficient manner.

Key Features:

  • Dual Format Support: JWT and CWT (CBOR Web Token) formats
  • Space Efficient: ZLIB compression reduces storage by 90%+
  • Privacy Preserving: Herd privacy through large status lists
  • Flexible Status Values: Support for 1, 2, 4, or 8-bit status values
  • Full Lifecycle: Create, sign, verify, and query status lists
  • Standards Compliant: Follows IETF draft-ietf-oauth-status-list-14
  • TypeScript Native: Full type safety with comprehensive types
  • Production Ready: Extensive test coverage (86+ tests)

Use Cases

  • SD-JWT Verifiable Credentials: Revocation for Selective Disclosure JWTs
  • mdoc/mDL: Status management for mobile Driver's Licenses and ISO 18013-5 documents
  • W3C Verifiable Credentials: Credential status tracking
  • OAuth Token Revocation: Scalable token status lists

Installation

npm install @vess-id/status-list

Requirements:

  • Node.js >= 20
  • TypeScript >= 5.0 (if using TypeScript)

Quick Start

Creating a JWT Status List

import {
  StatusList,
  createJWTStatusListPayload,
  signStatusListJWT,
  StandardStatusValues,
} from '@vess-id/status-list';
import { importPKCS8 } from 'jose';

// 1. Create a status list (1 bit per entry: 0=valid, 1=revoked)
const list = StatusList.create(10000, 1);

// 2. Revoke some credentials
list.setStatus(42, StandardStatusValues.INVALID);
list.setStatus(100, StandardStatusValues.INVALID);

// 3. Create JWT payload
const { payload } = createJWTStatusListPayload({
  statusList: list,
  iss: 'https://issuer.example.com',
  sub: 'https://issuer.example.com/status/1',
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 86400, // 24 hours
  ttl: 3600, // Cache for 1 hour
});

// 4. Sign the JWT
const privateKey = await importPKCS8(pemPrivateKey, 'ES256');
const jwt = await signStatusListJWT(payload, privateKey, { alg: 'ES256' });

console.log('Status List JWT:', jwt);

Verifying Credential Status

import {
  StatusListTokenHelper,
  StandardStatusValues,
} from '@vess-id/status-list';

// Extract status reference from credential
const credentialStatus = {
  idx: 42,
  uri: 'https://issuer.example.com/status/1',
};

// Fetch and parse status list
const helper = await StatusListTokenHelper.fromStatusReference(credentialStatus);

// Check if expired
if (helper.isExpired()) {
  throw new Error('Status list expired');
}

// Check credential status
const status = helper.getStatus(credentialStatus.idx);

if (status === StandardStatusValues.INVALID) {
  console.log('❌ Credential is REVOKED');
} else if (status === StandardStatusValues.VALID) {
  console.log('✅ Credential is VALID');
} else if (status === StandardStatusValues.SUSPENDED) {
  console.log('⏸️ Credential is SUSPENDED');
}

Creating a CWT Status List (for mdoc)

import {
  StatusList,
  createCWTStatusListPayload,
  encodeCWTPayload,
} from '@vess-id/status-list';

// 1. Create status list
const list = StatusList.create(10000, 2); // 2 bits: supports 4 status values

// 2. Set statuses
list.setStatus(0, 0); // Valid
list.setStatus(1, 1); // Invalid
list.setStatus(2, 2); // Suspended

// 3. Create CWT payload
const payload = createCWTStatusListPayload({
  sub: 'https://issuer.example.com/status/1',
  iss: 'https://issuer.example.com',
  lst: list.compressToBytes(),
  bits: 2,
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 86400,
});

// 4. Encode to CBOR
const cwtBytes = encodeCWTPayload(payload);

console.log('CWT Status List:', cwtBytes);

API Reference

Core Classes

StatusList

Main class for managing bit-packed status arrays.

// Create new list
const list = StatusList.create(size: number, bits: BitsPerStatus);

// From existing statuses
const list = StatusList.fromArray(statuses: StatusValue[], bits: BitsPerStatus);

// Decompress from JWT
const list = StatusList.decompressFromBase64URL(compressed: string, bits: BitsPerStatus);

// Decompress from CWT
const list = StatusList.decompressFromBytes(compressed: Uint8Array, bits: BitsPerStatus);

// Get/Set status
const status = list.getStatus(index: number): StatusValue;
list.setStatus(index: number, value: StatusValue): void;

// Compress for JWT
const compressed = list.compressToBase64URL(): string;

// Compress for CWT
const compressed = list.compressToBytes(): Uint8Array;

// Metadata
const size = list.getSize(): number;
const bits = list.getBitsPerStatus(): BitsPerStatus;

StatusListTokenHelper

High-level helper for working with Status List Tokens.

// From token (auto-detects JWT/CWT)
const helper = StatusListTokenHelper.fromToken(token: string | Uint8Array);

// From URI (fetches via HTTP)
const helper = await StatusListTokenHelper.fromStatusReference(
  reference: { idx: number; uri: string },
  options?: FetchOptions
);

// Query status
const status = helper.getStatus(index: number): StatusValue;

// Check expiration
const expired = helper.isExpired(currentTime?: number): boolean;

// Access metadata
const iss = helper.iss;
const sub = helper.sub;
const iat = helper.iat;
const exp = helper.exp;
const ttl = helper.ttl;
const bits = helper.bits;
const size = helper.size;
const format = helper.tokenFormat; // 'jwt' | 'cwt'

JWT Format

Creating JWT Status Lists

import {
  createJWTStatusListPayload,
  signStatusListJWT,
  type CreateJWTStatusListOptions,
} from '@vess-id/status-list';

// Create payload
const { header, payload } = createJWTStatusListPayload({
  statusList: list,
  iss: string,
  sub: string,
  iat?: number, // defaults to now
  exp?: number,
  ttl?: number,
  aggregationUri?: string,
});

// Sign
const jwt = await signStatusListJWT(
  payload,
  privateKey: KeyLike,
  options?: {
    alg?: string;
    kid?: string;
    additionalHeaders?: Record<string, unknown>;
  }
): Promise<string>;

Parsing JWT Status Lists

import {
  parseJWTStatusList,
  verifyStatusListJWT,
  extractStatusListReference,
} from '@vess-id/status-list';

// Parse (without verification)
const { header, payload, statusList } = parseJWTStatusList(jwt: string);

// Verify signature
const { header, payload } = await verifyStatusListJWT(
  jwt: string,
  publicKey: KeyLike,
  options?: {
    issuer?: string;
    validateExp?: boolean;
  }
);

// Extract reference from credential
const reference = extractStatusListReference(credentialJWT: string);
// Returns: { idx: number, uri: string }

CWT Format

Creating CWT Status Lists

import {
  createCWTStatusListPayload,
  encodeCWTPayload,
  signCWTStatusList,
  type COSEKey,
} from '@vess-id/status-list';

// Create payload
const payload = createCWTStatusListPayload({
  sub?: string,
  iss?: string,
  lst: Uint8Array,
  bits: BitsPerStatus,
  iat?: number,
  exp?: number,
  ttl?: number,
  aggregation_uri?: string,
  additionalClaims?: Record<number, unknown>,
});

// Encode to CBOR (unsigned)
const cwtBytes = encodeCWTPayload(payload);

// Sign with COSE Sign1 (advanced)
const cwtSigned = signCWTStatusList(
  payload,
  privateKey: COSEKey,
  options?: {
    alg?: number;
    kid?: Uint8Array;
    additionalHeaders?: Map<number, unknown>;
  }
);

Parsing CWT Status Lists

import {
  parseCWTStatusList,
  parseCWTStatusListSigned,
  extractStatusListReferenceCBOR,
} from '@vess-id/status-list';

// Parse unsigned CWT
const { payload, statusList } = parseCWTStatusList(cwtBytes: Uint8Array);

// Parse and verify signed CWT
const { payload, statusList } = parseCWTStatusListSigned(
  cwtBytes: Uint8Array,
  publicKey: COSEKey
);

// Extract reference from mdoc credential
const reference = extractStatusListReferenceCBOR(credentialCBOR: Uint8Array);
// Returns: { idx: number, uri: string }

Validation

import {
  validateJWTPayload,
  validateCWTPayload,
  validateExpiry,
  validateTTLBounds,
  isExpired,
} from '@vess-id/status-list';

// Validate payload structure
const result = validateJWTPayload(payload);
if (!result.valid) {
  console.error('Errors:', result.errors);
}

// Check expiration
const expiryResult = validateExpiry(exp, iat, ttl);
if (!expiryResult.valid) {
  console.error('Token expired');
}

// Validate TTL bounds (DoS prevention)
const ttlResult = validateTTLBounds(ttl);
if (!ttlResult.valid) {
  console.warn('TTL outside recommended bounds:', ttlResult.errors);
}

// Simple expiration check
if (isExpired(exp, iat, ttl)) {
  throw new Error('Token expired');
}

Fetching Status Lists

import {
  fetchStatusListToken,
  isValidStatusListUri,
  type FetchOptions,
} from '@vess-id/status-list';

// Fetch status list (auto-detects JWT/CWT)
const token = await fetchStatusListToken(
  uri: string,
  options?: {
    timeout?: number; // default: 10000ms
    headers?: Record<string, string>;
    maxRedirects?: number; // default: 3
    fetchImpl?: typeof fetch;
  }
);

// Validate URI
if (!isValidStatusListUri(uri)) {
  throw new Error('Invalid status list URI');
}

Types

Status Values

type BitsPerStatus = 1 | 2 | 4 | 8;
type StatusValue = number; // 0-255 depending on bits

enum StandardStatusValues {
  VALID = 0x00,
  INVALID = 0x01,
  SUSPENDED = 0x02,
  APPLICATION_SPECIFIC = 0x03,
}

Status Format

type StatusFormat = 'jwt' | 'cwt';

interface StatusListReference {
  idx: number;
  uri: string;
}

Advanced Examples

Large-Scale Status List (100K credentials)

import { StatusList, StandardStatusValues } from '@vess-id/status-list';

// Create list for 100,000 credentials
const list = StatusList.create(100000, 1);

// Revoke every 1000th credential
for (let i = 0; i < 100000; i += 1000) {
  list.setStatus(i, StandardStatusValues.INVALID);
}

// Compress
const compressed = list.compressToBase64URL();
console.log('Compressed size:', compressed.length, 'characters');
// Expected: ~4-5KB (90%+ compression)

// Original size: 100,000 bits = 12,500 bytes
// Compressed: ~5,000 bytes (~60% compression for sparse data)

Multi-Status System (2-bit)

import { StatusList } from '@vess-id/status-list';

const STATUS = {
  ACTIVE: 0,
  REVOKED: 1,
  SUSPENDED: 2,
  EXPIRED: 3,
};

// 2 bits per status = 4 possible values
const list = StatusList.create(1000, 2);

list.setStatus(0, STATUS.ACTIVE);
list.setStatus(1, STATUS.REVOKED);
list.setStatus(2, STATUS.SUSPENDED);
list.setStatus(3, STATUS.EXPIRED);

const status = list.getStatus(1);
console.log(status === STATUS.REVOKED); // true

Custom Validation

import {
  StatusListTokenHelper,
  validateExpiry,
  validateTTLBounds,
} from '@vess-id/status-list';

async function validateCredentialStatus(reference: { idx: number; uri: string }) {
  // Fetch status list
  const helper = await StatusListTokenHelper.fromStatusReference(reference);

  // Validate expiry
  const expiryResult = validateExpiry(helper.exp, helper.iat, helper.ttl);
  if (!expiryResult.valid) {
    throw new Error(`Status list expired: ${expiryResult.errors.join(', ')}`);
  }

  // Validate TTL bounds
  const ttlResult = validateTTLBounds(helper.ttl);
  if (!ttlResult.valid) {
    console.warn(`TTL warning: ${ttlResult.errors.join(', ')}`);
  }

  // Check status
  const status = helper.getStatus(reference.idx);
  return {
    valid: status === 0,
    status,
    issuer: helper.iss,
    expiresAt: helper.exp,
  };
}

Credential with Status (SD-JWT Example)

import { createJWTStatusListPayload, signStatusListJWT } from '@vess-id/status-list';

// 1. Issue credential with status claim
const credential = {
  iss: 'https://issuer.example.com',
  sub: '[email protected]',
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 31536000, // 1 year
  vc: {
    type: ['VerifiableCredential', 'UniversityDegree'],
    credentialSubject: {
      name: 'Alice Smith',
      degree: 'Bachelor of Science',
    },
  },
  status: {
    idx: 42,
    uri: 'https://issuer.example.com/status/1',
  },
};

// 2. Create status list
const list = StatusList.create(10000, 1);
const { payload } = createJWTStatusListPayload({
  statusList: list,
  iss: 'https://issuer.example.com',
  sub: 'https://issuer.example.com/status/1',
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 86400,
  ttl: 3600,
});

// 3. Sign status list
const statusListJWT = await signStatusListJWT(payload, privateKey);

// 4. Verifier checks status
const helper = await StatusListTokenHelper.fromStatusReference(credential.status);
const status = helper.getStatus(credential.status.idx);

if (status === 1) {
  throw new Error('Credential has been revoked');
}

Security Considerations

This library follows the security recommendations from the IETF specification:

1. TTL Bounds (Section 11.5)

// Default limits prevent DoS attacks
const MAX_TTL = 31536000; // 1 year
const MIN_TTL = 60; // 1 minute

// Validate TTL
const result = validateTTLBounds(ttl);
if (!result.valid) {
  console.warn('TTL outside recommended bounds');
}

2. Redirect Limits (Section 11.4)

// Default maximum: 3 redirects
const token = await fetchStatusListToken(uri, {
  maxRedirects: 3,
});

3. Timeout Protection

// Default timeout: 10 seconds
const token = await fetchStatusListToken(uri, {
  timeout: 10000,
});

4. Signature Verification

// Always verify signatures in production
const verified = await verifyStatusListJWT(jwt, publicKey);

5. Herd Privacy

// Use large lists for privacy (recommended: 10,000+ entries)
const list = StatusList.create(100000, 1);
// Even with few revoked credentials, observers cannot correlate

Performance

Compression Efficiency

| Entries | Bits | Uncompressed | Compressed | Ratio | |---------|------|--------------|------------|-------| | 10,000 | 1 | 1.22 KB | ~500 B | 60% | | 100,000 | 1 | 12.2 KB | ~5 KB | 60% | | 10,000 | 2 | 2.44 KB | ~1 KB | 60% | | 100,000 | 8 | 97.7 KB | ~20 KB | 80% |

Compression ratio depends on data entropy. Sparse lists compress better.

Memory Usage

// Minimal memory footprint
const list = StatusList.create(100000, 1);
// Memory: ~12.5 KB (uncompressed in memory)
// Network: ~5 KB (compressed over wire)

Testing

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

# Build
npm run build

# Lint
npm run lint

# Format
npm run format

Test Coverage

Test Files: 5 passed (5)
Tests: 86 passed (86)
- Core (bitpack, compression, StatusList): 58 tests
- JWT integration: 10 tests
- CWT integration: 18 tests

Compatibility

Node.js Versions

  • ✅ Node.js 20+
  • ✅ Node.js 22+

Module Systems

  • ✅ ESM (import)
  • ✅ CommonJS (require)

TypeScript

  • ✅ TypeScript 5.0+
  • ✅ Full type definitions included
  • ✅ Strict mode compatible

IETF Specification Compliance

This library implements draft-ietf-oauth-status-list-14:

  • ✅ Section 3: Status List Structure
  • ✅ Section 4: Bit Packing (LSB-first)
  • ✅ Section 5: JWT Status List
  • ✅ Section 5.2: CWT Status List
  • ✅ Section 6: Status Reference
  • ✅ Section 11: Security Considerations

Related Projects

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for new features
  4. Ensure all tests pass
  5. Submit a pull request

License

Apache License 2.0

Copyright (c) 2024 VESS Project

See LICENSE file for details.

Support


Made with ❤️ for the Verifiable Credentials ecosystem