01
Чтобы пользоваться ботом, ничего из этого не нужно
Пользоваться ботом — значит просто пользоваться приложением. Если вам передали приглашение бота (текст или QR, обычно опубликованные на его сайте):
- Если это канал: Новый → Присоединиться к каналу, вставить текст приглашения (начинается с
UEND) или отсканировать его QR, и Присоединиться. Внутри Обновить забирает записи; первый опрос может занять пару минут, пока адрес канала не опубликован в Tor. - Если это контакт: Новый → Новый контакт, вставить приглашение или отсканировать анимированный QR бота, дать имя и Добавить. Первое сообщение несёт зашифрованное рукопожатие; когда оно доходит, бот видит вас как контакт и отвечает то, что велит его программа.
- Проверить его: в чате, Ещё → Проверить лично → Вставить его код — код оператор публикует с подписью на своём сайте.
А вот чего бот не может — по устройству протокола:
- Не может прочитать ничего, что вы не отправили ему сами. Ваши переписки, группы и каналы он не видит и не знает об их существовании.
- Не может войти туда, куда его не звали. В группу он попадает только если администратор представит его, как любого участника; и не может публиковать в чужой канал: в каждый канал пишет только владелец его ключа.
- Не знает, кто читает его канал. Читатели забирают записи через анонимные цепочки, никогда не называя себя.
- «Официальных» ботов PrivChat не существует. Каждый бот отвечает перед своим оператором — именно он получает то, что вы пишете боту. Если вам важно, кто за ним стоит, проверьте его по опубликованному коду.
02
Что здесь называется ботом
- Просто ещё один узел. У него своя личность, свой onion-сервис Tor, своя зашифрованная база и свои каналы. Это движок приложения, собранный без Android.
- Неотличим на линии. Те же конверты, то же шифрование (Double Ratchet и PQXDH), тот же маскирующий трафик, те же каналы. Приложение не знает, что говорит с программой, потому что узнать это невозможно.
- Без каталога. Центральный каталог был бы тем самым сервером, ради отсутствия которого существует это приложение. Оператор раздаёт приглашение своего бота где хочет: на сайте, на плакате, в сообщении.
- Локальный API. Ваша программа (Python, Go, shell — что угодно) общается с ботом через Unix-сокет в его каталоге, в JSON. Удалённую часть обеспечивает Tor.
- Два применения, именно в этом порядке. Канал: бот публикует, а читает кто угодно, анонимно. И личная переписка: люди пишут боту, бот отвечает.
03
Что бот видит, а чего нет
О читателях своего канала — ничего. Читатель забирает записи через анонимную цепочку, не называя себя; бот видит, что кто-то читал, когда и сколько, и не может увидеть больше, потому что в протоколе для этого нет места. Канал с тысячей читателей знает о них ровно столько же, сколько канал с десятью.
О том, кто ему пишет — то же, что видит любой контакт. Написать боту значит передать его оператору ваш идентификатор, ваш обратный адрес Tor и то, что вы пишете. У контакта-человека это рассеяно; у популярного бота — сосредоточено на одном сервере. Поэтому каналы идут первыми, и поэтому бот не хранит историю: входящее сообщение живёт, пока программа оператора его не подтвердит, но не дольше семи дней.
Если сервер бота попадёт в плохие руки, заберут личность бота (а с ней возможность выдавать себя за него навсегда: отзыва нет), ключи его каналов и список контактов. Не заберут ничего ни у одного пользователя и ни у одного читателя. Восстановление — уничтожить бота, создать нового и опубликовать его новый отпечаток по другому каналу.
Ничто не скажет вам, что контакт — бот, и что это именно тот бот, за которого он себя выдаёт. Серьёзный оператор публикует код проверки своего бота с подписью на своём сайте; в приложении, в чате, Ещё → Проверить лично → Вставить его код (или отсканировать его QR) проверяет его так же, как человека.
04
Запуск бота
Эта часть — для операторов. Нужен собственный Linux — сервер или 64-битная Raspberry Pi 4 или 5, — Java 17 или новее (sudo apt install openjdk-17-jre-headless), около 300 МБ свободной памяти и выход в Tor. Телефон ботом быть не может: бот должен быть всегда включён и всегда в сети, а это компьютер оператора, не мобильный.
Пакет: privchat-bot-2.19.2.zip (скрипты, библиотеки и нативный arti; Linux x86_64 — Windows только для разработки). Пакет для Raspberry (aarch64) будет опубликован после проверки на настоящей плате, не раньше.
SHA-256 пакета:
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
# Создать бота: печать, личность, prekeys, база. Парольная фраза показывается ОДИН раз.
sudo -u privchat-bot /opt/privchat-bot/bin/privchat-bot --datos /var/lib/privchat-bot init --passphrase-generada
# Парольная фраза — в учётных данных, которые читает только root (никогда в переменной окружения)
sudo install -d -m 0700 /etc/privchat-bot
sudo install -m 0600 /dev/stdin /etc/privchat-bot/passphrase <<< 'фраза-которую-показал-init'
# Служба
sudo cp /opt/privchat-bot/etc/privchat-bot.service /etc/systemd/system/
sudo systemctl enable --now privchat-bot
Парольная фраза — вся защита от того, кто скопирует диск. У сервера нет Keystore телефона, и делать вид, что есть, было бы хуже, чем сказать об этом. Длинная или сгенерированная; с TPM 2.0 — привязанная к машине через systemd-creds encrypt --with-key=tpm2 и LoadCredentialEncrypted= в юните; без TPM — файл 0600 на зашифрованном диске. У бота нет PIN и нет блокировки экрана: перед ним никто не печатает.
05
Параметры
| опция | что делает | по умолчанию |
|---|---|---|
--datos DIR | каталог бота | ./privchat-bot |
--credencial FILE | парольная фраза в файле 0600 | учётные данные systemd или консоль |
--socket PATH | Unix-сокет API (короткий путь: AF_UNIX допускает ~100 байт) | DIR/api.sock |
--api-tcp PORT | дополнительно API на 127.0.0.1 с токеном | выключено |
--api-grupo GROUP | сокет может открывать и эта группа (0660) | только пользователь (0600) |
--retencion-dias N | сколько дней живёт неподтверждённое входящее сообщение | 7 (максимум 30) |
--max-contactos N | предел контактов по публичным приглашениям | 1000 |
--sin-relleno | выключает маскирующий трафик | включён, как в приложении |
--buzon | работает как почтовый ящик владельца: локальная сеть «Лично» вдобавок к Tor | выключен |
--bluetooth | с --buzon — ещё и по Bluetooth LE (только Linux, BlueZ) | выключен |
--malla ПОРТ | сеть LoRa: узел Meshtastic по USB (/dev/ttyACM0, COM4) или по TCP (host:4403); тексты уходят по радио, когда Tor не доставляет (бот 2.16.1) | выключен |
--verboso | подробный журнал, включая журнал Tor. Только для отладки | выключено |
Без демона: init, passphrase cambiar, copia-seguridad DEST.zip (чтобы восстановить на другой машине, но никогда — чтобы держать двух одинаковых ботов), restaurar SOURCE.zip и destruir --si. В журнале никогда нет onion-адресов и полных идентификаторов, и ни одно обслуженное чтение канала не оставляет строки.
06
Каналы со стороны бота
B="sudo -u privchat-bot /opt/privchat-bot/bin/privchat-bot --datos /var/lib/privchat-bot"
$B canal crear "Объявления посёлка" "Отключения воды и праздники" # возвращает canal_id
$B canal invitacion <canal_id> --png /var/www/avisos.png # текст для вставки, и QR в PNG
$B canal publicar <canal_id> "Завтра нет воды с 9 до 12"
$B canal publicar-foto <canal_id> /path/afisha.jpg "Афиша" # пережимается: EXIF умирает здесь
$B canal listar # записи, опросы и отданные байты
$B canal recortar <canal_id> 50 # оставить только последние 50
Каждая запись подписана ключом канала, зашифрована его ключом чтения и сцеплена с предыдущей: историю можно перестать отдавать, но нельзя переписать незаметно для читателей. До 12 КБ на текст; фото пережимается, пока не уместится в 900 КБ.
07
Личные контакты
Два вида приглашений, в том же формате, что и в приложении:
| личное | публичное (--publica) | |
|---|---|---|
| кто использует | один человек: первый; второго отклоняют | кто угодно, до --max-contactos |
| одноразовые prekeys | пять, как в приложении | нет: розданные тысячам, они не были бы одноразовыми |
| цена | ничего нового | первое сообщение каждого пользователя боту остаётся без дополнительной защиты prekey от будущей компрометации ключей бота; ratchet исцеляется, как только бот ответит |
| для чего | помощник для себя, уведомления для семьи | бот, открытый для всех |
$B invitacion crear web --publica # текст для вставки
$B invitacion crear web --publica --gif /var/www/bot.gif # плюс четыре QR приглашения в анимированном GIF
$B verificacion codigo --png /var/www/verificacion.png # опубликуйте с подписью: так узнают, что бот — ТОТ САМЫЙ бот
$B mensaje escuchar --reponer # события JSON, по одному на строку
$B mensaje enviar <peer_id> "Привет"
$B mensaje enviar <peer_id> "Привет" --retardo 3000 # уйдёт в случайный момент в ближайшие 3 с
$B mensaje confirmar <msg_id> # бот забывает сообщение
08
Бот как домашний почтовый ящик
С --buzon бот становится почтовым ящиком
одного человека — своего владельца: на Raspberry или домашнем компьютере он
хранит и передаёт его сообщения, пока того нет. Тот, кто оказался в этой сети
Wi‑Fi, оставляет то, что у него для владельца, и забирает то, что владелец
оставил ему; а если владелец включит «С интернетом» на телефоне, его контакты
смогут оставлять сообщения и через Tor, пока телефон выключен. В юните systemd
добавьте --buzon в ExecStart перед
run.
$B buzon invitar # текст вставляется в телефоне: Настройки → Мой ящик; первый, кто его использует, — владелец
$B buzon estado # владелец и что хранится
Bluetooth (только Linux, по желанию). С --buzon
--bluetooth ящик можно найти и по Bluetooth LE: тот, кто проходит мимо
двери с включённым для владельца режимом «Поговорить лично», оставляет и
забирает сообщения, не подключаясь к Wi‑Fi. Нужны запущенный BlueZ, адаптер,
пользователь службы в группе bluetooth и в
/etc/bluetooth/main.conf, раздел [General],
ReverseServiceDiscovery = false (затем перезапустить службу
bluetooth): без этого телефон показывает запрос на сопряжение и
канал не открывается. Этот параметр отключает и GATT-клиент BlueZ для других
устройств Bluetooth LE, поэтому лучше на отдельном устройстве. По умолчанию
выключено: объявление сообщает любому, кто сканирует с улицы, что здесь есть
PrivChat, хотя и не чей.
Хранит только сообщения владельца, в запечатанном виде, и ключи дня тех
контактов, которым владелец разрешил: никогда не его личность. Если ящик
украдут, он ничего не прочитает, а примерно через 48 часов перестанет
кого‑либо узнавать; отвязать его можно с телефона. Его адрес
.onion постоянен с первого запуска, даже если у него никогда не
было интернета. Как это выглядит в телефоне — в
руководстве.
09
Программирование бота: локальный API
Один запрос или ответ на строку, JSON, через сокет. Аутентификация — файловая система: кто может открыть сокет, тот может читать каталог бота. События приходят по тому же соединению после 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": "Доброе утро"}
← {"id": 2, "ok": true, "seq": 42}
← {"evento": "mensaje.recibido", "peer_id": "…", "msg_id": "…", "texto": "привет", "fecha": 1788500000}
| операция | параметры | возвращает |
|---|---|---|
estado | — | личность, onion, состояние Tor, счётчики |
suscribir | reponer | eventos[] с неподтверждённым; затем события вживую |
invitacion.crear | etiqueta, publica, qr | invite_id, texto, qr_gif |
contacto.listar / renombrar / borrar | peer_id… | — |
verificacion.codigo / comprobar | peer_id, codigo | texto и QR / resultado |
mensaje.enviar | peer_id, texto, respuesta_a, retardo_ms | msg_id |
adjunto.enviar / adjunto.leer | peer_id, nombre, base64 или ruta / msg_id | msg_id / байты |
mensaje.estado / mensaje.confirmar | msg_id | состояние / — (удаляется) |
canal.crear / invitacion / publicar / publicar_foto / recortar / borrar | canal_id, texto, pie… | seq, texto и QR… |
События: mensaje.recibido, adjunto.recibido, contacto.nuevo, entregado, tor. Они доставляются как минимум один раз: сообщение повторяется при каждом suscribir, пока вы его не подтвердите, а msg_id позволяет убрать дубли. Ошибки несут стабильный код (sin_sesion, invitacion_invalida, demasiado_grande…); внутренняя ошибка никогда не передаёт подробностей клиенту. Имена операций и полей — на испанском, языке проекта: это стабильный контракт, а не проза.
Целый бот-эхо на Python, без зависимостей:
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 call(**p):
f.write(json.dumps(p) + "\n"); f.flush(); return json.loads(f.readline())
r = call(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":
call(id=2, op="mensaje.enviar", peer_id=ev["peer_id"], texto="эхо: " + ev["texto"], respuesta_a=ev["msg_id"])
call(id=3, op="mensaje.confirmar", msg_id=ev["msg_id"])
С пакетом идут два полных примера: eco.py (отделяет ответы от событий, которые с ними пересекаются, — так и нужно делать, как только бот отвечает, продолжая слушать) и rss_a_canal.py, который публикует в канал каждую новую запись ленты RSS или Atom.
10
Правила, которые бот должен соблюдать
- Подтверждайте то, что обработали; неподтверждённое повторяется и через семь дней удаляется.
- Не скачивайте то, на что вам дали ссылку: бота, которого можно заставить запросить любой URL, кто-нибудь для этого использует.
- Считайте текст данными: он приходит от незнакомцев.
- Пределы: 12 КБ на текст канала, 7,5 МБ на вложение, 900 КБ на фото канала, 16 МБ на строку API.
- Масштаб: сотни личных контактов — да; тысячи — через канал.
- Onion, который никогда не выключается, проследить до его сторожевого узла легче, чем телефон; поэтому бот работает с vanguards, и поэтому его не стоит запускать рядом с тем, что вы не хотели бы с ним связывать.
11
Частые проблемы
| симптом | вероятная причина | что делать |
|---|---|---|
tor: arrancando в течение минут | нет сети, или антивирус либо прокси перехватывает TLS | отключить перехват; --verboso покажет причину от Tor |
| «Unix domain path too long» | путь к сокету длиннее ~100 байт | --socket /tmp/pb.sock |
| первый опрос из приложения ничего не приносит | адрес канала ещё не опубликован в Tor | подождать минуту-две и Обновить |
| сообщение какое-то время остаётся «в очереди» | цепочка rendezvous строится не сразу | подождать; бот подтверждает, как только получит |
contacto listar не показывает того, кто только что добавил бота | бот узнаёт о контакте по его первому сообщению | пусть что-нибудь напишет |
sin_sesion при отправке | этот peer_id ещё не контакт | contacto listar |