wandres.dev
IDENTIDAD SIN SERVIDOR · DID y delegación

Identificadores descentralizados: el nombre público de una clave

El estándar del W3C para identificadores verificables y autosoberanos, cómo se resuelven a un documento con material criptográfico y por qué el método que no resuelve nada es el que sirve aquí.

⏱ 20 min

La lección anterior terminó con una afirmación que quedó sin desarrollar: si la identidad es una clave, hace falta una forma estándar de nombrarla. Podríamos inventarla —una cadena en base64 con la clave pública dentro serviría técnicamente—, pero eso nos dejaría sin interoperabilidad, sin capacidad de cambiar de algoritmo y sin ningún camino para identidades que no sean simplemente una clave suelta. El consorcio de la web tiene un estándar para exactamente este problema, y todo el ecosistema de autorización local-first se apoya en él: los identificadores descentralizados. La especificación de UCAN los adopta sin ambigüedad —el sujeto de una capacidad debe referenciarse mediante un identificador de este tipo, y los campos de emisor y audiencia contienen uno— porque resuelven un requisito muy concreto: dar un nombre global, único, no falsificable y resoluble sin registro central. Esta lección explica su anatomía, qué significa resolverlos y por qué, de toda la familia de métodos que existen, el que hace falta aquí es precisamente el que no requiere resolver nada.

🎯 Al terminar esta lección sabrás
  • Leer la sintaxis de un identificador descentralizado y saber qué aporta cada uno de sus tres segmentos.
  • Entender qué es un documento asociado y qué relaciones de verificación declara.
  • Explicar por qué el método did:key convierte la resolución en una decodificación puramente local.
  • Distinguir el compromiso entre identificadores inmutables y documentos mutables, y sus consecuencias.

Anatomía de un identificador descentralizado

La sintaxis es deliberadamente pobre y esa pobreza es una virtud. Un identificador tiene tres segmentos separados por dos puntos: el esquema fijo did, el nombre del método y un identificador específico de ese método. Nada más. La especificación de UCAN muestra ejemplos como did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp en el campo de audiencia, y su especificación de revocación usa además did:web:alice.example.com y did:plc:ewvi7nxzyoun6zhxrhs64oiz en los ejemplos, lo que ilustra que el mismo formato acomoda familias de identidad muy distintas.

El segmento del método es la pieza interesante, porque es un punto de extensión. Cada método define su propio procedimiento para dos cosas: cómo se crea un identificador y cómo se obtiene, a partir de él, el material criptográfico asociado. Un método puede apoyarse en el DNS, en un libro mayor replicado, en un servicio alojado o en ninguna infraestructura en absoluto. El estándar no privilegia ninguno; lo que estandariza es la forma del nombre y la forma del documento que se obtiene al resolverlo, de modo que el consumidor pueda tratar identidades de procedencias muy distintas con el mismo código.

Lo que todos comparten, y lo que los hace útiles para nosotros, es la propiedad que la especificación de UCAN describe al hablar del sujeto: se comporta como un identificador único global, con la adición de ser verificable por clave pública. Esa segunda mitad es la que no tenía ningún esquema de nombres anterior. Un correo electrónico o un nombre de usuario son únicos por convención administrativa: alguien lleva un registro y evita duplicados. Un identificador descentralizado es único porque falsificarlo equivale a romper la criptografía subyacente, y esa imposibilidad de falsificación es exactamente lo que impide las colisiones maliciosas de espacio de nombres que conducen al problema del diputado confuso.

ℹ️
Autosoberano significa que nadie puede quitártelo, no que nadie pueda ignorarlo

El adjetivo autosoberano que acompaña a estos identificadores suele malinterpretarse en las dos direcciones. No significa que un servicio esté obligado a aceptarte: cualquier sistema puede decidir con quién habla, y esa libertad no la quita ningún estándar. Lo que significa es más modesto y más profundo: el identificador existe con independencia de cualquier registro, de modo que nadie puede revocarlo, reasignarlo, suspenderlo ni cobrarte por renovarlo. Si mañana desaparece el servicio donde lo usabas, el identificador sigue siendo tuyo y sigue siendo el mismo en cualquier otro sitio donde decidas usarlo. Es exactamente el mismo argumento de longevidad que vimos en el primer nivel del track aplicado al dato, trasladado ahora al nombre.

Resolver: del identificador al documento

Resolver un identificador es obtener su documento, que es la estructura donde vive todo lo que el mundo necesita saber para interactuar criptográficamente con ese sujeto. El documento no contiene datos personales ni atributos: contiene métodos de verificación —esencialmente claves públicas con su tipo y su identificador— y declaraciones sobre para qué sirve cada uno. Esas declaraciones son la parte que más se pasa por alto y la que más importa en un contexto de autorización, porque una misma entidad puede tener una clave para autenticarse, otra para acordar secretos de cifrado y otra distinta para delegar autoridad, y el documento es donde se dice cuál es cuál.

