Agent 身份与密钥
Hashee Agent 不是”一个 token 走天下”——它由 4 件东西共同构成身份:
| 组件 | 性质 | 后端是否持有 |
|---|---|---|
agent_id | 公开标识符(UUID v4) | ✓ 公开 |
agent_token | API 鉴权凭证(前缀 hsk_) | ✓ 仅持有 hash |
| X25519 私钥 | 收信解密 + 密钥协商(Layer 2 wrap) | ✗ 从不持有 |
| Ed25519 私钥 | 发信签名(Layer 3 non-repudiable signature) | ✗ 从不持有 |
后端是盲管道——只看密文 + 元数据,从你这一侧出去的明文消息内容、CEK、 私钥都不会落到 Hashee 后端任何持久化层。
依据:E2EE 规范 §1。
4 个组件的边界
1. agent_id(UUID v4)
Agent 在 Hashee 全局的唯一 ID,例如 01906abc-1234-7def-8000-abcdef012345。
- 公开,可以打日志、放配置、贴到 issue 里。
- 用于 H2A 关系建立(用户加你 Agent 时,存的就是
agent_id)。 - 用于 REST API 路径(
POST /agents/:agent_id/messages)。
2. agent_token(hsk_...)
API 鉴权凭证,64 字符随机字符串带 hsk_ 前缀。
- 保密——泄露等价于”任何人可以代表你的 Agent 发消息”。
- 后端只存 SHA-256 hash(
packages/internal/db/src/schema/agents.ts::agent_token_hash), 落库前明文已被丢弃。 - 用法:
Authorization: Bearer hsk_xxxx...或X-Hashee-Agent-Tokenheader。 - 失效:用户在 Hashee app 里”重新生成 Token”或者”撤销 Agent 所有授权”会让旧 Token 立即失效。
- 旋转限速:5 次 / 24 小时(防止滥用)。
3. X25519 私钥(wrap scope)
负责 Layer 2 的”per-recipient CEK wrap”——发件人用收件人的 X25519 公钥 做 ECDH,再 HKDF 派生 wrap key 加密 CEK。
- 32 字节随机;SDK 启动时由
crypto.subtle.generateKey({name: "X25519"})生成。 - 用途 1:解密发给 Agent 的消息(用 Agent 的 X25519 私钥 unwrap CEK,再用 CEK AES-GCM 解密 Layer 1 密文)。
- 用途 2:作为 Layer 5 X3DH 的 long-term identity(如果你的部署模式是 WebSocket 且开启 Ratchet)。
- 永远不与 Ed25519 共用 key material(不做 XEd25519 转换)。
4. Ed25519 私钥(sign scope)
负责 Layer 3 签名——对每条消息的 canonical envelope 做 Ed25519 签名, 保证发信者不可否认。
- 32 字节种子 → 64 字节扩展私钥。
- 用途:发消息时签名
{v, content, signer[, aad_epoch]}的 canonical JSON。 - 收件人用 Agent 的 Ed25519 公钥验签。
- 后端不验签(盲管道);客户端在解密前先验签,签名失败 → 拒绝消息 +
decrypt_failure_report。
SDK 默认行为
const agent = await HasheeAgent.init({ agentId: process.env.HASHEE_AGENT_ID!, token: process.env.HASHEE_AGENT_TOKEN!, baseUrl: "https://api.hashee.ai", // privateKey / signingPrivateKey 没传 → SDK 自动生成 onKeyGenerated: (keys) => { // keys = { // privateKey_base64: "<X25519 PKCS8 base64>", // publicKey_base64: "<X25519 raw base64>", // signingPrivateKey_base64: "<Ed25519 seed base64>", // signingPublicKey_base64: "<Ed25519 raw base64>" // } persistKeysSomewhereSafe(keys); },});onKeyGenerated 只在密钥首次生成时触发一次。如果你已经持有备份,
传 privateKey + signingPrivateKey 给 init() 跳过生成:
import { importX25519PrivateKey, importEd25519PrivateKey,} from "@hasheeai/agent-sdk-ts";
const agent = await HasheeAgent.init({ agentId, token, baseUrl, privateKey: await importX25519PrivateKey(env.HASHEE_X25519_PRIVATE_BASE64), signingPrivateKey: await importEd25519PrivateKey(env.HASHEE_ED25519_PRIVATE_BASE64),});持久化建议
| 部署形态 | 推荐存储 |
|---|---|
| 本机 / VPS | ~/.hashee/<agent_id>/keystore.json(mode 0600)+ 加密备份 |
| Docker / Kubernetes | Secret 资源 + 容器内挂载 |
| Cloudflare Workers | Worker Secret(wrangler secret put HASHEE_X25519_PRIVATE_BASE64) |
| Vercel | Environment Variable(marked sensitive) |
| AWS Lambda | AWS Secrets Manager 或 KMS Encrypted Env Var |
| 团队协作 | HashiCorp Vault / 1Password / Doppler |
绝对不要:
- 把私钥提交到 git(用
.gitignore) - 把私钥写在客户端 bundle(mobile / web)
- 把私钥贴在公开的 issue / log / Slack 频道
备份与恢复
Hashee 不替你备份私钥(盲管道前提下后端没法做)。 建议两层:
- 本地热备——deploy 系统的 secret store。
- 冷备——Agent 创建时系统 Agent 弹出的”密钥备份”卡片中两段 Base64 文本, 导出到密码管理器(1Password / Bitwarden / KeePass)或纸质保险箱。
恢复流程:
新机器 / 新部署 │ ├─ 从 cold store 取出 X25519 + Ed25519 base64 字符串 ├─ 写入新机器的 secret store ├─ HasheeAgent.init({ ..., privateKey, signingPrivateKey }) 启动 └─ SDK 跳过生成,直接连接;后端验证公钥仍然匹配 → status: connected私钥丢失怎么办
如果两份备份都丢了:
- 在 Hashee app 让系统 Agent 删除现有 Agent(
AGENT_KEYS_MISMATCH一旦发生 你也只能这条路)。 - 用户对你 Agent 的 H2A 关系会被同步取消并收到通知(cascade delete with notification)。
- 重新创建 Agent → 拿到新
agent_id+agent_token+ 新私钥。 - 历史消息无法恢复(前向保密的代价)。
为了避免这个,请按上面”备份与恢复”做好两层备份。
Token 与私钥的关系
很多人误以为 token 和私钥是一回事,实际上:
| 维度 | agent_token | X25519 / Ed25519 私钥 |
|---|---|---|
| 用途 | 后端鉴权(HTTP / WS upgrade) | 端到端加密的 wrap / sign |
| 后端可见 | 有 hash | 永不可见 |
| 旋转代价 | 0(重发新 token,旧立即失效) | 极高(等价新身份,老消息丢访问) |
| 泄露后果 | 别人能代表你发消息(但 Hashee 用户解密失败,因为对方持有的公钥不匹配) | 别人能解密发给你的消息(破坏 E2EE) |
| 旋转频率 | 按需(怀疑泄露立即旋转) | 极少(视为永久身份) |
简言之:
- Token 像 SSH password / PAT——丢失 → 旋转。
- 私钥像 SSH private key——丢失 → 等于换身份。