wandres.dev
P2P II · libp2p, iroh y QUIC

iroh: marcar claves, no direcciones

iroh convierte la clave pública del dispositivo en su dirección, deja que la biblioteca elija la mejor ruta disponible en cada instante, y lo construye todo sobre QUIC con TLS 1.3 en lugar de ofrecerte un catálogo de piezas.

⏱ 22 min

Hay una frase en la documentación de iroh que condensa toda su tesis y merece leerse despacio: cada extremo se identifica por una clave criptográfica, y esa clave —no una dirección de red— es la dirección. No es una metáfora ni un envoltorio sobre direcciones reales; es una decisión de diseño con consecuencias que se propagan a cada capa. Si la identidad es la clave, entonces la dirección de red deja de ser identidad y pasa a ser lo que siempre fue: un detalle circunstancial, uno entre varios caminos posibles, algo que puede cambiar sin que nadie deje de ser quien es. La biblioteca asume a partir de ahí una obligación que el proyecto formula como dos promesas: puedes alcanzar a un par esté donde esté, y la conexión que obtienes es la mejor disponible. iroh alcanzó su versión 1.0 el 15 de junio de 2026, y lo que estabilizó no fue solo una interfaz sino esa manera de plantear el problema.

🎯 Al terminar esta lección sabrás
  • Entender qué implica que la identidad de un dispositivo sea su clave pública y no su dirección.
  • Reconstruir el arranque de un extremo, desde los sondeos a los relevos hasta la elección del relevo de casa.
  • Distinguir los dos mecanismos de localización de pares y cuándo conviene cada uno.
  • Situar cada capa de la pila y saber qué se ha fijado y qué sigue siendo intercambiable.

La clave pública es la dirección

Cuando marcas por dirección, marcas a un sitio. Cuando marcas por clave, marcas a alguien. La diferencia se vuelve palpable en cuanto el dispositivo se mueve: un portátil que pasa del cable a la red inalámbrica y de ahí al punto de acceso del móvil cambia de dirección tres veces en un minuto, y con el modelo clásico eso son tres identidades distintas y tres reconexiones. Con el modelo de iroh, el EndpointId no se ha movido; lo que ha cambiado es qué rutas llevan hasta él, y la documentación afirma que cuando las condiciones de red cambian la biblioteca reacciona de inmediato y conmuta a la mejor ruta nueva de forma transparente, sin cortar la conexión.

Esa continuidad es la propiedad que más se subestima al evaluar la pila. Elimina de la aplicación toda una clase de código que hasta ahora era obligatoria: detectar la caída, reintentar con retroceso exponencial, reconstruir el estado de sesión, decidir qué hacer con las escrituras en vuelo. Ese código no desaparece del universo, se muda a la biblioteca, que está en mejor posición para escribirlo porque ve las interfaces de red y las rutas candidatas.

El sistema tiene, según su propia documentación, exactamente dos piezas. La biblioteca corre dentro de tu aplicación y ofrece la interfaz para crear extremos, conectar con otros y abrir flujos de datos. Los relevos corren en segundo plano sobre servidores públicamente alcanzables, ayudan a los extremos a encontrarse y a establecer conexiones directas, y transportan datos como reserva cuando la conexión directa no es posible. Dos piezas, no diez; esa parquedad es intencional y la lección siguiente examina su economía.

Cómo encuentra iroh a un par

El arranque de un extremo es más interesante de lo que sugiere su brevedad en el código. Al iniciarse, el extremo está configurado con un conjunto de relevos y envía sondeos de descubrimiento de dirección sobre QUIC a todos ellos. Ese gesto único resuelve dos incógnitas a la vez: el extremo aprende cuál es su propia dirección pública y aprende qué relevo tiene más cerca por latencia. El más cercano se convierte en su relevo de casa, y el extremo mantiene abierta contra él una conexión segura por WebSocket. La documentación señala que iroh funciona bien con un solo relevo, pero funciona mejor con relevos en cada región donde haya usuarios.

Con varios relevos en juego aparece un problema nuevo: quien quiere hablar contigo necesita saber a qué relevo acudir. iroh ofrece dos formas de publicar y consultar ese dato. La primera, la que viene de serie, usa DNS: el extremo publica un registro firmado con su relevo de casa mediante una petición HTTPS de escritura contra un servidor de nombres, y cualquiera que quiera alcanzarlo resuelve ese registro con una consulta DNS o HTTPS. La segunda, opcional, publica exactamente el mismo registro firmado en la tabla distribuida de la red de intercambio de ficheros, usando su extensión de almacenamiento de valores firmados.

