机器人

像认识一个人那样认识它。

PrivChat 机器人是一个自动化账号:它背后不是一个用着应用的人,而是某人——它的运营者——在自己的电脑上长期运行的一个程序。这里没有任何人的服务器,所以也没有机器人目录、没有 @ 用户名、没有互联网上的 API:像找一个人一样,通过它的邀请找到它。本页分为两半:前一半写给任何使用应用的人;从「部署一个机器人」往下,写给运营者和开发者。

暗处桌面上运行着的一台小型电脑。
使用机器人不需要下面这些 这里的机器人是什么 机器人能看到什么,看不到什么 部署一个机器人 参数 从机器人一侧操作频道 一对一联系人 把机器人当作家中的信箱 为机器人编程:本地 API 机器人必须遵守的规则 常见问题

01

使用机器人不需要下面这些

使用机器人就是照常使用应用。如果有人给你一个机器人的邀请(文本或二维码,通常公布在它的网站上):

  • 如果是频道:新建 → 加入频道,粘贴邀请文本(以 UEND 开头)或扫描其二维码,然后加入。进入后,刷新会拉取内容;第一次拉取可能要等一两分钟,直到频道地址在 Tor 上发布。
  • 如果是联系人:新建 → 新联系人,粘贴邀请或扫描机器人的动画二维码,起个名字,添加。你的第一条消息带着加密握手;到达后,机器人把你当作联系人,并回复它的程序所规定的内容。
  • 验证它:在聊天里,更多 → 当面验证 → 粘贴对方的代码,用运营者在其网站上签名公布的验证码核对。

而下面这些是机器人做不到的——由协议设计保证:

  • 读不到你没有亲自发给它的任何东西。你的对话、群组和频道,它既看不到,也不知道它们存在。
  • 进不了没有邀请它的地方。它只有在管理员像介绍任何成员那样介绍它时才能进群;也不能向别人的频道发布:每个频道只有持有其密钥的人才能写。
  • 不知道谁在读它的频道。读者通过匿名链路拉取内容,从不表明身份。
  • 不存在 PrivChat「官方」机器人。每个机器人由它的运营者负责——你写给机器人的内容就是交给了运营者。如果背后是谁对你重要,请用它公布的验证码核对。

02

这里的机器人是什么

  • 只是另一个节点。它有自己的身份、自己的 Tor onion 服务、自己的加密数据库和自己的频道。它就是应用的引擎,只是不带 Android 编译而成。
  • 在线路上无法分辨。同样的信封、同样的加密(Double Ratchet 与 PQXDH)、同样的掩护流量、同样的频道。应用不知道自己在和一个程序对话,因为根本没有办法知道。
  • 没有目录。一个中央目录就是这个应用存在的意义所要避免的那台服务器。运营者把机器人的邀请发到任何他想发的地方:网页、海报、一条消息。
  • 本地 API。你的程序(Python、Go、shell,什么都行)通过机器人目录下的 Unix 套接字、以 JSON 与它对话。远程的部分由 Tor 负责。
  • 两种用途,按这个顺序。频道:机器人发布,任何人匿名阅读。以及一对一:人们给它写信,机器人回答。

03

机器人能看到什么,看不到什么

关于它频道的读者,什么都看不到。读者通过匿名链路拉取内容,不表明身份;机器人只看到有人读过、何时、多少,再多也看不到,因为协议里没有地方放这些信息。有一千个读者的频道对读者的了解和只有十个读者的频道完全一样。

关于给它写信的人,看到的和任何联系人一样。给机器人写信,就是把你的标识、你的 Tor 回信地址和你写的内容交给它的运营者。对一个人类联系人来说这些是分散的;对一个热门机器人来说,这些集中在一台服务器上。所以频道排在前面,所以机器人不保存历史:一条收到的消息只存活到运营者的程序确认它为止,上限七天。

如果机器人的服务器落入坏人之手,他们会拿走机器人的身份(从而能永远冒充它:没有撤销机制)、它频道的密钥和它的联系人列表。他们拿不到任何用户或任何读者的任何东西。恢复方法是销毁这个机器人、创建一个新的,并在其他渠道公布新的指纹。

没有任何东西会告诉你某个联系人是机器人,也不会告诉你它就是它自称的那个机器人。认真的运营者会在自己的网站上公布经签名的机器人验证码;在应用的聊天里,更多 → 当面验证 → 粘贴对方的代码(或扫描它的二维码),就像验证一个人那样核对。

04

部署一个机器人

这部分是运营者的事。需要一台自己的 Linux——服务器,或 64 位的树莓派 4 或 5——、Java 17 或更新版本(sudo apt install openjdk-17-jre-headless)、大约 300 MB 空闲内存,以及通往 Tor 的出口。手机当不了机器人:机器人必须一直开机、一直在线,那得是运营者的电脑,不是手机。

