Modelo de dominio¶
Estado: propuesta v0.4. Los tipos son conceptuales, no un esquema SQL ni un ABI congelados. Protocolo · Arquitectura.
English version: ../DOMAIN_MODEL.md
1. Vocabulario y propiedad¶
| Entidad | Identificador y datos esenciales | Autoridad / lugar |
|---|---|---|
| Identity | identity_id = hash(domain, version, root_public_key); root pública |
Raíz generada por la persona; secreto en cliente/kit |
| Device | ID aleatorio, claves públicas separadas, estado | Identidad autoriza; cliente conserva secretos |
| DeviceCredential | Raíz, device ID, claves, uso, validez, firma raíz | Verificación cliente; copia pública en directorio |
| DeviceManifest | Raíz, versión, hash anterior, credenciales activas/revocadas, firma | Raíz; máximos conocidos en cada cliente |
| Realm | ID ligado a la clave de firma; clave Noise vigente; política operativa | Operador; no autentica personas |
| RealmEndpointList | Secuencia, clave Noise del realm, endpoints con tipo, prioridad y caducidad, firma del realm | Operador publica; cada cliente conserva su máximo conocido |
| RealmMembership | Realm, identidad, rol, estado y cuota | Servidor autoriza uso de recursos |
| Invite | Hash de token aleatorio, caducidad, usos restantes, rol permitido | Realm, consumo atómico |
| KeyPackageRecord | Referencia MLS, bytes estándar, dispositivo, expiry, estado | Cliente genera/firma; relay distribuye |
| Mailbox | ID aleatorio, dispositivo propietario, límites y generación | Receptor administra; servidor conoce propietario |
| DeliveryCapability | Secreto aleatorio, mailbox, alcance, expiry y generación | Receptor comparte; servidor verifica hash |
| Envelope | Mailbox, ID aleatorio local a esa entrega, ciphertext, expiración | Relay guarda bytes; cliente interpreta |
| Blob | ID aleatorio, ciphertext, tamaño almacenado, estado y TTL | Cliente cifra; relay conserva temporalmente |
| Conversation | ID local/app, grupo MLS, título, política, roster de personas/dispositivos | Solo clientes y mensajes E2EE |
| GroupState | ID MLS, epoch, transcript/context, leaf propia y secretos activos | Biblioteca MLS en almacenamiento cifrado |
| MessageEvent | ID aleatorio, autor dispositivo, tipo, cuerpo, referencias | Cliente; validado contra autenticación MLS |
| RouteBundle | Device ID, relay por clave de firma, mailbox, capability, clave exterior, versión, firma | Publicado por receptor dentro de intercambio autenticado; sin URLs, que provienen del RealmEndpointList |
| RecoveryKit | Raíz privada cifrada, metadatos mínimos de recuperación | Custodia del usuario; distinto del backup del servidor |
| HistoryArchive | Registros exportados y adjuntos optativos, versión y manifiesto | Archivo cifrado; sin estados MLS activos |
Los hashes de identidad tienen separación de dominio y representación exacta versionada. No usar concatenaciones ambiguas ni nombres de usuario como claves de seguridad. IDs y capabilities emplean CSPRNG; propuesta: 256 bits para capabilities y 128 bits o más para IDs no secretos. Conocer un ID no autoriza acceso.
2. Relaciones¶
erDiagram
IDENTITY ||--o{ DEVICE : autoriza
IDENTITY ||--o{ REALM_MEMBERSHIP : participa
REALM ||--o{ REALM_MEMBERSHIP : admite
DEVICE ||--o{ MAILBOX : recibe
MAILBOX ||--o{ ENVELOPE : contiene
DEVICE ||--o{ KEY_PACKAGE : publica
CONVERSATION ||--o{ GROUP_LEAF : incluye
DEVICE ||--o{ GROUP_LEAF : representa
CONVERSATION ||--o{ MESSAGE_EVENT : conserva_localmente
El diagrama combina entidades locales y del servidor. CONVERSATION, GROUP_LEAF y MESSAGE_EVENT no son tablas del realm. Una misma persona puede pertenecer a varios realms con la misma identidad; eso facilita continuidad y permite correlación entre ellos. Se permite usar identidades distintas si el usuario desea separar contextos; no se fusionan automáticamente.
3. Ciclo de vida de claves¶
| Material | Uso y residencia | Rotación / pérdida |
|---|---|---|
| Raíz Ed25519 | Firma credenciales y manifiestos; guardada cifrada, desbloqueo explícito | Compromiso exige nueva raíz y reverificación; sin ella no hay continuidad fuerte |
| Clave de firma MLS por dispositivo | Leaf y mensajes MLS; solo core | Dispositivo nuevo obtiene clave nueva; no se clona |
| Clave Noise estática de dispositivo (X25519) | Iniciador del canal con el realm; prueba de posesión es el handshake | Rotable mediante credencial nueva firmada por la raíz; separada de firma MLS, HPKE exterior y raíz |
| Clave HPKE receptora exterior | Ocultar envoltorio/cabeceras por dispositivo | Rotar y conservar anterior solo durante ventana acotada de cola |
| Secretos de epochs y mensajes | Cifrado/descifrado MLS | Biblioteca evoluciona y elimina según política; no exportar para recuperar historial |
| Clave local de datos | Cifrar DB, índices y secretos persistidos | Envueltas mediante almacén del SO; migración transaccional al rotar |
| FileKey | Una clave aleatoria por archivo | Se distribuye dentro de MLS; no reutilizar entre archivos |
| Secreto del kit / secreto del archivo | Abrir recuperación de raíz / historial | Alta entropía, separados; perderlos impide abrir cada copia |
| Clave de firma del realm (Ed25519) | Identidad del servicio; firma listas de endpoints y clave Noise | Rotarla exige nuevo bootstrap de clientes; no cambia la raíz de usuarios |
| Clave Noise estática del realm (X25519) | Respondedor del canal | Se rota publicando lista nueva; la anterior se acepta durante una ventana acotada |
| Certificado TLS del endpoint | Atravesar proxies y middleboxes | Opcional; WebPKI o autofirmado; no participa en la seguridad del protocolo |
| Capabilities | Acceso limitado a mailbox/blob | Secreto revocable por alcance; guardado como hash donde sea verificable |
Una credencial de dispositivo vincula todas sus claves públicas con sus usos, el ID de dispositivo y la identidad. No reutilizar bytes de una clave Ed25519 como una clave X25519 de cifrado por conveniencia. El perfil de suites y codificaciones se fija en PROTOCOL.
4. Esquema lógico del servidor¶
Tablas propuestas: realm_memberships, invites, device_credentials, device_manifests, key_packages, mailboxes, capabilities, envelopes, blobs, push_subscriptions, endpoint_lists, schema_migrations. No hay tabla de sesiones: la sesión es el canal Noise en memoria, y la autorización se resuelve contra device_credentials al completar el handshake. Ninguna tabla contiene conversaciones.
Restricciones esenciales:
envelopeses única por(mailbox_id, delivery_id), con hash del cuerpo para detectar reintento conflictivo. Nunca usar un ID común de mensaje para todos los destinatarios.- ACK opera sobre IDs concretos y pertenencia al mailbox; no elimina indiscriminadamente todo lo anterior a un cursor del cliente.
- Un token de invitación tiene consumo y creación de membresía en una transacción.
- Los KeyPackages pasan por
available → consumedde forma atómica al reclamarlos. Una respuesta perdida puede desperdiciar un paquete; no lo vuelve a poner en circulación. Un relay malicioso aún puede reproducirlo y los clientes deben detectarlo donde tengan estado. mailboxes.owner_device_idy cuota son datos visibles: opacidad del ID no significa anonimato del dueño.- Capabilities de lectura, escritura y administración tienen alcances independientes. Hashes de secretos aleatorios de alta entropía, comparación segura, caducidad y revocación.
- Un envelope solo se confirma al remitente después de commit durable. El cuerpo no se modifica tras aceptación.
- Blobs completos son inmutables;
staging → committed → expired → deleted. GC ignora cargas activas y coordina snapshots. - Nada de cuerpos de mensaje, nombres de archivos originales, claves de recuperación, secretos MLS o claves locales.
El servidor puede guardar metadatos operativos mínimos, pero nunca se confía en ellos para autenticidad del contenido. El realm comprometido puede falsificar estados de consumo, expiry y ACK.
5. Estado local y atomicidad¶
La base cifrada contiene contactos y sus fingerprints verificados, máximos de manifiestos, grupos MLS, política, roster, eventos, outbox por destinatario, inbox/deduplicación, rutas y archivo local. Debe cubrir el almacenamiento del provider MLS, WAL, índices de búsqueda y temporales.
Unidad de envío: estado MLS posterior + evento local + bytes MLS producidos + intentos de entrega persistidos. Si el cifrado exterior ocurre después, parte de esos mismos bytes persistidos; jamás se vuelve a ejecutar el envío MLS desde un snapshot previo.
Unidad de recepción: marca de entrega procesada + cambio MLS + evento/recibo + trabajo pendiente. El ACK de transporte se emite después del commit. Los eventos de una epoch futura se pueden persistir como pendientes antes de ACK, pero no mostrarse como autenticados hasta poder validarlos.
No depender de que dos bases o dos conexiones autónomas hagan commit «casi a la vez». La prueba de viabilidad debe elegir un provider con transacción compartida o diseñar y revisar un journal durable con recuperación equivalente.
Los commits de control tienen estado persistente propio: prepared → selected → merged → welcome_released, con registro único de selección por epoch padre en el perfil del coordinador. No confundirlo con la outbox de mensajes de aplicación. Véase preparación y aceptación. La base local debe cumplir los requisitos de durabilidad.
6. Estados y transiciones¶
Entrega local, por destinatario¶
draft → queued_local → sealed_durable → relay_accepted → device_received
│ │ └→ read (optativo)
├→ retry_wait └→ expired_or_unknown
└→ failed_action_required
relay_accepted procede del servidor y puede ser falso si es malicioso. device_received exige recibo E2EE de ese dispositivo. read solo se produce si se habilitan recibos y nunca demuestra atención humana. El agregado de un grupo muestra pendientes/parciales; no convierte el ACK de un dispositivo en recepción de todos. Un dispositivo eliminado deja de contarse para futuros mensajes, sin reescribir la evidencia de los anteriores.
Los borradores se pueden editar libremente; un evento ya sellado es inmutable. Editar un mensaje enviado, si se añade después de V1, será otro evento E2EE con autorización y referencia explícitas.
Dispositivo¶
created_local → root_authorized → realm_registered → group_joined
└→ revoked └→ removed_from_group
Son ejes relacionados, no un estado global único: un dispositivo puede estar registrado y no pertenecer a ningún grupo, o retirado del realm pero conservar viejos mensajes. revoked es terminal para esa credencial; recuperar genera nuevo ID y claves.
Grupo¶
creating → active → awaiting_commit → active
├→ needs_resync → fresh_rejoin
└→ fork_suspected → paused → verified_new_group
No se fusionan forks de secretos MLS con reglas de «última escritura gana». El historial viejo puede permanecer legible mientras se suspende envío al grupo afectado.
7. Invariantes de dominio¶
- La identidad no depende del nombre, hostname o membresía en el servidor.
- Una firma válida acredita quién autorizó bytes; no implica frescura ni autorización de cualquier acción.
- Cada leaf se vincula a un dispositivo autorizado; una persona puede ocupar varias leaves.
- El servidor no añade dispositivos a un grupo por modificar su directorio.
- Los clientes no retroceden versiones conocidas ni resucitan estados MLS desde backups.
- Recuperar identidad, recuperar historial y reingresar en grupos son operaciones separadas.
- Una eliminación local o un TTL no prometen borrado remoto de copias.
- La UI deriva sus estados de hechos locales, aceptación de relay y recibos autenticados por separado.
Referencias y alcance de revisión: README.
Persistencia de invitaciones personales (implementada)¶
Esquema 5 del relay añade issued_invitations, invitation_audit y
key_package_claim_receipts; owner autoriza invitaciones y se asigna
expresamente desde el host. Perfil 8 añade invitation_operations e
invitation_hellos. Enlace/token/secreto pendiente viven cifrados en el perfil,
no en preferencias Flutter. MLS, conversación, outbox y vínculo invitación→grupo
comparten transacción. Véase ciclo de vida y retención.