qredential

SD-JWT · Token Status List · base45

La respuesta ya está dentro del código

Una credencial que se prueba a sí misma. Firma, caducidad, revocación y claims, todo comprobado sin que una sola petición salga del dispositivo.

TypeScript, cero dependencias en tiempo de ejecución, solo WebCrypto. Node, navegadores y React Native, porque quien verifica suele ser un móvil.

firmando una credencial de demostración…
 
 
peticiones de red· verificado en·

Qué hace esto en la práctica

Una credencial es una afirmación que alguien firmó. “Esta persona es mayor de 18 años.” “Esta persona puede conducir.” La firma es lo que le da valor: dice que una autoridad responde por esa frase y que nadie la modificó después.

En papel, y en un PDF, una firma cubre el documento entero. Para probar una sola línea hay que entregarlo todo. Muestra el permiso de conducir solo para probar tu edad y la otra persona se entera además de tu dirección, tu número de documento y tu fecha exacta de nacimiento. Nunca pidió nada de eso. Ahora lo tiene igual.

La divulgación selectiva rompe ese intercambio. El emisor firma cada dato por separado, y la credencial lleva solo una huella de cada uno. Tú eliges qué revelar, y el resto sigue siendo una huella que no dice nada sobre su contenido. La firma sigue siendo válida, porque nunca fue una firma sobre un bloque indivisible.

Todo eso cabe dentro del código que lee el verificador: la firma, las huellas y los datos que elegiste mostrar. No se descarga nada durante la verificación, así que una puerta, un autobús, un consultorio rural o un avión en vuelo pueden comprobar la credencial sin red alguna.

Queda una pieza, y es justo la que se olvida. Todo lo que se puede escanear se puede fotografiar. Por eso quien presenta también tiene que firmar un desafío nuevo con una clave privada que nunca sale de su dispositivo. Sin ese paso, la captura de pantalla de la credencial de otra persona pasaría. Esta biblioteca rechaza cualquier presentación que no lo traiga.

Hecha para el momento en que no hay cobertura

Construí el eCNH, el permiso de conducir digital de Brasil, usado por más de 40 millones de personas. La parte que más me enseñó no fue la aplicación. Fue la carretera: un agente leyendo un permiso con una barra de cobertura, o ninguna, necesitando un sí o un no en menos de un segundo.

Todo lo que hace falta para esa respuesta cabe en el código. La firma prueba al emisor, los claims están ahí mismo, y lo único que viene de fuera es la clave pública del emisor, que cambia tan rara vez que puedes distribuirla con la aplicación y refrescarla cada semana.

Bibliotecas para esto existen. Son SDKs corporativos: pesados, atados al perfil de un país y escritos como si ya trabajaras en el sector de la identidad. Esta es la versión que un ingeniero de producto añade un martes.

Tres partes, y ninguna de ellas es un servidor

01 · EMITIR

Firmar cada claim una vez

El emisor decide qué claims podrán ocultarse después, y firma un digest para cada uno de ellos.

02 · PRESENTAR

Enviar solo lo que se pide

La cartera descarta los claims que se queda. La firma del emisor sigue siendo válida sobre lo que queda.

03 · VERIFICAR

Responder sin preguntar a nadie

Firma, caducidad, revocación y cada disclosure, comprobados contra una clave fijada en el dispositivo.

Demuestra que eres mayor de 18 sin entregar tu fecha de nacimiento

Las leyes de verificación de edad llegan más rápido que las herramientas. La implementación habitual hace que el cliente suba una foto de su documento a un tercero, lo cual es un desastre de privacidad y una filtración esperando a ocurrir.

La divulgación selectiva lo hace bien. El verificador no puede aprender la fecha de nacimiento ni queriendo, porque ese valor nunca salió de la cartera. La propiedad es criptográfica, no una promesa en una política de privacidad.

// La credencial guarda nombre, dirección, fecha de nacimiento y número de documento.
// El bar recibe un booleano.
const presentation = await present(credential, { disclose: ['over_18'] })

const result = await verify(presentation, { trust })
result.claims             // { over_18: true }
result.claims.birth_date  // undefined, y nunca se transmitió
result.withheld           // 4, y no puede decirte cuáles cuatro

Números reales, incluido el incómodo

