- Especificación completa del protocolo (PROTOCOL.md) - Referencia de API (API.md) - Implementación crypto: SHA256, ChaCha20-Poly1305 - Device ID con Base32 y verificación Luhn32 - Framing de mensajes (HELLO, PING, DATA, etc.) - Discovery local UDP broadcast - Estructura de conexiones y estados - Build system para Zig 0.15.2 Pendiente: TLS 1.3, STUN, Global Discovery HTTPS, Relay 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
20 KiB
Protocolo zcatp2p - Especificación Técnica v1.0
1. Visión General
zcatp2p es un protocolo de comunicación P2P para intercambio seguro de mensajes entre nodos identificados criptográficamente. Diseñado para comunicación directa entre empresas (facturas, certificados) sin intermediarios.
1.1 Principios de Diseño
- Seguridad primero: Todo tráfico cifrado E2E con TLS 1.3
- Identificación por certificado: Device ID = SHA256(certificado)
- Descentralizado: Funciona sin servidor central (LAN)
- NAT-friendly: STUN + relay para conectividad universal
- Sin dependencias: Implementación completa en Zig puro
1.2 Componentes del Sistema
┌──────────────────────────────────────────────────────────────┐
│ NODO SIMIFACTU │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Discovery │ │ STUN │ │ Relay Client │ │
│ │ (local+ │ │ Client │ │ (fallback) │ │
│ │ global) │ │ │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
│ │ │ │ │
│ ┌──────┴────────────────┴────────────────────┴──────────┐ │
│ │ Connection Manager │ │
│ │ - Mantiene conexiones activas │ │
│ │ - Intenta conexión directa primero │ │
│ │ - Fallback a relay si falla │ │
│ └──────────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────┴────────────────────────────┐ │
│ │ TLS 1.3 Layer │ │
│ │ - Autenticación mutua por certificado │ │
│ │ - Device ID derivado de certificado │ │
│ └──────────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────┴────────────────────────────┐ │
│ │ Protocol Layer │ │
│ │ - Message framing │ │
│ │ - Compresión LZ4 opcional │ │
│ │ - Request/Response pattern │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
2. Identidad de Dispositivo (Device ID)
2.1 Generación
El Device ID es un identificador único de 32 bytes derivado del certificado TLS:
DeviceID = SHA256(DER_encoded_certificate)
2.2 Representación en String
Para facilitar el intercambio, el Device ID se representa como string Base32 con dígitos de verificación Luhn:
Formato: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX
└──7──┘ └──7──┘ └──7──┘ └──7──┘ └──7──┘ └──7──┘ └──7──┘ └──7──┘
Cada grupo de 7 caracteres incluye 1 dígito de verificación Luhn.
Total: 56 caracteres + 7 guiones = 63 caracteres
2.3 Algoritmo Luhn para Base32
Alfabeto Base32: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
function luhn32(s):
factor = 1
sum = 0
n = 32 // base
for char in reverse(s):
codepoint = base32_to_int(char)
addend = factor * codepoint
factor = (factor == 2) ? 1 : 2
addend = (addend / n) + (addend % n)
sum += addend
remainder = sum % n
check = (n - remainder) % n
return int_to_base32(check)
2.4 Certificado Auto-firmado
Cada nodo genera un certificado X.509 auto-firmado al inicializarse:
- Subject: CN=syncthing (o "zcatp2p" para nuestra implementación)
- Key Algorithm: ECDSA P-256 o Ed25519
- Validity: 20 años
- Serial: Random 128 bits
3. Protocolo de Transporte
3.1 Conexión TLS 1.3
Toda comunicación usa TLS 1.3 con autenticación mutua de certificados:
Cliente Servidor
│ │
│──────── ClientHello ────────────────>│
│ + key_share │
│ + supported_versions │
│ │
│<─────── ServerHello ─────────────────│
│ + key_share │
│ + EncryptedExtensions │
│ + CertificateRequest │
│ + Certificate │
│ + CertificateVerify │
│ + Finished │
│ │
│──────── Certificate ────────────────>│
│ + CertificateVerify │
│ + Finished │
│ │
│<═══════ Application Data ═══════════>│
│ │
3.2 Cipher Suites Soportadas
En orden de preferencia:
- TLS_CHACHA20_POLY1305_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_AES_128_GCM_SHA256
3.3 Verificación de Identidad
Después del handshake TLS, ambos extremos:
- Calculan SHA256 del certificado del peer
- Verifican que coincide con el Device ID esperado
- Si no coincide, cierran la conexión
4. Protocolo de Mensajes
4.1 Formato de Trama
Cada mensaje tiene el siguiente formato:
┌────────────────┬────────────────┬─────────────────────────────┐
│ Header (2B) │ Length (4B) │ Payload (variable) │
└────────────────┴────────────────┴─────────────────────────────┘
│ │ │
│ │ └── Datos del mensaje
│ └── Longitud del payload en big-endian
└── Tipo de mensaje (1B) + Flags (1B)
4.2 Tipos de Mensaje
Valor Nombre Descripción
───── ────── ───────────
0x00 HELLO Intercambio inicial de información
0x01 PING Keepalive
0x02 PONG Respuesta a PING
0x03 DATA Datos de aplicación
0x04 DATA_ACK Confirmación de recepción
0x05 CLOSE Cierre de conexión
0x06 ERROR Notificación de error
4.3 Flags
Bit Nombre Descripción
─── ────── ───────────
0 COMPRESSED Payload comprimido con LZ4
1 ENCRYPTED Payload cifrado (adicional a TLS)
2 REQUEST Espera respuesta
3 RESPONSE Es respuesta a REQUEST
4-7 Reserved Para uso futuro
4.4 Mensaje HELLO
Intercambiado inmediatamente después del handshake TLS:
┌─────────────────────────────────────────────────────────────┐
│ HELLO Message │
├─────────────────────────────────────────────────────────────┤
│ device_name_len: u8 │ Longitud del nombre │
│ device_name: [N]u8 │ Nombre del dispositivo │
│ client_name_len: u8 │ Longitud del cliente │
│ client_name: [N]u8 │ "simifactu" o "zcatp2p" │
│ client_version_len: u8 │ Longitud de versión │
│ client_version: [N]u8 │ Ej: "1.0.0" │
│ timestamp: i64 │ Unix timestamp │
│ capabilities: u32 │ Bitmap de capacidades │
└─────────────────────────────────────────────────────────────┘
Capacidades (bitmap):
Bit Capacidad
─── ─────────
0 COMPRESSION_LZ4
1 ENCRYPTION_CHACHA20
2 RELAY_SUPPORT
3 IPV6_SUPPORT
4.5 Mensaje DATA
┌─────────────────────────────────────────────────────────────┐
│ DATA Message │
├─────────────────────────────────────────────────────────────┤
│ message_id: u32 │ ID único para correlación │
│ content_type_len: u8 │ Longitud del tipo │
│ content_type: [N]u8 │ MIME type │
│ data_len: u32 │ Longitud de los datos │
│ data: [N]u8 │ Datos de aplicación │
└─────────────────────────────────────────────────────────────┘
Content types sugeridos para Simifactu:
application/x-simifactu-invoice- Facturaapplication/x-simifactu-certificate- Certificadoapplication/x-simifactu-verifactu- Datos Verifactu
4.6 Mensaje CLOSE
┌─────────────────────────────────────────────────────────────┐
│ CLOSE Message │
├─────────────────────────────────────────────────────────────┤
│ reason_len: u8 │ Longitud del motivo │
│ reason: [N]u8 │ Motivo del cierre │
└─────────────────────────────────────────────────────────────┘
5. Discovery Protocol
5.1 Local Discovery (LAN)
5.1.1 Formato de Anuncio
Enviado via UDP broadcast (IPv4) o multicast (IPv6):
Puerto: 21027
IPv4 Broadcast: 255.255.255.255:21027
IPv6 Multicast: [ff12::8384]:21027
┌─────────────────────────────────────────────────────────────┐
│ Local Announce Packet │
├─────────────────────────────────────────────────────────────┤
│ magic: u32 │ 0x2EA7D90B │
│ device_id: [32]u8 │ SHA256 del certificado │
│ instance_id: i64 │ ID único por ejecución │
│ num_addresses: u8 │ Número de direcciones │
│ addresses: [N]Address │ Lista de direcciones │
└─────────────────────────────────────────────────────────────┘
Address:
│ addr_len: u8 │ Longitud de la URL │
│ addr: [N]u8 │ URL (ej: "tcp://192.168.1.5:22000") │
5.1.2 Frecuencia de Anuncios
- Intervalo normal: 30 segundos
- Al detectar nuevo dispositivo: inmediatamente
- Caducidad del cache: 90 segundos
5.2 Global Discovery (Internet)
5.2.1 Servidor de Discovery
El servidor de discovery es un servicio HTTPS que:
- Recibe anuncios de dispositivos
- Responde consultas sobre dispositivos
Para funcionar en Internet, necesitas al menos un servidor de discovery. Puedes usar servidores públicos de Syncthing o ejecutar uno propio.
5.2.2 Anuncio (POST)
POST https://discovery.example.com/v2/
Content-Type: application/json
Authorization: (certificado TLS cliente)
{
"addresses": [
"tcp://203.0.113.45:22000",
"relay://relay.example.com:22067/?id=XXXXX"
]
}
El Device ID se extrae del certificado TLS del cliente.
5.2.3 Consulta (GET)
GET https://discovery.example.com/v2/?device=XXXXXXX-XXXXXXX-...
Response:
{
"addresses": [
"tcp://203.0.113.45:22000",
"relay://relay.example.com:22067/?id=XXXXX"
]
}
5.2.4 Headers de Control
Response Headers:
- Reannounce-After: 1800 // Segundos hasta próximo anuncio
- Retry-After: 60 // En caso de error
Status Codes:
- 200: OK
- 400: Bad Request
- 403: Forbidden (sin certificado)
- 404: Device not found
- 429: Too Many Requests
6. NAT Traversal
6.1 Estrategia de Conexión
Orden de intentos para conectar con un peer:
- Conexión directa TCP: Si tenemos IP directa
- Conexión directa con STUN: Usar IP externa descubierta
- Hole punching: Ambos peers intentan simultáneamente
- Relay: Último recurso si todo falla
6.2 STUN (Session Traversal Utilities for NAT)
Usamos STUN para descubrir nuestra IP externa y tipo de NAT.
6.2.1 Servidores STUN públicos
stun.l.google.com:19302
stun1.l.google.com:19302
stun.syncthing.net:3478
6.2.2 Tipos de NAT detectables
Tipo Hole-punchable? Descripción
──── ─────────────── ───────────
Full Cone Sí Puerto externo fijo
Restricted Cone Sí Requiere envío previo
Port Restricted Cone Sí + puerto específico
Symmetric No Puerto diferente por destino
6.3 Relay Protocol
Cuando no es posible conexión directa, usamos un servidor relay.
6.3.1 Conectarse al Relay
1. Cliente se conecta al relay via TLS
2. Envía JoinRelayRequest
3. Relay responde con SessionInvitation
4. Cliente A publica su dirección de relay en discovery
5. Cliente B consulta discovery, ve dirección relay
6. Cliente B conecta al relay, envía ConnectRequest(device_id_A)
7. Relay notifica a A, ambos reciben SessionInvitation
8. Relay hace de proxy transparente
6.3.2 Mensajes del Protocolo Relay
JoinRelayRequest:
│ token_len: u8 │
│ token: [N]u8 │ Token de autenticación (opcional)
ConnectRequest:
│ device_id: [32]u8 │ ID del dispositivo destino
SessionInvitation:
│ from: [32]u8 │ Device ID del peer
│ key: [32]u8 │ Clave de sesión
│ address: [16]u8 │ IP del relay
│ port: u16 │ Puerto
│ is_server: bool │ ¿Somos el "servidor"?
Response:
│ code: i32 │ 0=OK, 1=NotFound, 2=AlreadyConnected
│ message_len: u8 │
│ message: [N]u8 │
7. Cifrado Adicional (Opcional)
Además del cifrado TLS, se puede añadir cifrado a nivel de aplicación.
7.1 ChaCha20-Poly1305
Nonce size: 24 bytes (XChaCha20)
Tag size: 16 bytes
Key size: 32 bytes
Encrypted = Nonce || ChaCha20-Poly1305(Key, Nonce, Plaintext)
7.2 Derivación de Claves
Para cifrado de datos específicos:
FileKey = HKDF-SHA256(
IKM = FolderKey || filename,
salt = "zcatp2p",
info = empty
)
8. Compresión
8.1 LZ4
Usamos LZ4 para compresión de mensajes grandes (>128 bytes):
Compressed = u32_be(original_size) || LZ4_compress(data)
Solo comprimimos si el resultado es al menos 3% más pequeño.
9. Keepalive y Timeouts
Intervalo de PING: 90 segundos
Timeout de recepción: 300 segundos (5 min)
Timeout de conexión: 30 segundos
Timeout de close: 10 segundos
10. Puertos por Defecto
Puerto Uso
───── ───
22000 Conexiones P2P directas
21027 Local discovery (UDP)
22067 Relay server
22070 Relay status (HTTP)
443 Global discovery (HTTPS)
11. URLs de Direcciones
Formato Ejemplo
─────── ───────
tcp://host:port tcp://192.168.1.5:22000
tcp4://host:port tcp4://192.168.1.5:22000
tcp6://host:port tcp6://[::1]:22000
relay://host:port/?id=XXX relay://relay.example.com:22067/?id=ABCDEFG
12. Consideraciones de Seguridad
- Certificados: Generar con entropía suficiente
- Device ID: Verificar siempre después del handshake TLS
- Replay attacks: Usar timestamps y nonces únicos
- DoS: Limitar conexiones por IP, rate limiting
- Man-in-the-middle: Verificar Device ID conocidos
- Relay: El relay ve metadatos pero NO el contenido (TLS end-to-end)
13. Ejemplo de Flujo Completo
Empresa A quiere enviar factura a Empresa B
1. A tiene el Device ID de B (intercambiado previamente, ej: QR)
2. A busca a B:
a. Busca en cache local
b. Envía broadcast LAN
c. Consulta discovery global
3. A obtiene direcciones de B:
["tcp://203.0.113.45:22000", "relay://relay.example.com:22067/?id=B"]
4. A intenta conectar:
a. Intenta TCP directo → falla (NAT)
b. Intenta relay → éxito
5. Handshake TLS:
- A presenta certificado
- B presenta certificado
- Ambos verifican Device IDs
6. Intercambio HELLO:
- A envía sus capacidades
- B responde con las suyas
7. A envía factura:
DATA {
message_id: 1,
content_type: "application/x-simifactu-invoice",
data: <factura_serializada>
}
8. B confirma:
DATA_ACK { message_id: 1 }
9. Cierre:
A envía CLOSE { reason: "transfer complete" }
B cierra conexión