{
  "id": "did:example:1234",
  "verificationMethod": [
    {
      "id": "did:example:1234#clave-0",
      "type": "Multikey",
      "controller": "did:example:1234",
      "publicKeyMultibase": "z6Mki..."
    }
  ],
  "authentication": ["did:example:1234#clave-0"],
  "capabilityDelegation": ["did:example:1234#clave-0"],
  "capabilityInvocation": ["did:example:1234#clave-0"]
}

El procedimiento para llegar hasta ese documento es lo que distingue radicalmente a unos métodos de otros, y de ahí sale el criterio de elección para una aplicación local-first. Un método basado en nombres de dominio resuelve haciendo una petición de red a ese dominio: es cómodo, es legible para un humano y exige estar conectado y confiar en el operador del dominio. Un método basado en un libro mayor replicado resuelve consultando ese libro: elimina al operador único pero introduce dependencia de la red y latencia. Cualquiera de los dos rompe la propiedad que hemos pasado cincuenta niveles construyendo, que es poder trabajar sin conexión.

flowchart TD
DID[identificador descentralizado] --> M[metodo]
M --> W[via dominio: peticion de red y confianza en el operador]
M --> L[via libro mayor: consulta distribuida y latencia]
M --> K[via clave: decodificacion puramente local]
W --> DOC[documento con material criptografico]
L --> DOC
K --> DOC
style W fill:#f38ba8,color:#11111b
style L fill:#f9e2af,color:#11111b
style K fill:#a6e3a1,color:#11111b

did:key: cuando resolver es decodificar

La respuesta del ecosistema a ese problema es elegante hasta el punto de parecer trampa. El método did:key no apunta a la clave: contiene la clave. El identificador específico del método es la propia clave pública codificada, precedida de un prefijo que declara de qué tipo de clave se trata. Resolverlo, por tanto, no es consultar nada, sino decodificar la cadena que ya tienes delante. La operación es local, determinista, instantánea y no puede fallar por partición de red, porque no hay red.

Esto no es una excentricidad de nicho: es lo que la propia especificación de UCAN exige. En su cuadro de suite criptográfica, el algoritmo de resumen obligatorio es SHA-256, las firmas admitidas son Ed25519, P-256 y secp256k1 —con preferencia recomendada por Ed25519— y el método de identificador requerido es exactamente did:key. No es una de varias opciones sugeridas: es el mínimo que toda implementación debe soportar para ser interoperable.

// La resolucion es una funcion pura sin efectos ni red
function resolver(did) {
  const [esquema, metodo, id] = did.split(":");
  if (esquema !== "did" || metodo !== "key") throw new Error("metodo no local");
  const bytes = decodificarMultibase(id);      // el prefijo z indica base58btc
  const { codigo, clave } = leerMulticodec(bytes); // el codigo declara el algoritmo
  return { tipo: nombreDeCodigo(codigo), clavePublica: clave };
}

// Verificar una firma nunca sale del dispositivo
const { clavePublica } = resolver(delegacion.iss);
const ok = await verificar(clavePublica, delegacion.firma, delegacion.cuerpo);

Merece la pena detenerse en la codificación porque explica los prefijos que se ven por todas partes y que a primera vista parecen ruido. La z inicial no forma parte de la clave: es el indicador de que lo que sigue está en base58 con el alfabeto de Bitcoin. Los caracteres siguientes tampoco son clave todavía, sino un código que declara el algoritmo, de modo que los identificadores de claves Ed25519 empiezan de forma reconocible por z6Mk y los de otros tipos por secuencias distintas. Este diseño es autodescriptivo por construcción: el identificador lleva dentro qué es, y por eso puede evolucionar a algoritmos futuros sin romper a los lectores antiguos, que sencillamente encontrarán un código que no reconocen y lo rechazarán limpiamente en lugar de interpretarlo mal.

💡
El identificador es la clave, y eso tiene un corolario incómodo

Si el identificador es la clave pública, entonces rotar la clave cambia el identificador. No es un defecto de implementación ni algo que se pueda arreglar con más ingeniería: es una consecuencia lógica de haber eliminado la indirección. Los métodos que permiten cambiar de clave sin cambiar de nombre lo consiguen precisamente por tener un documento mutable en algún lugar consultable, y ese lugar es una dependencia de red. Es el mismo compromiso de siempre en otra ropa: puedes tener un nombre estable o puedes tener resolución sin conexión, y elegir ambos exige una tercera pieza. Cuál es esa pieza, y por qué las tres lecciones siguientes giran a su alrededor, es lo que hace que el problema de añadir un dispositivo no sea trivial.

Qué no es un identificador descentralizado

