跳转到内容

接收第一条消息

这一页把”用户发了句 hi → Agent 收到 hi”中间发生的每一步全部展开。 读完后你应该能回答:哪一步在客户端、哪一步在后端、哪些字节是密文、 哪些是元数据、SDK 替你做了什么、什么时候轮到你的代码出场。

链路全图

Hashee Mobile / Desktop Hashee 后端 你的 Agent 进程
│ │ │
[1] 用户键入 "hi" │ │
│ │ │
[2] 客户端 Core 加密管线: │ │
├─ generateCek() (32B 随机) │ │
├─ encryptContent(cek, "hi", aad) │ │
│ → ContentEnvelope (Layer 1) │ │
├─ wrapCek(cek, agent_x25519_pk) │ │
│ → WrapEnvelope per recipient (Layer 2) │ │
├─ signEnvelope(unsigned, user_ed25519_sk) │ │
│ → SignedEnvelope (Layer 3) │ │
├─ Ratchet step → 包外层 (Layer 4 if WS) │ │
└─ encodeWireEnvelope() → base64 │ │
│ │ │
├─POST /messages or WS frame──────────► │
│ │ │
│ [3] 后端校验: │
│ ├─ session token / WS auth │
│ ├─ rate limit │
│ ├─ recipient (Agent) 存在 │
│ └─ payload size │
│ │ │
│ [4] 写 messages 表 │
│ ├─ 仅元数据 │
│ │ (sender_id, conv_id, │
│ │ encrypted_payload BLOB, │
│ │ timestamps) │
│ └─ 大对象 (附件) 走 R2 │
│ │ │
│ [5] 路由: │
│ ├─ ConversationDO 收到事件 │
│ ├─ 找到接收方 Agent 的连接 │
│ │ (WS / Webhook / Polling) │
│ └─ DeliveryShardDO 扇出 │
│ ├──WS frame──────────────────►│
│ │ │
│ │ [6] SDK Transport 收到 frame
│ │ │
│ │ [7] SDK 解密管线:
│ │ ├─ decodeWireEnvelope() → SignedEnvelope
│ │ ├─ verifyEnvelope(env, user_ed25519_pk)
│ │ │ ✗ 签名失败 → 丢弃 + decrypt_failure_report
│ │ ├─ findWrapForRecipient(env, agent_id)
│ │ │ → 该 Agent 的 WrapEnvelope
│ │ ├─ unwrapCek(wrap, agent_x25519_sk) → CEK
│ │ ├─ decryptContent(cek, content, aad) → "hi"
│ │ └─ if Ratchet → 推进接收链
│ │ │
│ │ [8] InboundMessage 派发:
│ │ ├─ parseInboundMessage()
│ │ │ → { conversation_id, sender_id,
│ │ │ payload: { type: "text", text: "hi" },
│ │ │ created_at, headers, attachments }
│ │ ├─ 调用所有 messageHandlers
│ │ │ → addMessageHandler 注册的 callback
│ │ │
│ │ [9] 你的代码出场:
│ │ ├─ msg.payload.text === "hi"
│ │ ├─ 调 LLM / 业务逻辑
│ │ └─ agent.send(conv_id, {type:"text", text:"hello"})
│ │ │
│ │ [10] SDK 加密发送 (镜像 [2]):
│ │ ├─ generateCek
│ │ ├─ encryptContent
│ │ ├─ wrapCek (now to user_x25519_pk)
│ │ ├─ signEnvelope (with agent_ed25519_sk)
│ │ └─ WS frame send
│ │ ◄─────────WS frame──────── │
│ │ │
│ │ 路由 / 写库 / DO 扇出 │
│ ◄──WS frame────── │
│ │ │
[11] 客户端 Core 解密管线 (镜像 [7]): │ │
│ │ │
[12] 用户看到 "hello" 气泡 │ │

后端可见 vs 不可见

字段后端可见?
conversation_id, sender_id, recipient_id✓ 元数据
created_at, client_message_id (idempotency)
encrypted_payload (整段 base64 wire envelope)✓ 但是密文
attachment_id 引用✓ R2 对象引用
消息明文(“hi” / “hello”)
CEK
用户 / Agent 私钥
Ed25519 签名内容✓ 但加密后只是字节流

SDK 替你做了什么

发消息时(你只调 agent.send(...)):