Un permiso de conducir realista, ocho claims, caducidad a cinco años, puntero a lista de estado. Medido, no estimado:

CredencialCaracteresVersión del QR
todo visible~74018, se escanea bien
los ocho claims divulgables~159027, demasiado densa
presenting only over_18~111522, todavía densa
La divulgación selectiva casi duplica la credencial. Cada claim divulgable cuesta una sal más un digest firmado, y los digests se quedan en el payload revele el titular el claim o no. Es deliberado, ya que un recuento de digests que encogiera con lo que revelas filtraría lo que ocultaste. Eso hace que el ahorro al presentar sea del treinta por ciento aquí, no del ochenta que promete la intuición. Haz divulgables dos o tres claims, no todos, y deja que fits() te diga dónde estás antes de imprimir nada.

No te fíes de mi palabra

El playground ejecuta la biblioteca entera en tu navegador y le lanza ocho ataques reales. Cada uno imprime el código de rechazo que espera, así que puedes comprobar la biblioteca contra sus propias afirmaciones en lugar de fiarte de un README.

Cambiar un claim en el payload
bad_signature
Inventar un claim nunca emitido
digest_mismatch
Enviar una disclosure dos veces
digest_mismatch
Firmar con la clave equivocada
bad_signature
Decir que eres otra autoridad
unknown_issuer
Usarla dentro de diez años
expired
Usarla tras la revocación
revoked
Esconderse tras una lista caducada
status_list_stale
Repetir una presentación grabada
holder_proof_invalid
Presentar la foto del código de otro
holder_proof_missing

Por qué esta, y cuándo no

Cinco razones para elegirla, y después la parte honesta.

  • Unas 1.300 líneas que se pueden leerLo bastante pequeña para que una sola persona la lea entera en una tarde y sepa qué hace. La seguridad que no puedes leer es seguridad que aceptas por fe.
  • Cero dependenciasNada de npm en tiempo de ejecución. No hay más cadena de suministro que auditar que esta, y nada que pueda cambiar bajo tus pies la semana que viene.
  • Rechaza lo que no puede comprobar por completoSin aprobaciones parciales, sin avisos que puedas ignorar. Cada fallo vuelve como un motivo con nombre, que puedes registrar y tratar, nunca como un falso a secas.
  • Corre donde ocurre la verificaciónNode, Deno, Bun, navegadores, React Native y edge workers. El mismo código en todos, sobre la criptografía estándar que ya trae la plataforma.
  • Probada de tres manerasPruebas unitarias, los vectores oficiales publicados con el RFC 9901, y pruebas de propiedades que le lanzan entradas aleatorias y malformadas buscando un caso que se cuele.

Y cuándo esta es la herramienta equivocada.

  • Necesitas permisos de conducir ISO 18013-5Es otra codificación, CBOR y COSE, y otro estándar. Esta biblioteca habla SD-JWT, el formato que usan las carteras europeas y las especificaciones OpenID. Problema parecido, trabajo distinto.
  • Quieres una cartera completaEsto verifica y presenta. No guarda credenciales, no gestiona dispositivos ni se ocupa del alta de usuarios. Es una pieza, pensada para vivir dentro de tu producto.
  • Necesitas una auditoría externa antes de un despliegue reguladoTodavía no existe. Las pruebas y los vectores del RFC son públicos, y cada línea también, pero para un despliegue regulado conviene reservar presupuesto para tu propia revisión.

Instalar

npm i qredential

Node 20 o posterior, cualquier navegador actual, React Native. Unas 1.300 líneas de código sin dependencias en tiempo de ejecución, precisamente para que leerlo antes de confiar en él sea realista.

Estándares, no invenciones

  • SD-JWT, RFC 9901divulgación selectiva y key binding, anidada y recursiva, el mecanismo que usa la cartera de identidad europea
  • SD-JWT VCla forma de la credencial
  • Token Status Listrevocación que funciona desde una copia en caché
  • base45, RFC 9285el sobre del QR, elegido por compatibilidad de lectores

No es mDL ISO 18013-5, que es CBOR y COSE en lugar de JWT. Está en la hoja de ruta, y una credencial que use algo no soportado se rechaza en lugar de entenderse a medias. Afirmar la mitad de un estándar de conformidad es peor que no afirmar nada.