Terminamos delimitando, porque este es un terreno donde el entusiasmo produce afirmaciones que no se sostienen. Un identificador de este tipo no es una credencial: no dice nada sobre ti, no acredita tu edad, tu empleo ni tu pertenencia a nada. Esa función corresponde a otra familia de especificaciones —las credenciales verificables—, que la propia especificación de UCAN señala como emparentada pero distinta, dirigida a afirmar atributos sobre el poseedor de un identificador y no a otorgar autoridad.

Tampoco es, por sí mismo, una autorización. Saber quién eres no dice qué puedes hacer, y confundir ambas cosas es reintroducir por la puerta de atrás el modelo de lista de control de acceso que acabamos de desmontar: si el recurso tiene que mirar una tabla de identificadores permitidos, no hemos avanzado nada. El identificador es únicamente el nombre del principal; la autoridad viaja aparte, en el certificado que veremos en la lección siguiente.

Y no es, por último, una garantía de que quien controla la clave sea una persona concreta. La criptografía demuestra posesión de un secreto, no intención ni consentimiento ni humanidad. Cualquier vínculo entre un identificador y un ser humano es una afirmación externa que alguien tendrá que sostener por otros medios, y todo el diseño de este nivel está construido justamente para no necesitar ese vínculo: quién eres es irrelevante, lo que importa es qué certificado presentas.

🔑

Es un nombre no falsificable

Único globalmente sin registro central, porque su unicidad descansa en la criptografía y no en un administrador que evite duplicados.

📄

Resuelve a material criptográfico

El documento asociado declara qué claves existen y para qué sirve cada una: autenticar, delegar autoridad o invocarla.

Con `did:key` no hay resolución

El identificador contiene la clave codificada, de modo que obtenerla es decodificar y nunca depende de la red ni de un tercero.

🚫

No dice qué puedes hacer

La identidad y la autoridad son cosas separadas por diseño, y mantenerlas separadas es lo que evita recaer en el modelo de tabla.

La verdadera aportación no es descentralizar el nombre: es que el nombre deje de ser una referencia

Casi todo lo que se escribe sobre identificadores descentralizados se detiene en el prefijo descentralizado, como si la aportación fuese quitar el registro central, y esa lectura deja escapar lo que de verdad hace posible este nivel. Piensa en cualquier sistema de nombres que hayas usado. Un nombre de dominio, una dirección de correo, un identificador de usuario en una base de datos: todos son referencias. El nombre no contiene nada, apunta a una entrada, y esa entrada vive en algún sitio que alguien mantiene. La consecuencia inevitable es que usar el nombre exige desreferenciarlo, y desreferenciar exige que el sitio exista, esté disponible y sea honesto. Es una dependencia tan omnipresente que resulta invisible hasta que se quita. Lo que hace did:key no es descentralizar esa entrada: es eliminar la indirección por completo. El nombre deja de apuntar a la clave y pasa a ser la clave en otra representación. Y en ese instante el nombre hereda todas las propiedades de un valor en lugar de las de una referencia: es inmutable, es autoverificable, se puede copiar sin coordinación, comparar por igualdad sin ambigüedad y transportar sin contexto. Si esto te suena, es porque es literalmente la misma idea que estudiamos al direccionar por contenido: allí el nombre de un dato era su hash, y aquí el nombre de un sujeto es su clave. En ambos casos la ganancia es idéntica —verificación local, sin confianza en el mensajero, sin red— y el precio también es idéntico y no se puede eludir: si el nombre es el contenido, cambiar el contenido cambia el nombre. Un fichero editado tiene otro hash; una clave rotada tiene otro identificador. Toda la dificultad de las lecciones cuarta y quinta se deduce de esta única frase. Añadir un dispositivo, recuperar una cuenta y rotar una clave comprometida son, los tres, casos particulares del mismo problema: querer una identidad estable construida sobre nombres que por definición no lo son. Y la solución, cuando llegue, tendrá la forma que ya conoces de otros niveles del track: reconstruir la mutabilidad como una capa de indirección explícita sobre valores inmutables —una cadena de delegaciones que enlaza claves nuevas a una raíz antigua—, exactamente igual que un puntero de rama reconstruye la mutabilidad sobre los objetos inmutables de un almacén direccionado por contenido. No hay magia nueva en este nivel. Hay una idea vieja del track aplicada a la autoridad.

⚔️ Construye y disecciona identificadores
  1. Genera un par Ed25519 y construye a mano el identificador did:key correspondiente, con su código de algoritmo y su codificación en base58.
  2. Escribe la función inversa de resolución y comprueba que recuperas exactamente los bytes de la clave pública original.
  3. Introduce un error de un solo carácter en el identificador y verifica que la decodificación falla en lugar de devolver una clave incorrecta.
  4. Repite el ejercicio con P-256 y observa cómo cambia el prefijo visible del identificador.
  5. Escribe en dos líneas qué tendría que ocurrir en tu aplicación si el identificador de un usuario cambiara, y anota cuántos sitios se verían afectados.