UC-018: Enviar Mensaje DIDComm
Especificación Funcional
Este caso de uso define el comportamiento previsto. El transporte de mensajes DIDComm aún no está integrado en la wallet — los mensajes actualmente solo se almacenan localmente.
Descripción
Desde una conversación activa en la wallet, el usuario compone y envía un mensaje de texto a otro usuario de wallet. La wallet cifra el mensaje usando cifrado autenticado DIDComm v2 (doble sobre: cifrado extremo a extremo interno para el destinatario, sobre de enrutamiento externo para el mediator), y lo entrega a través del servicio mediator. El destinatario recibe el mensaje en tiempo real si está online, o lo recupera más tarde de la cola offline del mediator. El mensaje enviado se almacena localmente en el historial de la conversación.
Actores
- Emisor (Usuario Final): Persona que compone y envía el mensaje desde su wallet
- Wallet (Frontend): Aplicación Svelte que proporciona la UI de chat y la composición de mensajes
- Wallet (Rust Backend): Comandos Tauri que realizan el empaquetado de mensajes DIDComm (cifrado JWE, firma JWS)
- Mediator: Servicio Go que enruta el mensaje cifrado al destinatario
Precondiciones
- El emisor tiene una identidad creada en la wallet (UC-001)
- La sesión de la wallet está desbloqueada
- La wallet está registrada en el mediator (UC-017)
- Existe una conversación activa con el destinatario (UC-020)
- El emisor conoce el DID del destinatario
Flujo Principal
- El usuario abre una conversación existente desde la pantalla de chat
- La wallet muestra la conversación con el historial de mensajes y el área de entrada
- El usuario escribe un mensaje en el campo de entrada del chat
- El usuario presiona Enter o pulsa el botón de enviar
- La wallet valida que el mensaje no está vacío tras recortar espacios
- La wallet crea un mensaje DIDComm v2 Basic Message:
id: UUID v4type:https://didcomm.org/basicmessage/2.0/messagefrom: DID del emisor (did:almena:<sender_public_key_hex>)to: DID del destinatario (did:almena:<recipient_public_key_hex>)body:{ "content": "<texto_del_mensaje>" }thid: ID de hilo (identificador de conversación:<sender_did>:<recipient_did>)created_time: timestamp UTC actual
- La wallet invoca el Rust backend para empaquetar el mensaje (sobre interno):
- Firma el mensaje en texto plano con la clave privada Ed25519 del emisor (JWS)
- Cifra el mensaje firmado para el destinatario usando ECDH-ES+A256KW con la clave pública X25519 del destinatario (JWE)
- El resultado es el sobre interno: solo el destinatario puede descifrarlo
- La wallet envuelve el sobre interno en un mensaje forward (sobre externo):
- Tipo:
https://didcomm.org/routing/2.0/forward to: el DID del mediatorbody.next: DID de enrutamiento del destinatario en el mediatorattachments: el sobre interno cifrado- Cifra el mensaje forward para el mediator usando la clave pública X25519 del mediator
- Tipo:
- La wallet envía el mensaje con doble cifrado al mediator vía
POST /didcomm - El mediator confirma la recepción (HTTP 202 Accepted)
- La wallet almacena el mensaje enviado localmente en el Tauri Store (
chat.json) bajo la conversación del destinatario - El mensaje aparece en la UI del chat como una burbuja enviada (alineada a la derecha, gradiente naranja) con el timestamp actual
- El campo de entrada del mensaje se limpia y el foco vuelve a él
Flujos Alternativos
FA-1: Mediator inaccesible
- En el paso 9, la petición de red al mediator falla (timeout, error de conexión)
- La wallet almacena el mensaje localmente con estado de entrega
pending - El mensaje aparece en la UI con un indicador de pendiente (icono de reloj)
- La wallet reintenta la entrega cuando se restablece la conexión con el mediator
- Tras un reintento exitoso, el estado se actualiza a
sent
FA-2: DID del destinatario no resoluble
- En el paso 7, la wallet no puede resolver el DID del destinatario para obtener su clave pública
- La wallet muestra un error: "No se puede cifrar el mensaje — clave del destinatario no disponible"
- El mensaje no se envía ni se almacena
FA-3: Mensaje vacío
- En el paso 5, el mensaje está vacío o solo contiene espacios en blanco
- El botón de enviar está deshabilitado y no se realiza ninguna acción
FA-4: Mensaje demasiado largo
- En el paso 5, el mensaje excede la longitud máxima (configurable, por defecto 4096 caracteres)
- La wallet muestra un aviso de contador de caracteres e impide el envío
FA-5: WebSocket disponible para entrega
- Si la conexión WebSocket con el mediator está activa, la wallet puede opcionalmente enviar el mensaje por WebSocket en lugar de HTTP POST
- El comportamiento es idéntico; el transporte es una optimización para menor latencia
Postcondiciones
- El mensaje cifrado ha sido entregado al mediator para su enrutamiento al destinatario
- El mediator retiene el mensaje para entrega en vivo o recogida offline
- El mensaje en texto plano se almacena localmente en el historial de conversación del emisor
- La UI del chat refleja el nuevo mensaje enviado con timestamp
- El campo de entrada del mensaje está limpio
Módulos Involucrados
| Módulo | Rol |
|---|---|
| wallet (frontend) | UI de chat, composición de mensajes, visualización de mensajes enviados, indicadores de estado de entrega |
| wallet (Rust backend) | Firma Ed25519 (JWS), acuerdo de claves X25519, cifrado JWE (sobres interno + externo), resolución de DID |
| mediator | Recepción de mensajes, enrutamiento al destinatario, entrega en vivo vía WebSocket o almacenamiento en cola offline |
Notas Técnicas
- Cifrado de doble sobre: El mensaje se cifra dos veces. El sobre interno (emisor → destinatario) proporciona cifrado extremo a extremo que el mediator no puede leer. El sobre externo (emisor → mediator) proporciona la información de enrutamiento. Este es el mecanismo estándar de reenvío de DIDComm v2
- Tipo de mensaje:
https://didcomm.org/basicmessage/2.0/message— el tipo de mensaje DIDComm más simple para chat de texto plano - Modelo de hilos: Cada conversación tiene un ID de hilo (
thid) formado por<initiator_did>:<responder_did>. Todos los mensajes en una conversación comparten el mismothid, permitiendo la agrupación de mensajes - Resolución de claves: El emisor debe obtener la clave pública X25519 del destinatario para cifrar el sobre interno. Puede resolverse desde el DID Document del destinatario (si está anclado en blockchain vía UC-003) o desde un intercambio de claves previo
- Algoritmos de cifrado: JWE con ECDH-ES+A256KW para envoltorio de claves y A256CBC-HS512 para cifrado de contenido (igual que la especificación del mediator)
- Almacenamiento local: Los mensajes enviados se almacenan en texto plano en el Tauri Store (
chat.json) indexados por DID del contacto. Solo la wallet del emisor tiene acceso al texto plano — el mediator solo ve sobres cifrados - Entrega offline: Si el destinatario no está conectado al mediator vía WebSocket, el mediator almacena el sobre interno cifrado hasta 72 horas. El destinatario lo recupera mediante Message Pickup 3.0 (UC-019)
- Limitación de tasa: El mediator impone 60 mensajes/minuto por DID. Si se excede, la wallet recibe HTTP 429 y debe aplicar backoff