📝
Que el registro sea el mismo en ambos caminos es lo que hace honesta la elección

El detalle que convierte esta dualidad en algo más que una opción de configuración es que el registro publicado es idéntico en los dos casos: mismo contenido, misma firma. El servidor de nombres no es una autoridad que valide nada, es un almacén conveniente y rápido; la tabla distribuida es un almacén lento y sin dueño. Como la firma viaja con el dato, ninguno de los dos puede mentir sobre dónde está un par, solo puede negarse a responder o responder con algo caducado. Esa propiedad es la que te permite empezar con DNS por comodidad y migrar a la tabla distribuida cuando la independencia de infraestructura pase a importarte, sin cambiar el modelo de confianza ni una línea de la lógica de tu aplicación.

Falta el caso más frecuente en la vida real y el más fácil de olvidar: dos dispositivos en la misma red local. Ahí no hace falta perforar nada, solo que cada lado aprenda la dirección local del otro, y para eso existe el descubrimiento por multidifusión en la red local. Cuando la perforación sí hace falta, iroh la ejecuta dentro de la propia conexión QUIC, mediante una extensión llamada n0_nat_traversal inspirada en el borrador de perforación para QUIC pero con su propio identificador de parámetro de transporte.

flowchart TD
INI[el extremo arranca con una lista de relevos] --> QAD[sondeos de descubrimiento de direccion sobre QUIC]
QAD --> IP[aprende su propia direccion publica]
QAD --> CASA[elige el relevo mas cercano por latencia]
CASA --> WS[mantiene abierta una conexion segura por websocket]
CASA --> PUB[publica un registro firmado con su relevo de casa]
PUB --> DNS[servidor de nombres por escritura HTTPS]
PUB --> DHT[tabla distribuida con valores firmados]
WS --> HP[intercambio de direcciones y perforacion dentro de QUIC]
HP --> DIR[ruta directa validada]
HP --> REL[si falla la ruta sigue por el relevo]
style INI fill:#a6e3a1,color:#11111b
style CASA fill:#f9e2af,color:#11111b
style DIR fill:#89b4fa,color:#11111b
style REL fill:#cba6f7,color:#11111b

La pila: QUIC con TLS 1.3 y un enrutador por ALPN

Vista por capas, una aplicación de iroh es una pila corta donde cada nivel tiene un solo cometido. Abajo, el transporte lleva bytes cifrados entre máquinas: UDP es el valor por defecto y se puede sustituir por Tor, por una red de mezcla o por Bluetooth cuando el escenario lo pide. Encima, QUIC con TLS 1.3 aporta cifrado extremo a extremo, autenticación y multiplexado de flujos: los tres ejes que en una pila modular se negocian por separado quedan aquí resueltos de un golpe. Sobre él, el extremo es la interfaz de conexión, con su identificador estable, su localización de pares, su perforación y su reserva por relevo.

Las dos capas superiores son las que dan forma al código que escribes. El enrutador escucha sobre un extremo y despacha cada conexión entrante al manejador que corresponda según su cadena de negociación de protocolo de aplicación; es lo que permite que varios protocolos compartan un mismo extremo. Y los protocolos definen qué hacen dos pares una vez conectados: transferir ficheros, sincronizar documentos, difundir mensajes.

// Un solo extremo, dos protocolos, despacho por identificador de protocolo
use iroh::{protocol::Router, Endpoint};
use iroh_blobs::{store::mem::MemStore, BlobsProtocol};
use iroh_gossip::net::Gossip;

let endpoint = Endpoint::bind(presets::N0).await?;

let store = MemStore::new();
let blobs = BlobsProtocol::new(&store, None);
let gossip = Gossip::builder().spawn(endpoint.clone());

let _router = Router::builder(endpoint)
    .accept(iroh_blobs::ALPN, blobs)
    .accept(iroh_gossip::ALPN, gossip)
    .spawn();

Conviene detenerse en la línea que abre el extremo, porque en ella está toda la diferencia de filosofía con el nivel anterior. No hay lista de transportes, ni de cifrados, ni de multiplexores: hay una llamada que enlaza el extremo con un conjunto de valores por defecto. La elección no ha desaparecido, la ha tomado la biblioteca. Lo que sí queda explícito, y con razón, es qué protocolos aceptas: dos llamadas de aceptación y este nodo responde a transferencias de ficheros y a difusión por temas, cada una encaminada por su identificador.

Lo que ganas y lo que cedes

