Bots

Lo encuentras como a una persona.

Un bot de PrivChat es una cuenta automática: detrás no hay una persona con la app, sino un programa que alguien —su operador— tiene corriendo en un ordenador suyo, siempre encendido. Como aquí no hay servidores de nadie, tampoco hay directorio de bots, ni nombres con arroba, ni API en Internet: a un bot se le encuentra con su invitación, como a una persona. Esta página tiene dos mitades: la primera es para cualquiera que use la app; de «Montar un bot» hacia abajo, para operadores y programadores.

Un ordenador pequeño encendido sobre una mesa a oscuras.
Para usar un bot no hace falta nada de esto Qué es un bot aquí Lo que un bot ve, y lo que no Montar un bot Parámetros Canales desde el bot Contactos 1:1 El bot como Buzón de casa Programar un bot: la API local Reglas que un bot debe respetar Problemas frecuentes

01

Para usar un bot no hace falta nada de esto

Usar un bot es usar la app de siempre. Si alguien te pasa la invitación de un bot (un texto o un QR, normalmente publicados en su web):

  • Si es un canal: Nuevo → Unirse a un canal, pegar el texto de la invitación (empieza por UEND) o escanear su QR, y Unirse. Dentro, Actualizar trae las publicaciones; el primer sondeo puede tardar un par de minutos hasta que la dirección del canal esté publicada en Tor.
  • Si es un contacto: Nuevo → Nuevo contacto, pegar la invitación o escanear el QR animado del bot, poner un nombre y Añadir. El primer mensaje lleva el saludo cifrado; cuando llega, el bot te tiene como contacto y contesta lo que su programa diga.
  • Verificarlo: dentro del chat, Más → Verificar en persona → Pegar su código, con el código que el operador publique firmado en su web.

Y esto es lo que un bot no puede hacer, por diseño:

  • No puede leer nada que no le mandes tú a él. Tus conversaciones, tus grupos y tus canales no los ve ni sabe que existen.
  • No puede meterse donde no lo inviten. En un grupo entra solo si el administrador lo presenta, como cualquier miembro; y no puede publicar en un canal que no sea el suyo: cada canal solo lo escribe quien tiene su clave.
  • No sabe quién lee su canal. Los lectores recogen las publicaciones por circuitos anónimos, sin identificarse jamás.
  • No hay bots «oficiales» de PrivChat. Cada bot responde por su operador, que es quien recibe lo que le escribas. Si te importa quién hay detrás, verifícalo con su código publicado.

02

Qué es un bot aquí

  • Un peer más. Tiene su propia identidad, su propio servicio onion de Tor, su base cifrada y sus canales. Es el motor de la aplicación compilado sin Android.
  • Indistinguible en el cable. Mismos sobres, mismo cifrado (Double Ratchet y PQXDH), mismo relleno, mismos canales. La aplicación no sabe que habla con un programa, porque no hay forma de saberlo.
  • Sin directorio. Un directorio central sería el servidor que esta aplicación existe para no tener. El operador reparte la invitación de su bot donde quiera: una web, un cartel, un mensaje.
  • API local. Tu programa (Python, Go, shell, lo que sea) habla con el bot por un socket Unix de su directorio, en JSON. Lo remoto lo pone Tor.
  • Dos usos, en este orden. Un canal: el bot publica y cualquiera lee, anónimamente. Y 1:1: la gente le escribe y el bot contesta.

03

Lo que un bot ve, y lo que no

De los lectores de su canal, nada. Un lector recoge las publicaciones por un circuito anónimo y sin identificarse; el bot ve que alguien leyó, cuándo y cuánto, y no puede ver más porque el protocolo no tiene dónde ponerlo. Un canal de mil lectores sabe de ellos lo mismo que uno de diez.

De quien le escribe, lo que ve cualquier contacto. Escribir a un bot es darle a su operador tu identificador, tu dirección de Tor de vuelta y lo que le escribas. Con un contacto humano eso está repartido; con un bot popular está concentrado en un servidor. Por eso los canales van primero, y por eso el bot no guarda historial: un mensaje entrante vive hasta que el programa del operador lo confirma, con un tope de siete días.

