Volver a Explorar
EstándarTécnicoIdentidad y CVIntermedio

Revocar credenciales sin perder privacidad

10 minVerificado · 2026-05-17

Una credencial verificable puede tener que ser revocada antes de su fecha de vencimiento: el ciudadano perdió un puesto, la matrícula caducó, la información fue corregida, hubo un error de emisión. El verificador necesita saberlo, pero el mecanismo de revocación tiene que respetar dos propiedades fundamentales del modelo SSI: el verificador no contacta al emisor, y el emisor no sabe quién está verificando.

El estándar canónico del W3C para resolver este problema se llama BitstringStatusList. Es elegante, escalable y preserva la privacidad por diseño.

El problema que resuelve

Los dos extremos a evitar son obvios:

Llamar al emisor en cada verificación

Rompe la privacidad: el emisor sabe quién verifica cuándo y dónde. Mata el funcionamiento offline. Genera carga operativa en el emisor (cada verificación es una llamada).

Publicar la lista de DIDs revocados

Expone correlaciones: ver "estos DIDs fueron revocados esta semana" te dice mucho sobre quiénes son. Cada credencial revelaría algo sobre el holder al que pertenece.

BitstringStatusList resuelve ambos: el emisor publica un único bitstring grande que se descarga una vez y se cachea. Cada credencial sabe en qué bit de ese bitstring vive su status. El verificador chequea ese bit localmente.

Cómo funciona

  1. 1
    El emisor mantiene un bitstring de N bits (típicamente 131.072 bits = 16 KB sin comprimir). Cada bit representa una credencial.
  2. 2
    Al emitir, el emisor le asigna un statusListIndex a la credencial: "tu status vive en el bit 94.567".
  3. 3
    La credencial lleva esa referencia en su campo credentialStatus.
  4. 4
    El bitstring completo se publica en una URL pública (https://issuer.gob/credentials/status/3), firmado como una VC más.
  5. 5
    Cuando alguien revoca, el emisor flippea el bit correspondiente y republica.
  6. 6
    Para verificar: descargar el bitstring, leer el bit que indica statusListIndex. Listo.

El bitstring se descarga una vez por verificador y se cachea. El emisor no se entera quién consulta el status de qué credencial: solo ve descargas del bitstring entero.

Privacidad por diseño

Verificador anónimo

Descarga un archivo público, no avisa qué credencial está verificando. El emisor no sabe si está verificando, ni cuándo, ni a quién pertenece la credencial.

Emisor opaco

Solo ve descargas del bitstring, pero no a qué credencial corresponde cada chequeo. Mil verificaciones de mil credenciales distintas se ven igual que mil verificaciones de la misma credencial.

Sin correlación entre revocaciones

El bitstring se publica completo siempre. Ver "cuántos bits flippearon esta semana" no te dice quiénes son. Cada bit es indistinguible del resto sin la credencial original.

Estructura de los datos

En cada credencial emitida, se incluye una referencia al status list:

"credentialStatus": {
  "id": "https://salta.gob.ar/credentials/status/3#94567",
  "type": "BitstringStatusListEntry",
  "statusPurpose": "revocation",
  "statusListIndex": "94567",
  "statusListCredential": "https://salta.gob.ar/credentials/status/3"
}

El status list mismo es una credencial verificable:

{
  "@context": ["https://www.w3.org/ns/credentials/v2"],
  "id": "https://salta.gob.ar/credentials/status/3",
  "type": ["VerifiableCredential", "BitstringStatusListCredential"],
  "issuer": "did:web:salta.gob.ar",
  "validFrom": "2026-05-17T00:00:00Z",
  "credentialSubject": {
    "id": "https://salta.gob.ar/credentials/status/3#list",
    "type": "BitstringStatusList",
    "statusPurpose": "revocation",
    "encodedList": "uH4sIAAAAAAAA_..."
  }
}

El campo encodedList es el bitstring comprimido con GZIP y codificado en base64url. Estar firmado como una VC es importante: garantiza que solo el emisor real puede publicar listas autorizadas. Si alguien intentara servir una lista falsa, la firma no validaría.

Operación de revocación

import { loadStatusList, flipBit, signVC } from "@sovrahq/agent";

async function revoke(credentialId: string) {
  const credential = await db.credentials.get(credentialId);
  const listId = credential.statusListCredential;
  const index = credential.statusListIndex;

  // Cargar el bitstring actual, flippear el bit, re-firmar
  const list = await loadStatusList(listId);
  flipBit(list, index, 1);

  const signed = await signVC(list, {
    issuer: "did:web:salta.gob.ar",
  });

  await statusListStore.publish(listId, signed);
}

Republicar es atómico: una versión cacheada vieja sigue funcionando hasta que el TTL expira, momento en que los verificadores descargan la nueva.

Verificación de status

import { resolveStatusList, getBit } from "@sovrahq/agent";

async function isRevoked(credential: VerifiableCredential): Promise<boolean> {
  const statusEntry = credential.credentialStatus;
  if (!statusEntry) return false;

  // Resolver y verificar firma del status list
  const list = await resolveStatusList(statusEntry.statusListCredential);

  // Leer el bit del índice de esta credencial
  const bit = getBit(list.encodedList, Number(statusEntry.statusListIndex));

  return bit === 1;
}

El verificador cachea las status lists típicamente por 5-60 minutos según el caso de uso. Para credenciales críticas (financiero, salud), TTL más corto. Para casos de baja sensibilidad, TTL más largo.

Tamaños y ritmo de revocación

ParámetroValor típico
Bits por lista131.072 (16 KB descomprimido)
Tamaño tras GZIP~2-4 KB (bitstring esparso)
Credenciales por listaHasta 131.072 con un solo statusPurpose
Estrategia para más credencialesMúltiples listas, cada una con su URL

Una jurisdicción que emite millones de credenciales mantiene varias listas. Cada credencial lleva el índice + la URL específica de su lista. No hay límite práctico a la escala.

Más allá de "revocada / vigente"

El campo statusPurpose no se limita a revocación. Soporta otros estados:

revocation

La credencial fue invalidada definitivamente y no debe ser aceptada. Es el caso más común.

suspension

La credencial está temporalmente inactiva, pero puede reactivarse. Útil para suspensiones por falta de pago o congelamiento administrativo.

Para casos avanzados se pueden combinar listas: una para revocación, otra para suspensión, ambas referenciadas desde la misma credencial.

Alternativas (cuándo no usar BitstringStatusList)

Migración desde StatusList2021

Si tu sistema ya emite credenciales con StatusList2021 (el predecesor en el W3C VC 1.1), la migración es directa: la estructura es casi idéntica, los conceptos son los mismos. Las diferencias clave:

  • type pasa de StatusList2021Credential a BitstringStatusListCredential.
  • RevocationList2020Status y RevocationList2021Status se unifican en BitstringStatusListEntry.
  • El statusPurpose ahora es obligatorio y declara explícitamente la semántica del bit.

Las credenciales viejas con StatusList2021 siguen siendo válidas. Los emisores migran al nuevo formato a medida que emiten credenciales nuevas.

Referencias

Relacionados

Tagsrevocacionw3cstatus-listprivacidadvc