步骤SDK 内部函数(@hasheeai/agent-sdk-ts
1. 生成一次性 CEKgenerateCek()
2. 加密内容(AES-256-GCM + AAD)encryptContent(cek, plaintext, aad)
3. 为每个收件人 wrap CEKwrapCek(cek, recipient_x25519_pk)
4. 计算 canonical envelope JSONcanonicalizeEnvelope(unsigned)
5. Ed25519 签名signEnvelope(unsigned, agent_ed25519_sk)
6. 编码成 wire envelopeencodeWireEnvelope({signed, wraps})
7. WS 发送 / HTTP POSTtransport.send(frame)

收消息时(你只在 addMessageHandler 拿到明文):

步骤SDK 内部函数
1. 解码 wire envelopedecodeWireEnvelope(base64)
2. 验证 Ed25519 签名verifyEnvelope(signed, sender_ed25519_pk)
3. 找到给本 Agent 的 wrapfindWrapForRecipient(env, agent_id)
4. unwrap CEKunwrapCek(wrap, agent_x25519_sk)
5. 解密内容decryptContent(cek, content, aad)
6. 解析为 InboundMessageparseInboundMessage()
7. 调用你的 handlerdispatchInboundFrame(msg, handlers)

AAD 绑定

Layer 1 加密的 AAD(Additional Authenticated Data)= 44 字节:

[ conversation_id (16) | epoch_id (8 BE) | "hashee-envelope-v1.1" (20) ]

意义:

  • 同一 CEK + 同一密文,换 conversation_id 就解不出来 → 防”密文搬运”。
  • 换 epoch_id 也解不出来 → 支持密钥轮换的 forward secrecy。
  • wire 字面量绑死 → 防协议混淆攻击(v1.1 密文不能被解析为 v1)。

详见 E2EE 规范 §3.1。

出错时会发生什么

错误SDK 行为
签名不通过丢弃 + 调 reportDecryptFailure(reason: "signature")
找不到给本 Agent 的 wrap丢弃 + reportDecryptFailure(reason: "no_wrap")
unwrap CEK 失败(私钥不对)丢弃 + reportDecryptFailure(reason: "unwrap")
AES-GCM 解密 tag 不匹配丢弃 + reportDecryptFailure(reason: "aead")
Payload 不是合法 JSON丢弃 + reportDecryptFailure(reason: "parse")

reportDecryptFailure 默认走 console.warn + 上报 telemetry endpoint, 不抛异常给你的 handler——单条消息坏掉不影响后续消息。

你可以注册 addStatusHandler 来获得连接级 / decrypt 失败级事件。

演示视频

下面这段约 90 秒的视频把 Hashee app 与 Agent 终端并排播放,逐帧高亮加密链路上每一步发生的位置:

视频文字版逐节描述(无视频也能阅读)
  • 00:00 – 00:10 — 屏幕分两半。左:Hashee Mobile 模拟器主页,已添加 DemoBot; 右:终端,DemoBot 进程显示 connection: connected
  • 00:10 – 00:20 — 左侧打开 DemoBot 会话,输入框输入 “hi” 准备发送。屏幕底部 叠加文字 “[1] 用户键入”。
  • 00:20 – 00:35 — 用户点发送;左侧画面短暂高亮 “[2] 客户端加密管线” 标签, 屏幕边缘流动显示步骤:generateCek / encryptContent / wrapCek / signEnvelope。 消息气泡变为”已发送”。
  • 00:35 – 00:50 — 中间叠加云端示意(盲管道),文字 “[3-5] 后端只看密文 + 路由”,背景流动显示 ConversationDO + DeliveryShardDO 节点动画。
  • 00:50 – 01:05 — 右侧终端弹出新行 [hashee] inbound message conv=01906... payload.text="hi"。叠加文字 “[6-9] SDK 解密 → 你的代码”。
  • 01:05 – 01:20 — 终端显示业务代码触发 OpenAI 调用 + agent.send(...) 调用。 屏幕边缘再次流动 “[10] 加密发送” 步骤标签。
  • 01:20 – 01:30 — 镜像回到左侧,DemoBot 头像下方”正在输入…”消失, Agent 回复气泡 “Hello! 你刚说了 ‘hi’” 浮现。

常见误区

Q: 后端能看到我的 Agent 内部 prompt 吗? 不能。LLM API 调用发生在你的 Agent 进程内,Hashee 后端不参与。

Q: 我能在后端管理 Agent 的对话历史吗? 可以,但你看到的是密文。你要保存明文,必须在你的 Agent 侧自己存。 (推荐方案:Agent 进程把”对话摘要”加密后写到你自己的数据库。)

Q: 用户撤销 Agent 后,我手里的明文怎么办? 平台无法替你删除——这是 Agent 侧的责任。 你应该响应 relation.revoked 事件 + 删除自己存的相关数据。 详见 更新与撤销

下一步