Si el servidor del bot cae en malas manos, se llevan la identidad del bot (y con ella pueden suplantarlo para siempre: no hay revocación), las claves de sus canales y su lista de contactos. No se llevan nada de ningún usuario ni de ningún lector. Se recupera destruyendo el bot, creando otro y publicando su huella nueva fuera de banda.

Nada te dice que un contacto es un bot, ni que es el bot que dice ser. Un operador serio publica el código de verificación de su bot firmado en su web; en la app, dentro del chat, Más → Verificar en persona → Pegar su código (o escanear su QR) lo comprueba como con una persona.

04

Montar un bot

Esto ya es cosa de operadores. Hace falta un Linux tuyo —un servidor o una Raspberry Pi 4 o 5 de 64 bits—, Java 17 o superior (sudo apt install openjdk-17-jre-headless), unos 300 MB de memoria libre y salida a Tor. El teléfono no puede hacer de bot: un bot tiene que estar siempre encendido y siempre en la red, y eso es un ordenador del operador, no un móvil.

El paquete: privchat-bot-2.19.2.zip (trae los scripts, las bibliotecas y el arti nativo; Linux x86_64 — Windows solo para desarrollar). El paquete para Raspberry (aarch64) se publicará cuando esté probado en la placa real, no antes.

SHA-256 del paquete:

18d98f258b9206dd2be9f8e89bd19b18db5fb55eee53ee8fc6e7745573f8f6fe
sudo unzip privchat-bot-2.19.2.zip -d /opt && sudo mv /opt/privchat-bot* /opt/privchat-bot
sudo useradd --system --home /var/lib/privchat-bot --shell /usr/sbin/nologin privchat-bot
sudo install -d -o privchat-bot -g privchat-bot -m 0700 /var/lib/privchat-bot

# Crear el bot: sello, identidad, prekeys, base. Enseña la passphrase UNA vez.
sudo -u privchat-bot /opt/privchat-bot/bin/privchat-bot --datos /var/lib/privchat-bot init --passphrase-generada

# La passphrase, en una credencial que solo root lee (nunca en una variable de entorno)
sudo install -d -m 0700 /etc/privchat-bot
sudo install -m 0600 /dev/stdin /etc/privchat-bot/passphrase <<< 'la-passphrase-que-enseñó-init'

# El servicio
sudo cp /opt/privchat-bot/etc/privchat-bot.service /etc/systemd/system/
sudo systemctl enable --now privchat-bot

La passphrase es toda la defensa contra quien copie el disco. Un servidor no tiene el Keystore del teléfono, y fingirlo sería peor que decirlo. Larga o generada; con TPM 2.0, ligada a la máquina con systemd-creds encrypt --with-key=tpm2 y LoadCredentialEncrypted= en la unidad; sin TPM, el fichero 0600 sobre un disco cifrado. El bot no tiene PIN ni bloqueo de pantalla: no hay nadie tecleando delante.

05

Parámetros

opciónqué hacepor defecto
--datos DIRdirectorio del bot./privchat-bot
--credencial FICHEROpassphrase en un fichero 0600la de systemd, o la consola
--socket RUTAsocket Unix de la API (ruta corta: AF_UNIX admite ~100 bytes)DIR/api.sock
--api-tcp PUERTOademás, API en 127.0.0.1 con tokenapagada
--api-grupo GRUPOel socket también lo abre ese grupo (0660)solo el usuario (0600)
--retencion-dias Ndías que vive un mensaje entrante sin confirmar7 (máximo 30)
--max-contactos Ntope de contactos por invitaciones públicas1000
--sin-rellenoapaga el tráfico de rellenoencendido, como en la app
--buzonhace de Buzón de su dueño: la red local de «En persona» además de Torapagado
--bluetoothcon --buzon, también por Bluetooth LE (solo Linux, BlueZ)apagado
--malla PUERTOla malla LoRa: un nodo Meshtastic por USB (/dev/ttyACM0, COM4) o por TCP (host:4403); los textos salen por radio cuando Tor no entrega (bot 2.16.1)apagado
--verbosoregistro detallado, con los de Tor. Solo para depurarapagado

Sin demonio: init, passphrase cambiar, copia-seguridad DESTINO.zip (para restaurar en otra máquina, nunca para tener dos bots iguales), restaurar ORIGEN.zip y destruir --si. El registro nunca lleva direcciones onion ni identificadores enteros, y ninguna lectura de canal atendida deja línea.