安装包:privchat-bot-2.19.2.zip(脚本、库和原生 arti;Linux x86_64——Windows 仅用于开发)。树莓派(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 PATHAPI 的 Unix 套接字(路径要短:AF_UNIX 只允许约 100 字节)DIR/api.sock
--api-tcp PORT另外在 127.0.0.1 上带令牌开放 API关闭
--api-grupo GROUP该系统组也可打开套接字(0660)仅用户本人(0600)
--retencion-dias N未确认的收到消息存活的天数7(最多 30)
--max-contactos N通过公开邀请加入的联系人上限1000
--sin-relleno关闭掩护流量开启,与应用相同
--buzon作为主人的信箱:除 Tor 外,还使用“面对面”的本地网络关闭
--bluetooth与 --buzon 一起使用时,也通过低功耗蓝牙(仅 Linux,BlueZ)关闭
--malla 端口LoRa 网状网络:通过 USB(/dev/ttyACM0、COM4)或 TCP(host:4403)连接 Meshtastic 节点;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        # 可粘贴的文本,以及 PNG 里的二维码
$B canal publicar <canal_id> "明天 9 点到 12 点停水"
$B canal publicar-foto <canal_id> /path/haibao.jpg "海报"        # 会被重新压缩:EXIF 在这里消失
$B canal listar                                                 # 内容数、拉取次数和已服务字节数
$B canal recortar <canal_id> 50                                 # 只保留最近 50 条

每条内容都由频道密钥签名、用其读取密钥加密并与前一条相连:历史可以停止提供,但不能在读者毫无察觉的情况下被改写。每条文本最多 12 KB;照片会被重新压缩直到装进 900 KB。

07

一对一联系人

两种邀请,格式与应用里的相同:

个人邀请公开邀请(--publica)
谁能使用一个人:第一个;第二个会被拒绝任何人,直到 --max-contactos 上限
一次性 prekeys五个,与应用相同没有:分发给成千上万的人,它们就不再是一次性的了
代价没有新的代价每个用户发给机器人的第一条消息,在机器人密钥未来被攻破的情况下,缺少 prekey 提供的额外保护;机器人一回复,ratchet 就会自愈
用途给自己的助手、给家人的通知面向公众开放的机器人
$B invitacion crear web --publica                        # 可粘贴的文本
$B invitacion crear web --publica --gif /var/www/bot.gif  # 外加邀请的四个二维码,做成动画 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,机器人就成为某个人(它的主人)的信箱:放在家中的树莓派或电脑上,在主人不在时保管并转交其消息。来到该 Wi‑Fi 的人会留下给主人的消息,并取走主人留给他们的消息;如果主人在手机上开启“联网”,其联系人也能在手机关机时通过 Tor 留言。在 systemd 单元中,把 --buzon 加到 ExecStart 里,放在 run 之前。

$B buzon invitar                        # 在手机上粘贴这段文字:设置 → 我的信箱;第一个使用它的人就是主人
$B buzon estado                         # 主人以及保管的内容

蓝牙(仅 Linux,可选)。使用 --buzon --bluetooth 时,信箱也可以通过低功耗蓝牙被找到:为主人开启了“面对面聊天”的人经过门口时,无需连接 Wi‑Fi 就能留下和取走消息。需要运行中的 BlueZ、一个适配器、服务用户属于 bluetooth 组,并在 /etc/bluetooth/main.conf 的 [General] 部分设置 ReverseServiceDiscovery = false(然后重启 bluetooth 服务):否则手机会弹出配对请求,通道无法建立。该设置也会关闭 BlueZ 对其他低功耗蓝牙设备的 GATT 客户端,所以最好用在专用设备上。默认关闭,因为广播会让在街上扫描的人知道这里有 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 状态、计数器
suscribirreponer包含未确认消息的 eventos[];之后是实时事件
invitacion.crearetiqueta、publica、qrinvite_id、texto、qr_gif
contacto.listar / renombrar / borrarpeer_id…—
verificacion.codigo / comprobarpeer_id、codigotexto 与二维码 / resultado
mensaje.enviarpeer_id、texto、respuesta_a、retardo_msmsg_id
adjunto.enviar / adjunto.leerpeer_id、nombre、base64 或 ruta / msg_idmsg_id / 字节
mensaje.estado / mensaje.confirmarmsg_id状态 / —(删除)
canal.crear / invitacion / publicar / publicar_foto / recortar / borrarcanal_id、texto、pie…seq、texto 与二维码…

事件: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 KB,附件每个 7.5 MB,频道照片每张 900 KB,API 每行 16 MB。
  • 规模:几百个一对一联系人可以;上千,请用频道。
  • 一个永不下线的 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