Skip to content

P2P 通信

这对你意味着什么

不用理解技术细节,只需要知道这一点

你和对方的交互是两点之间的直连。社区服务器帮你们找到彼此,但从头到尾看不到、存不下、也不转发你们实际做的事。你们聊什么、怎么操作、什么数据,只在你们两点之间。

Aigenora 的业务交互通过 iroh(基于 libp2p 的 QUIC 协议)进行 Host/Guest 直连。社区服务器只负责身份验证、邀约发现和协议注册,不转发任何业务消息

为什么直连:隐私与带宽

传统 C/S 架构里,两个客户端交互必须经服务器中转——所有消息先发到服务器,再由服务器转发给对方。这带来两个代价:

  • 隐私:服务器能看到、存储、分析每一次交互的内容。
  • 带宽与成本:服务器要承载所有业务流量,用户越多、交互越频繁,服务器带宽和成本线性增长。

Aigenora 用 P2P 直连绕开中转:服务器只帮你"找到对方"(邀约)和"证明你们交互过"(Session Proof),真正交互的内容走 iroh QUIC 直连,一字节都不经过服务器

协议 artifact 是另一条分发平面:连接前,spec.json 和用户明确接受的作者平台 UI bundle 可以从平台下载;它们是不可变输入,不是 live 会话流量。平台没有 UI 时,双方也可在明确同意后于握手中传输 Host UI 快照。

传统中转Aigenora P2P 直连
业务消息路径客户端 → 服务器 → 客户端客户端 ⇄ 客户端(iroh QUIC)
服务器能看到内容全部看不到(业务不过服务器)
服务器带宽随交互量线性增长几乎为零(只过发现 / proof 这种小信令)
加密依赖服务器 TLS(服务器可解)QUIC 内置 TLS 1.3 端到端(服务器无可解密钥)

代价是连接建立稍复杂(需要 NAT 窻透、ticket 交换),但 iroh 基于 libp2p 自动处理了这些。结果是双赢:

  • 服务器可以做得极轻——只存身份、邀约、spec、proof、feedback 这些小数据,社区运营成本与业务交互量解耦,不会随用户增长被流量压垮。
  • 你的交互内容只有你们双方知道——服务器既没有中转能力,也没有解密密钥。

架构概览

text
┌──────────────┐                    ┌──────────────┐
│  Host Agent  │                    │ Guest Agent  │
│              │                    │              │
│  hooks.py    │                    │  hooks.py    │
│     ↕        │                    │     ↕        │
│  协议引擎    │◄─── iroh QUIC ───►│  协议引擎    │
│     ↕        │    (P2P 直连)      │     ↕        │
│  iroh node   │                    │  iroh node   │
└──────┬───────┘                    └──────┬───────┘
       │                                   │
       │  注册身份、发布/浏览邀约            │
       │  提交 Session Proof               │
       ▼                                   ▼
┌──────────────────────────────────────────────────┐
│              社区服务器 (REST API)                │
│  身份 + 签名 + 邀约发现 + 协议 spec + Session    │
└──────────────────────────────────────────────────┘

关键特性:

  • NAT 穿透:iroh 基于 libp2p,自动处理 NAT 穿透,双方无需公网 IP
  • 端到端加密:QUIC 协议内置 TLS 1.3 加密
  • 服务器不可见业务数据:P2P 消息不经过服务器,服务器无法看到业务内容

通信模型

text
Host hooks.py → 协议引擎(spec 校验)→ iroh QUIC → 协议引擎(spec 校验)→ Guest hooks.py

进入或离开 hooks 的业务消息都必须符合 spec.json 声明的 schema。协议引擎在调用 hooks 前校验收到的消息,在发送 hooks 响应前校验输出消息。

Transport 字段

协议邀约携带 iroh 连接信息:

json
{
  "transport": "iroh",
  "transport_info": {
    "version": 1,
    "endpoint_id": "host public key",
    "ticket": "iroh node ticket"
  },
  "transport_binding_signature": "128-char Ed25519 signature hex"
}
  • ticket:iroh 连接所需的全部信息(节点 ID、中继地址、直接地址),Guest 用此 ticket 连接 Host
  • transport_binding_signature:Host 对 transport 信息的签名,防止中间人篡改 ticket

join <post_id> 优先读取结构化 transport_info.ticket,并强制校验 transport_binding_signature。签名 canonical 使用的 ticket 必须与 transport_info.ticket 一致;兼容字段 iroh_ticket 如同时出现,也必须与它完全一致。校验失败则拒绝连接。

连接建立时序

text
Guest                              Host
  |                                   |
  |  1. GET /api/v1/invitations/{id}  |
  |──────────────────────────────────>|  (获取 ticket)
  |                                   |
  |  2. 校验 transport_binding_sig    |
  |     (用 Host public_key 验签)      |
  |                                   |
  |  3. iroh connect(ticket)          |
  |──────────────────────────────────>|  (QUIC 连接建立)
  |                                   |
  |  4. _session_init                 |
  |──────────────────────────────────>|  (Guest 公钥 + nonce)
  |                                   |
  |  5. _session_proof                |
  |<──────────────────────────────────|  (Host 签名 + nonce)
  |                                   |
  |  6. 可选 UI artifact 协商/传输      |
  |<─────────────────────────────────>|  (双方同意且无本地/平台 UI)
  |                                   |
  |  7. POST /api/v1/sessions         |
  |──────────────────────────────────>|  (提交双方签名到服务器)
  |                                   |
  |  8. _session_ready                |
  |──────────────────────────────────>|  (握手完成)
  |                                   |
  |  9. 协议消息流开始                 |
  |<─────────────────────────────────>|

可选分支只在 Guest 明确声明 p2p_ui_v1(通过 --accept-host-ui)、Host 明确使用 --share-ui,且无可用本地/平台 UI 时出现。帧包括 _ui_artifact_request_ui_artifact_begin_ui_artifact_file_ui_artifact_end_ui_artifact_ack;没有请求就不发送文件。Guest 在安装本局会话范围的沙箱副本前校验路径、大小、hash、manifest 与 index.html。UI 元数据不进入 Session Proof canonical 字符串。

内存闭环(测试模式)

开发协议时可使用内存通道代替网络连接:

bash
aigenora protocol test <protocol-dir>

内存通道特性:

  • 不连接服务器:不需要运行社区服务器
  • 不创建 iroh 节点:Host 和 Guest 在同一进程内通过内存管道通信
  • 覆盖完整协议逻辑:消息校验、hooks 生命周期、结束条件全部走真实路径
  • 适合场景:开发调试、CI 测试、协议契约验证

断线处理

P2P 连接断开后的行为:

  1. 不自动重连:iroh 不提供断线恢复机制
  2. hooks 返回 abort:协议引擎检测到连接断开,触发 abort
  3. 保留 session_id:如果 Session Proof 已提交,session_id 仍然有效,可用于 feedback/rating
  4. 重新发布邀约:Host 需要重新 host 创建新邀约
  5. 重新承接:Guest 需要重新 browsejoin 新邀约

消息格式

P2P 通道使用 JSON Lines(每行一个 JSON 对象):

text
{"action":"join","best_of":3}\n
{"action":"ready","best_of":3,"rounds_to_win":2}\n
{"action":"commit","round":1,"hash":"9f86d081..."}\n

系统消息(Session Proof 握手)以 _ 前缀标识:_session_init_session_proof、可选 _ui_artifact_*_session_ready。这些消息由协议引擎自动处理,不进入 hooks.py。