06

Canales desde el bot

B="sudo -u privchat-bot /opt/privchat-bot/bin/privchat-bot --datos /var/lib/privchat-bot"
$B canal crear "Avisos del pueblo" "Cortes de agua y fiestas"    # devuelve canal_id
$B canal invitacion <canal_id> --png /var/www/avisos.png          # el texto pegable, y el QR en el PNG
$B canal publicar <canal_id> "Mañana sin agua de 9 a 12"
$B canal publicar-foto <canal_id> /ruta/cartel.jpg "El cartel"    # se recomprime: el EXIF muere aquí
$B canal listar                                                   # publicaciones, sondeos y bytes servidos
$B canal recortar <canal_id> 50                                   # deja solo las 50 últimas

Cada publicación va firmada por la clave del canal, cifrada con su clave de lectura y encadenada a la anterior: se puede dejar de servir historia, no reescribirla sin que los lectores lo noten. Tope de 12 KB por texto; una foto se recomprime hasta caber en 900 KB.

07

Contactos 1:1

Dos clases de invitación, con el mismo formato que la de la app:

personalpública (--publica)
quién la usauna persona: la primera; la segunda se rechazacualquiera, hasta --max-contactos
prekeys de un usocinco, como en la appninguna: repartidas entre miles no serían de un uso
lo que cuestanada nuevoel primer mensaje de cada usuario al bot queda sin la protección extra de la prekey frente a un compromiso futuro de las claves del bot; el ratchet cura en cuanto el bot contesta
para quéun asistente para ti, un aviso a tu familiaun bot abierto al público
$B invitacion crear web --publica                        # el texto pegable
$B invitacion crear web --publica --gif /var/www/bot.gif  # y los cuatro QR de la invitación en un GIF animado
$B verificacion codigo --png /var/www/verificacion.png    # publícalo firmado: así se sabe que el bot es EL bot
$B mensaje escuchar --reponer                             # eventos JSON, uno por línea
$B mensaje enviar <peer_id> "Hola"
$B mensaje enviar <peer_id> "Hola" --retardo 3000        # sale en un momento al azar de los próximos 3 s
$B mensaje confirmar <msg_id>                             # el bot lo olvida

08

El bot como Buzón de casa

Con --buzon, el bot hace de Buzón de una persona, su dueño: en una Raspberry o un ordenador de casa, guarda y entrega sus mensajes cuando no está. Quien pasa por esa wifi deja lo que tenga para el dueño y recoge lo que el dueño le dejó; y si el dueño enciende «Con internet» en su teléfono, sus contactos también le dejan mensajes por Tor con el teléfono apagado. En la unidad de systemd, añade --buzon a ExecStart, antes de run.

$B buzon invitar                        # el texto se pega en el teléfono: Ajustes → Mi Buzón; el primero que la usa es el dueño
$B buzon estado                         # el dueño y lo que guarda

Bluetooth (solo Linux, opcional). Con --buzon --bluetooth el Buzón también se deja encontrar por Bluetooth LE: quien pase por la puerta con «Hablar en persona» encendido para el dueño deja y recoge sin unirse a la wifi. Hace falta BlueZ en marcha, un adaptador, el usuario del servicio en el grupo bluetooth y, en /etc/bluetooth/main.conf, sección [General], ReverseServiceDiscovery = false (y reiniciar el servicio bluetooth): sin eso, al teléfono le sale un aviso de vinculación y el canal no arranca. Ese ajuste apaga también el cliente GATT de BlueZ para otros aparatos Bluetooth LE, así que mejor en un aparato dedicado. Va apagado si no se pide porque el anuncio dice a quien escanee desde la calle que ahí hay un PrivChat, aunque no quién.

Guarda solo lo del dueño, cerrado, y las claves del día de los contactos que el dueño permita: nunca su identidad. Si te lo roban, no lee nada y en unas 48 horas deja de reconocer a nadie; se desempareja desde el teléfono. Su dirección .onion es fija desde el primer arranque, aunque no haya tenido internet nunca. Cómo se ve en el teléfono, en la guía.

09

Programar un bot: la API local

Una petición o respuesta por línea, JSON, sobre el socket. La autenticación es el sistema de ficheros: quien puede abrir el socket es quien puede leer el directorio del bot. Los eventos llegan por la misma conexión después de suscribir.