Lo que ganas es que el catálogo de decisiones de red se reduce a una: qué relevos usar. Todo lo demás —transporte, cifrado, multiplexado, localización, perforación, conmutación de ruta— está resuelto y probado en la biblioteca, que además existe como binario nativo ligero pensado para empotrarse en aplicaciones de escritorio, móviles, servidores, navegadores y sistemas empotrados, con enlaces para C, Swift, Python, JavaScript, Kotlin y Go además de Rust.

Lo que cedes es el eje que otras pilas te dejaban tocar. Si tu escenario exige un transporte que no está previsto, o un modelo de identidad distinto de una clave por dispositivo, o un mecanismo de localización propio, estás peleando contra el diseño en lugar de configurarlo. Es un intercambio nítido y no siempre favorable; lo importante es hacerlo con los ojos abiertos.

🔑

Identidad estable

La clave pública es la dirección. El dispositivo cambia de red y sigue siendo el mismo par, sin reconexión visible para la aplicación.

📡

Relevo de casa

Elegido por latencia con sondeos sobre QUIC al arrancar. Mismo gesto que revela tu dirección pública.

🧱

QUIC con TLS 1.3

Cifrado, autenticación y multiplexado en una sola capa. Los tres ejes que otras pilas negocian por separado.

🚦

Enrutador por protocolo

Varios protocolos sobre un mismo extremo, despachados por su identificador. Añadir capacidades es añadir aceptaciones.

Marcar por clave no es una comodidad de nombrado: es mover la seguridad al principio de la conversación

La forma habitual de presentar el modelo de iroh —marcas una clave en vez de una dirección— suena a azúcar sintáctico, a una capa de nombres bonita sobre el mismo problema de siempre. Vale la pena resistir esa lectura, porque debajo hay un desplazamiento que reordena todo el edificio de la seguridad. En el modelo clásico, la secuencia es: resuelvo un nombre, obtengo una dirección, abro una conexión con quien conteste en esa dirección y después compruebo mediante certificados si quien contestó era quien esperaba. La autenticación llega al final, es un veredicto sobre una conversación ya empezada, y toda la industria de autoridades de certificación existe para emitir ese veredicto en nombre de terceros en los que hay que confiar. Cuando la dirección es la clave, esa secuencia se invierte: no hay un momento en que autentiques, porque no hay forma de marcar sin haber declarado ya con quién quieres hablar. Un extremo que responde y no posee la clave privada correspondiente no es un impostor detectado a tiempo, es sencillamente alguien con quien la sesión no llega a establecerse; el intercambio de claves de TLS 1.3 dentro de QUIC no tiene con qué completarse. La suplantación no se detecta: deja de ser expresable. De ahí salen tres consecuencias que explican por qué esta decisión aparece tan temprano en el nivel. La primera es que desaparece la confianza en terceros para la identidad de los pares: no hay autoridad que emita nada, la clave se comparte por el canal que tú elijas —un billete, un código, una invitación— y ese canal es el único punto de confianza del sistema. La segunda es que la movilidad deja de ser un problema de seguridad: si la identidad no viaja en la dirección, cambiar de dirección no debilita nada y la biblioteca puede conmutar de ruta con total libertad, incluso a mitad de transferencia, porque ninguna decisión de seguridad estaba anclada a la ruta anterior. Y la tercera, la que más consecuencias tiene sobre la arquitectura de tu producto: el relevo puede ser una pieza tonta y desconfiable, porque no autentica a nadie, no guarda nada y no puede leer lo que transporta, ya que el cifrado es de extremo a extremo por construcción. Un intermediario al que no hay que confiarle nada es un intermediario que puede ser barato, replicable y desechable, y esa es exactamente la propiedad que la lección siguiente convierte en un argumento económico. Marcar por clave, en definitiva, no simplifica el nombrado: reorganiza quién tiene que confiar en quién, y esa reorganización es la que hace posible todo lo demás.

⚔️ Levanta dos extremos y observa cómo eligen su ruta
  1. Abre dos extremos en máquinas distintas y anota sus identificadores: comprueba que son estables entre reinicios si conservas la clave.
  2. Registra qué relevo eligió cada uno como relevo de casa y verifica que la elección se corresponde con la latencia medida.
  3. Conecta ambos en la misma red local y confirma que la ruta se establece sin intervención del relevo.
  4. Repite con los dispositivos en redes distintas y anota cuánto tarda la promoción de relevo a ruta directa.
  5. Cambia de red uno de los dispositivos a mitad de transferencia y comprueba si la conexión sobrevive a la conmutación.
  6. Añade un segundo protocolo al enrutador y verifica que ambos conviven sobre el mismo extremo sin abrir un puerto más.