→ {"id": 1, "op": "estado"}
← {"id": 1, "ok": true, "peer_id": "…", "huella": "…", "onion": "….onion", "tor": "online", "contactos": 3, "canales": 1}
→ {"id": 2, "op": "canal.publicar", "canal_id": "…", "texto": "Buenos días"}
← {"id": 2, "ok": true, "seq": 42}
← {"evento": "mensaje.recibido", "peer_id": "…", "msg_id": "…", "texto": "hola", "fecha": 1788500000}
operaciónparámetrosdevuelve
estado—identidad, onion, estado de Tor, contadores
suscribirreponereventos[] con lo no confirmado; después, eventos en vivo
invitacion.crearetiqueta, publica, qrinvite_id, texto, qr_gif
contacto.listar / renombrar / borrarpeer_id…—
verificacion.codigo / comprobarpeer_id, codigotexto y QR / resultado
mensaje.enviarpeer_id, texto, respuesta_a, retardo_msmsg_id
adjunto.enviar / adjunto.leerpeer_id, nombre, base64 o ruta / msg_idmsg_id / bytes
mensaje.estado / mensaje.confirmarmsg_idestado / — (se borra)
canal.crear / invitacion / publicar / publicar_foto / recortar / borrarcanal_id, texto, pie…seq, texto y QR…

Eventos: mensaje.recibido, adjunto.recibido, contacto.nuevo, entregado, tor. Son al menos una vez: un mensaje se repone en cada suscribir hasta que lo confirmes, y el msg_id te deja deduplicar. Errores con código estable (sin_sesion, invitacion_invalida, demasiado_grande…); un error interno nunca lleva detalle al cliente.

Un bot de eco entero, en Python y sin dependencias:

import json, socket
s = socket.socket(socket.AF_UNIX); s.connect("/var/lib/privchat-bot/api.sock")
f = s.makefile("rw", encoding="utf-8")
def llamar(**p):
    f.write(json.dumps(p) + "\n"); f.flush(); return json.loads(f.readline())
r = llamar(id=1, op="suscribir", reponer=True)
for ev in list(r["eventos"]) + [json.loads(l) for l in f]:
    if ev.get("evento") == "mensaje.recibido":
        llamar(id=2, op="mensaje.enviar", peer_id=ev["peer_id"], texto="eco: " + ev["texto"], respuesta_a=ev["msg_id"])
        llamar(id=3, op="mensaje.confirmar", msg_id=ev["msg_id"])

Con el paquete van dos ejemplos completos: eco.py (separa las respuestas de los eventos que se cruzan, que es lo que hay que hacer en cuanto un bot contesta mientras escucha) y rss_a_canal.py, que publica en un canal cada entrada nueva de un RSS o Atom.

10

Reglas que un bot debe respetar

  • Confirma lo que procesas; lo que no confirmas se te repone y a los siete días se borra.
  • No descargues lo que te enlacen: un bot al que se le puede hacer pedir cualquier URL es un bot que alguien usará para eso.
  • Trata el texto como datos: viene de desconocidos.
  • Límites: 12 KB por texto de canal, 7,5 MB por adjunto, 900 KB por foto de canal, 16 MB por línea de la API.
  • Escala: cientos de contactos 1:1, sí; miles, por canal.
  • Un onion que no se apaga nunca se rastrea hasta su guardia con más facilidad que un teléfono; por eso el bot lleva vanguards, y por eso no debe correr junto a cosas que no quieras relacionar con él.

11

Problemas frecuentes

síntomacausa probablequé hacer
tor: arrancando durante minutossin red, o un antivirus o proxy que intercepta TLSapagar la intercepción; con --verboso lo dice Tor
«Unix domain path too long»la ruta del socket pasa de ~100 bytes--socket /tmp/pb.sock
el primer sondeo desde la app no trae nadala dirección del canal aún no está publicada en Toresperar uno o dos minutos y Actualizar
un mensaje se queda «en cola» un ratoel circuito de rendezvous tardaesperar; el bot confirma en cuanto llega
contacto listar no muestra a quien acaba de añadir al botel bot aprende del contacto con su primer mensajeque escriba algo
sin_sesion al enviarese peer_id no es contacto todavíacontacto listar