@agentunion/fastaun-browser 0.2.19 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/_packed_docs/CHANGELOG.md +50 -0
- package/_packed_docs/agent.md/SCHEMA.md +173 -0
- package/_packed_docs/agent.md/examples/codeagent-claudecode.md +61 -0
- package/_packed_docs/agent.md/examples/human-developer.md +60 -0
- package/_packed_docs/agent.md/examples/openclaw-lobster.md +52 -0
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +43 -0
- package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +205 -0
- package/_packed_docs/protocol/00A-/350/256/276/350/256/241/345/216/237/345/210/231-/344/270/272Agent/350/200/214/347/224/237.md +197 -0
- package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +549 -0
- package/_packed_docs/protocol/02-/350/257/201/344/271/246/344/270/216/344/277/241/344/273/273/344/275/223/347/263/273.md +810 -0
- package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +262 -0
- package/_packed_docs/protocol/04-Peer-/345/255/220/345/215/217/350/256/256.md +180 -0
- package/_packed_docs/protocol/05-Relay-/345/255/220/345/215/217/350/256/256.md +164 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1135 -0
- package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +234 -0
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +900 -0
- package/_packed_docs/protocol/08-AUN-E2EE.md +413 -0
- package/_packed_docs/protocol/09-/345/256/211/345/205/250/350/200/203/350/231/221.md +316 -0
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +804 -0
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +271 -0
- package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +329 -0
- package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +141 -0
- package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -0
- package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +419 -0
- package/_packed_docs/protocol/README.md +71 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +118 -0
- package/_packed_docs/protocol/agent.md/examples/codeagent-claudecode.md +61 -0
- package/_packed_docs/protocol/agent.md/examples/human-developer.md +60 -0
- package/_packed_docs/protocol/agent.md/examples/openclaw-lobster.md +52 -0
- package/_packed_docs/protocol/aun-docs-guide.md +49 -0
- package/_packed_docs/protocol/index.md +124 -0
- package/_packed_docs/protocol//350/215/211/346/241/210-agent.md/347/255/276/345/220/215/345/215/217/350/256/256.md +205 -0
- package/_packed_docs/protocol//350/215/211/346/241/210-/346/213/222/347/273/235/344/277/241/345/217/267/345/215/217/350/256/256.md +249 -0
- package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +337 -0
- package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +80 -0
- package/_packed_docs/protocol//351/231/204/345/275/225C-/347/247/201/351/222/245/347/256/241/347/220/206/344/270/216/350/272/253/344/273/275/346/201/242/345/244/215.md +704 -0
- package/_packed_docs/protocol//351/231/204/345/275/225D-Root_CA_/346/262/273/347/220/206/346/234/272/345/210/266.md +620 -0
- package/_packed_docs/protocol//351/231/204/345/275/225E-Root_CA_/345/207/206/345/205/245/346/265/201/347/250/213.md +605 -0
- package/_packed_docs/protocol//351/231/204/345/275/225F-Issuer_CA_/347/224/263/350/257/267/346/265/201/347/250/213.md +548 -0
- package/_packed_docs/protocol//351/231/204/345/275/225G-AID_/345/255/244/345/204/277/351/242/204/351/230/262/344/270/216/346/225/221/346/217/264/346/234/272/345/210/266.md +513 -0
- package/_packed_docs/protocol//351/231/204/345/275/225H-Identity/346/234/215/345/212/241/345/256/236/347/216/260/346/214/207/345/215/227.md +619 -0
- package/_packed_docs/protocol//351/231/204/345/275/225I-/350/267/250/345/237/237/346/266/210/346/201/257/350/267/257/347/224/261/345/256/236/347/216/260/346/214/207/345/215/227.md +492 -0
- package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +402 -0
- package/_packed_docs/protocol//351/231/204/345/275/225K-Agent_Web/345/217/221/347/216/260/345/215/217/350/256/256.md +130 -0
- package/_packed_docs/protocol//351/231/204/345/275/225L-E2EE/345/256/236/347/216/260/346/214/207/345/215/227.md +267 -0
- package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +367 -0
- package/_packed_docs/python-sdk-v2-only-changelog.md +189 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +223 -0
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +354 -0
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +172 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +396 -0
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +611 -0
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1203 -0
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +150 -0
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +89 -0
- package/_packed_docs/sdk/09-custody-api-manual.md +445 -0
- package/_packed_docs/sdk/09-group-rpc-manual.md +1895 -0
- package/_packed_docs/sdk/09-message-rpc-manual.md +597 -0
- package/_packed_docs/sdk/09-meta-rpc-manual.md +142 -0
- package/_packed_docs/sdk/09-payload-reference.md +702 -0
- package/_packed_docs/sdk/09-storage-rpc-manual.md +408 -0
- package/_packed_docs/sdk/09-stream-rpc-manual.md +275 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +72 -0
- package/_packed_docs/sdk/INDEX.md +131 -0
- package/_packed_docs/sdk/README.md +307 -0
- package/dist/auth.d.ts +2 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +33 -14
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +14300 -0
- package/dist/client.d.ts +200 -178
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3096 -4019
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +0 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -4
- package/dist/config.js.map +1 -1
- package/dist/crypto.d.ts +8 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +114 -1
- package/dist/crypto.js.map +1 -1
- package/dist/e2ee.d.ts +5 -210
- package/dist/e2ee.d.ts.map +1 -1
- package/dist/e2ee.js +4 -1379
- package/dist/e2ee.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/namespaces/auth.d.ts +1 -0
- package/dist/namespaces/auth.d.ts.map +1 -1
- package/dist/namespaces/auth.js +23 -8
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/protected-headers.d.ts +14 -0
- package/dist/protected-headers.d.ts.map +1 -0
- package/dist/protected-headers.js +47 -0
- package/dist/protected-headers.js.map +1 -0
- package/dist/seq-tracker.d.ts +7 -2
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +31 -10
- package/dist/seq-tracker.js.map +1 -1
- package/dist/transport.d.ts +9 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +24 -0
- package/dist/transport.js.map +1 -1
- package/dist/v2/crypto/aead.d.ts +26 -0
- package/dist/v2/crypto/aead.d.ts.map +1 -0
- package/dist/v2/crypto/aead.js +63 -0
- package/dist/v2/crypto/aead.js.map +1 -0
- package/dist/v2/crypto/canonical.d.ts +21 -0
- package/dist/v2/crypto/canonical.d.ts.map +1 -0
- package/dist/v2/crypto/canonical.js +111 -0
- package/dist/v2/crypto/canonical.js.map +1 -0
- package/dist/v2/crypto/dh-path.d.ts +21 -0
- package/dist/v2/crypto/dh-path.d.ts.map +1 -0
- package/dist/v2/crypto/dh-path.js +50 -0
- package/dist/v2/crypto/dh-path.js.map +1 -0
- package/dist/v2/crypto/ecdh.d.ts +19 -0
- package/dist/v2/crypto/ecdh.d.ts.map +1 -0
- package/dist/v2/crypto/ecdh.js +101 -0
- package/dist/v2/crypto/ecdh.js.map +1 -0
- package/dist/v2/crypto/ecdsa.d.ts +16 -0
- package/dist/v2/crypto/ecdsa.d.ts.map +1 -0
- package/dist/v2/crypto/ecdsa.js +52 -0
- package/dist/v2/crypto/ecdsa.js.map +1 -0
- package/dist/v2/crypto/hkdf.d.ts +21 -0
- package/dist/v2/crypto/hkdf.d.ts.map +1 -0
- package/dist/v2/crypto/hkdf.js +32 -0
- package/dist/v2/crypto/hkdf.js.map +1 -0
- package/dist/v2/crypto/index.d.ts +9 -0
- package/dist/v2/crypto/index.d.ts.map +1 -0
- package/dist/v2/crypto/index.js +8 -0
- package/dist/v2/crypto/index.js.map +1 -0
- package/dist/v2/crypto/recipients.d.ts +43 -0
- package/dist/v2/crypto/recipients.d.ts.map +1 -0
- package/dist/v2/crypto/recipients.js +188 -0
- package/dist/v2/crypto/recipients.js.map +1 -0
- package/dist/v2/e2ee/decrypt.d.ts +13 -0
- package/dist/v2/e2ee/decrypt.d.ts.map +1 -0
- package/dist/v2/e2ee/decrypt.js +176 -0
- package/dist/v2/e2ee/decrypt.js.map +1 -0
- package/dist/v2/e2ee/encrypt-group.d.ts +14 -0
- package/dist/v2/e2ee/encrypt-group.d.ts.map +1 -0
- package/dist/v2/e2ee/encrypt-group.js +196 -0
- package/dist/v2/e2ee/encrypt-group.js.map +1 -0
- package/dist/v2/e2ee/encrypt-p2p.d.ts +15 -0
- package/dist/v2/e2ee/encrypt-p2p.d.ts.map +1 -0
- package/dist/v2/e2ee/encrypt-p2p.js +240 -0
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -0
- package/dist/v2/e2ee/index.d.ts +9 -0
- package/dist/v2/e2ee/index.d.ts.map +1 -0
- package/dist/v2/e2ee/index.js +9 -0
- package/dist/v2/e2ee/index.js.map +1 -0
- package/dist/v2/e2ee/metadata-auth.d.ts +9 -0
- package/dist/v2/e2ee/metadata-auth.d.ts.map +1 -0
- package/dist/v2/e2ee/metadata-auth.js +60 -0
- package/dist/v2/e2ee/metadata-auth.js.map +1 -0
- package/dist/v2/e2ee/types.d.ts +57 -0
- package/dist/v2/e2ee/types.d.ts.map +1 -0
- package/dist/v2/e2ee/types.js +7 -0
- package/dist/v2/e2ee/types.js.map +1 -0
- package/dist/v2/session/index.d.ts +4 -0
- package/dist/v2/session/index.d.ts.map +1 -0
- package/dist/v2/session/index.js +3 -0
- package/dist/v2/session/index.js.map +1 -0
- package/dist/v2/session/keystore.d.ts +48 -0
- package/dist/v2/session/keystore.d.ts.map +1 -0
- package/dist/v2/session/keystore.js +184 -0
- package/dist/v2/session/keystore.js.map +1 -0
- package/dist/v2/session/session.d.ts +98 -0
- package/dist/v2/session/session.d.ts.map +1 -0
- package/dist/v2/session/session.js +270 -0
- package/dist/v2/session/session.js.map +1 -0
- package/dist/v2/state/commitment.d.ts +10 -0
- package/dist/v2/state/commitment.d.ts.map +1 -0
- package/dist/v2/state/commitment.js +86 -0
- package/dist/v2/state/commitment.js.map +1 -0
- package/dist/v2/state/index.d.ts +2 -0
- package/dist/v2/state/index.d.ts.map +1 -0
- package/dist/v2/state/index.js +2 -0
- package/dist/v2/state/index.js.map +1 -0
- package/package.json +43 -37
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
# AUN-E2EE 群组扩展规范
|
|
2
|
+
|
|
3
|
+
> 版本:1.0-draft
|
|
4
|
+
> 状态:规范性文档
|
|
5
|
+
> 适用范围:AUN 客户端 SDK、客户端应用、跨语言实现
|
|
6
|
+
> 不适用范围:Group Service 服务端加解密实现
|
|
7
|
+
> 前置依赖:[08-AUN-E2EE](./08-AUN-E2EE.md)(P2P E2EE 规范)、[10-Group-子协议](./10-Group-子协议.md)
|
|
8
|
+
> 定位:**群组消息端到端加密层**,基于 Epoch Group Key 机制
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. 目标与边界
|
|
13
|
+
|
|
14
|
+
本规范定义 AUN 群组成员之间的端到端消息加密协议。
|
|
15
|
+
|
|
16
|
+
### 1.1 目标
|
|
17
|
+
|
|
18
|
+
- 让群组内的 N 个成员在现有 `group.send` / `event/group.message_created` 之上实现端到端加密
|
|
19
|
+
- 让 Group Service 仅看到最小必要路由元数据和密文 payload
|
|
20
|
+
- 保持与 P2P E2EE 一致的**无状态设计**——每条群消息独立可解密,不依赖任何历史状态
|
|
21
|
+
- 为各语言 SDK 提供统一的群组密文格式、密钥分发协议和恢复机制
|
|
22
|
+
|
|
23
|
+
### 1.2 服务端职责
|
|
24
|
+
|
|
25
|
+
Group Service **只做**:
|
|
26
|
+
|
|
27
|
+
- 认证发送方(JWT token)
|
|
28
|
+
- 校验群成员权限
|
|
29
|
+
- 透传 `encrypted: true` 的 payload
|
|
30
|
+
- 存储和广播密文 payload
|
|
31
|
+
- **Epoch 版本协调**:提供 `group.e2ee.get_epoch` 和 `group.e2ee.rotate_epoch` RPC,作为 epoch 版本的 CAS(Compare-And-Swap)同步点,确保并发轮换不冲突
|
|
32
|
+
|
|
33
|
+
Group Service **绝不做**:
|
|
34
|
+
|
|
35
|
+
- 加解密群消息
|
|
36
|
+
- 持有或管理 group_secret 明文
|
|
37
|
+
- 参与密钥协商或密钥派生
|
|
38
|
+
|
|
39
|
+
### 1.3 设计原则
|
|
40
|
+
|
|
41
|
+
| 原则 | 说明 |
|
|
42
|
+
|------|------|
|
|
43
|
+
| **无状态** | 每条消息独立派生密钥,不维护链式状态,不可能断链 |
|
|
44
|
+
| **复用 P2P E2EE** | group_secret 分发完全复用 P2P E2EE 通道(prekey_ecdh_v2 / long_term_key) |
|
|
45
|
+
| **不信任服务端** | group_secret 从未经过服务端明文,通过成员列表承诺辅助检测注入 |
|
|
46
|
+
| **最小状态** | 每个群客户端需保存 `epoch`、`group_secret`、`commitment`、`member_aids`、`updated_at`;可选保留 `old_epochs[]` 用于历史消息解密 |
|
|
47
|
+
|
|
48
|
+
### 1.4 无状态设计哲学
|
|
49
|
+
|
|
50
|
+
本规范延续 P2P E2EE([08-AUN-E2EE](./08-AUN-E2EE.md) §2)确立的**完全无状态**工程哲学。
|
|
51
|
+
|
|
52
|
+
经典群组 E2EE 方案(Signal Sender Keys、MLS TreeKEM、ANP Group Session)均维护有状态的密钥链:每个 sender 持有 hash chain,接收方需要同步 chain_index,消息必须按序处理。这在 Agent 场景下带来严重的工程风险:
|
|
53
|
+
|
|
54
|
+
| 有状态群组方案的代价 | AUN 场景下的问题 |
|
|
55
|
+
|-------------------|----------------|
|
|
56
|
+
| 每个 sender 一个 chain state | Agent 数量动态变化,状态管理复杂 |
|
|
57
|
+
| 消息乱序需要缓存跳过的密钥 | Agent 通信天然异步,乱序是常态 |
|
|
58
|
+
| chain_index 不同步导致断链 | 断链后整个群组通信中断,不可接受 |
|
|
59
|
+
| 状态丢失不可恢复(独立 Sender Keys) | Agent 重启频繁 |
|
|
60
|
+
|
|
61
|
+
AUN Group E2EE 选择 **Epoch Group Key**——每条消息从 group_secret + message_id 独立派生密钥,不维护任何链式状态:
|
|
62
|
+
|
|
63
|
+
- **不可能断链**:消息乱序、丢失、客户端重启均不影响解密
|
|
64
|
+
- **状态可恢复**:group_secret 在手,任何消息都能解密;丢失时可向任意成员请求补发
|
|
65
|
+
- **O(n) 分发**:无需每个 sender 逐人广播 chain key(Signal/ANP 为 O(n²))
|
|
66
|
+
|
|
67
|
+
代价是放弃 epoch 内前向安全(同一 epoch 内的消息共享 group_secret),通过**缩短 epoch 生命周期**(定时轮换)来弥补。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 2. 与 AUN-Core 的关系
|
|
72
|
+
|
|
73
|
+
AUN-E2EE-Group 建立在以下核心能力之上:
|
|
74
|
+
|
|
75
|
+
- **P2P E2EE**(08-AUN-E2EE):group_secret 分发通道
|
|
76
|
+
- **Group 子协议**(10-Group-子协议):群组管理、成员管理、消息传输
|
|
77
|
+
- **AID + 证书链身份体系**:成员身份验证
|
|
78
|
+
|
|
79
|
+
群组密文消息通过 `group.send` 承载;Group Service 无需识别 payload 内部字段。
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 3. 术语
|
|
84
|
+
|
|
85
|
+
### 3.1 Epoch
|
|
86
|
+
|
|
87
|
+
群组密钥的版本号。每次需要轮换 group_secret 时,epoch 递增。epoch 从 1 开始。
|
|
88
|
+
|
|
89
|
+
### 3.2 Group Secret
|
|
90
|
+
|
|
91
|
+
群组对称密钥。256 位随机字节,用于派生每条群消息的加密密钥。每个 epoch 对应一个独立的 group_secret。
|
|
92
|
+
|
|
93
|
+
### 3.3 Epoch Key Distribution
|
|
94
|
+
|
|
95
|
+
通过 P2P E2EE 通道向群组成员分发 group_secret 的过程。
|
|
96
|
+
|
|
97
|
+
### 3.4 Membership Commitment
|
|
98
|
+
|
|
99
|
+
群成员列表的 SHA-256 摘要,用于防止服务端篡改成员列表。
|
|
100
|
+
|
|
101
|
+
### 3.5 密文群消息
|
|
102
|
+
|
|
103
|
+
通过 `group.send` 传输的加密群消息,`encrypted` **MUST** 为 `true`,`payload.type` **MUST** 为 `e2ee.group_encrypted`。
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 4. 算法套件
|
|
108
|
+
|
|
109
|
+
与 P2P E2EE 保持一致:
|
|
110
|
+
|
|
111
|
+
- **MUST** 支持 `P256_HKDF_SHA256_AES_256_GCM`
|
|
112
|
+
- **MAY** 支持其他套件
|
|
113
|
+
|
|
114
|
+
### 4.1 密钥派生
|
|
115
|
+
|
|
116
|
+
每条群消息的加密密钥从 group_secret 独立派生:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
msg_key = HKDF-SHA256(
|
|
120
|
+
ikm = group_secret,
|
|
121
|
+
salt = None,
|
|
122
|
+
info = "aun-group:{group_id}:msg:{message_id}",
|
|
123
|
+
length = 32
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- `group_id`:群组唯一标识
|
|
128
|
+
- `message_id`:消息 UUID,由**发送方客户端**在加密前生成(格式 `gm-{uuid}`),写入 AAD 并参与密钥派生。注意:Group Service 会在外层消息记录中填充自己的 `message_id`,该值与 AAD 中的 `message_id` 可能不同。解密时 **MUST** 使用 AAD 中的 `message_id`。
|
|
129
|
+
|
|
130
|
+
### 4.2 消息加密
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
nonce = random(12 bytes)
|
|
134
|
+
aad_bytes = canonical_json(aad)
|
|
135
|
+
ciphertext = AES-256-GCM(msg_key, nonce, plaintext, aad_bytes)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 4.3 消息解密
|
|
139
|
+
|
|
140
|
+
接收方从密文 payload 内部的 `aad` 字段读取 `group_id` 和 `message_id`(即发送方加密时写入的原始值),结合本地持有的 `group_secret`,执行相同的 HKDF 派生和 AES-256-GCM 解密。
|
|
141
|
+
|
|
142
|
+
> **实现注意**:外层消息记录中的 `message_id`、`sender_aid` 由 Group Service 填充,可能与 AAD 中的值不同。密钥派生和 AAD 校验 **MUST** 以 payload 内部的 `aad` 为准。同时,接收方 **MUST** 校验外层 `group_id` 与 AAD 中的 `group_id` 一致,外层 `sender_aid`/`from` 与 AAD 中的 `from` 一致,不一致时拒绝解密。
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 5. Epoch 生命周期
|
|
147
|
+
|
|
148
|
+
### 5.1 Epoch 轮换触发条件
|
|
149
|
+
|
|
150
|
+
| 触发条件 | 是否 MUST 轮换 | 说明 |
|
|
151
|
+
|----------|:-----------:|------|
|
|
152
|
+
| 成员被踢出(`group.kick`) | **MUST** | 离开者仍持有旧 group_secret |
|
|
153
|
+
| 成员主动退出(`group.leave`) | **MUST** | 离开者仍持有旧 group_secret;剩余在线 admin/owner 负责轮换,离开者自身不执行轮换 |
|
|
154
|
+
| 成员加入(`group.add_member`) | **MUST** | 新成员加入会改变成员集;服务端记录 `min_read_epoch = join_epoch + 1`,客户端 MUST 轮换后再向新成员分发新 epoch 密钥 |
|
|
155
|
+
| 定时轮换 | **MAY** | 缩小密钥泄露窗口,建议每 24 小时 |
|
|
156
|
+
| 管理员手动轮换 | **MAY** | 怀疑密钥泄露时主动触发 |
|
|
157
|
+
| 群组解散(`group.dissolve`) | 不适用 | 群组不再存在 |
|
|
158
|
+
|
|
159
|
+
### 5.2 Epoch 轮换流程
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
1. 触发者(admin/owner)生成新的 group_secret:
|
|
163
|
+
group_secret = random(32 bytes)
|
|
164
|
+
epoch += 1
|
|
165
|
+
|
|
166
|
+
2. 计算 Membership Commitment(§6)
|
|
167
|
+
|
|
168
|
+
3. 构建并签名 Membership Manifest(§6A)
|
|
169
|
+
|
|
170
|
+
4. 通过 P2P E2EE 逐个分发给每个当前成员:
|
|
171
|
+
for member in current_members:
|
|
172
|
+
p2p_encrypt_send(member, {
|
|
173
|
+
type: "e2ee.group_key_distribution",
|
|
174
|
+
group_id,
|
|
175
|
+
epoch,
|
|
176
|
+
group_secret, // 32 bytes, base64
|
|
177
|
+
commitment, // SHA-256 hex
|
|
178
|
+
member_aids, // 排序后的完整成员 AID 列表
|
|
179
|
+
distributed_by, // 分发者 AID
|
|
180
|
+
distributed_at, // 分发时间戳(ms)
|
|
181
|
+
manifest // 签名的 Membership Manifest(§6A)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
4. 本地持久化新的 group_secret 和 epoch
|
|
185
|
+
5. 安全擦除旧的 group_secret(MAY 保留至旧 epoch 消息超时)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 5.2.1 Epoch CAS 轮换 RPC
|
|
189
|
+
|
|
190
|
+
通过 `group.e2ee.rotate_epoch` RPC 在服务端进行 CAS(Compare-And-Swap)轮换。
|
|
191
|
+
|
|
192
|
+
**必填参数**:
|
|
193
|
+
|
|
194
|
+
| 参数 | 类型 | 说明 |
|
|
195
|
+
|------|------|------|
|
|
196
|
+
| `group_id` | string | 群组标识 |
|
|
197
|
+
| `current_epoch` | int | 当前 epoch(CAS 条件) |
|
|
198
|
+
| `rotation_signature` | string | base64 编码的 ECDSA 签名 |
|
|
199
|
+
| `rotation_timestamp` | string | 签名时间戳(Unix 秒),5 分钟新鲜度窗口 |
|
|
200
|
+
|
|
201
|
+
**签名输入**:`"{group_id}|{current_epoch}|{new_epoch}|{aid}|{rotation_timestamp}"`
|
|
202
|
+
|
|
203
|
+
服务端 **MUST** 验证签名有效性、时间戳新鲜度,并拒绝重复签名。
|
|
204
|
+
|
|
205
|
+
### 5.3 新成员加入
|
|
206
|
+
|
|
207
|
+
新成员加入时 **MUST** 触发 epoch 轮换。服务端在成员写入时 **MUST** 记录加入时的 `join_epoch`,并将该成员的 `min_read_epoch` 设置为 `join_epoch + 1`(若群组尚未启用 E2EE epoch,则为 0)。因此,新成员 **MUST NOT** 获得加入前 epoch 的 group_secret。
|
|
208
|
+
|
|
209
|
+
执行加入操作的 admin/owner,或成员变更事件选举出的在线 owner/admin leader,**MUST** 通过 `group.e2ee.rotate_epoch` 完成 CAS 轮换,然后通过 P2P E2EE 向当前成员(包含新成员)分发新 epoch 的 group_secret:
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
p2p_encrypt_send(new_member, {
|
|
213
|
+
type: "e2ee.group_key_distribution",
|
|
214
|
+
group_id,
|
|
215
|
+
epoch, // 新 epoch
|
|
216
|
+
group_secret,
|
|
217
|
+
commitment,
|
|
218
|
+
member_aids, // 含新成员的列表
|
|
219
|
+
distributed_by,
|
|
220
|
+
distributed_at,
|
|
221
|
+
manifest // 签名的 Membership Manifest(§6A)
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
新成员收到后只能解密新 epoch 及之后的群消息。若轮换尚未完成,服务端 **SHOULD** 拒绝新成员发送低于 `min_read_epoch` 的群组 E2EE 消息,并返回“epoch rotation pending”类错误。
|
|
226
|
+
|
|
227
|
+
### 5.4 分发职责
|
|
228
|
+
|
|
229
|
+
group_secret 的分发 **MUST NOT** 依赖单一节点:
|
|
230
|
+
|
|
231
|
+
| 场景 | 分发者 |
|
|
232
|
+
|------|--------|
|
|
233
|
+
| 踢人 | 执行 `group.kick` 的 admin/owner |
|
|
234
|
+
| 成员退出 | 剩余在线 admin/owner(离开者不执行轮换) |
|
|
235
|
+
| 加人/审批通过/邀请码入群 | 执行操作的 admin/owner 或成员变更事件选举出的在线 owner/admin leader |
|
|
236
|
+
| 定时轮换 | 任意在线 admin/owner |
|
|
237
|
+
| 密钥补发 | 任意持有当前 group_secret 的成员(§8) |
|
|
238
|
+
|
|
239
|
+
### 5.5 历史消息访问策略
|
|
240
|
+
|
|
241
|
+
| 策略 | 行为 | 适用场景 |
|
|
242
|
+
|------|------|---------|
|
|
243
|
+
| **加入前历史隔离**(默认且唯一) | 加入时触发 epoch 轮换,新成员只拿到新 group_secret;服务端通过 `min_read_epoch` 阻止其使用旧 epoch | 所有群组 |
|
|
244
|
+
|
|
245
|
+
如业务需要向新成员开放入群前历史,**MUST** 在应用层通过单独的历史授权、导出或重加密流程实现,不得复用加入前的 group_secret。
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 6. Membership Commitment
|
|
250
|
+
|
|
251
|
+
### 6.1 目的
|
|
252
|
+
|
|
253
|
+
让所有群成员能够**检测**成员列表篡改。当分发者将 group_secret 发送给群成员时,附带一个基于成员列表的哈希摘要。接收方通过重算摘要来验证自己收到的成员列表是否自洽。
|
|
254
|
+
|
|
255
|
+
> **局限性**:Membership Commitment 是纯哈希校验,不包含密码学签名。它能确保所有成员收到的列表一致(一致性检测),但**不能**独立阻止恶意分发者构造虚假列表。防御幽灵成员注入的有效性依赖于:(1) 合法成员能从其他渠道(如 `group.get_members` RPC)获取可信成员列表并比对;(2) 多个成员的 commitment 互相印证。
|
|
256
|
+
|
|
257
|
+
### 6.2 计算方式
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
commitment = SHA-256(
|
|
261
|
+
sort(member_aids).join("|") + "|" + str(epoch) + "|" + group_id + "|" + SHA-256(group_secret).hex()
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
其中:
|
|
266
|
+
- `sort(member_aids)` 为所有当前群成员的 AID 按字典序升序排列
|
|
267
|
+
- `SHA-256(group_secret).hex()` 为 group_secret(32 字节原始密钥)的 SHA-256 哈希的十六进制表示
|
|
268
|
+
- 将 group_secret 的哈希绑定到 commitment 中,防止恶意分发者替换密钥但保持 commitment 不变
|
|
269
|
+
|
|
270
|
+
### 6.3 验证流程
|
|
271
|
+
|
|
272
|
+
接收方收到 `e2ee.group_key_distribution` 消息后 **MUST**:
|
|
273
|
+
|
|
274
|
+
1. 验证 `commitment == SHA-256(sort(member_aids) + "|" + epoch + "|" + group_id + "|" + SHA-256(group_secret).hex())`
|
|
275
|
+
2. 验证自己的 AID 在 `member_aids` 列表中
|
|
276
|
+
3. 如果 `member_aids` 与本地已知的成员列表存在差异,**SHOULD** 向用户发出告警
|
|
277
|
+
4. **SHOULD** 通过 `group.get_members` RPC 获取服务端成员列表进行比对(如果可用)
|
|
278
|
+
|
|
279
|
+
### 6.4 防护效果
|
|
280
|
+
|
|
281
|
+
| 攻击 | 防护级别 | 说明 |
|
|
282
|
+
|------|:-------:|------|
|
|
283
|
+
| 成员列表不一致 | ✅ 检测 | 所有人收到同一个 commitment,可互相比对 |
|
|
284
|
+
| 幽灵成员注入(分发者被骗) | ⚠️ 辅助检测 | 合法成员看到完整列表后有机会发现异常 AID,但需要额外的可信成员列表源进行比对 |
|
|
285
|
+
| 幽灵成员注入(分发者串通) | ❌ 无法防御 | commitment 由分发者构造,串通场景下哈希无意义 |
|
|
286
|
+
| epoch 轮换阻断 | ⚠️ 需检测 | 需要客户端检测:成员变更事件后应在合理时间内收到新 epoch |
|
|
287
|
+
| 选择性消息丢弃 | ❌ 无法防御 | 加密层无法解决,需消息确认/回执机制 |
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 6A. Membership Manifest(成员变更授权凭证)
|
|
292
|
+
|
|
293
|
+
### 6A.1 目的
|
|
294
|
+
|
|
295
|
+
Membership Commitment(§6)是纯哈希检测,无法证明「谁发起了这次成员变更」。Membership Manifest 补充了密码学签名层,让接收方能验证:
|
|
296
|
+
|
|
297
|
+
- **谁**发起了 epoch 轮换(`initiator_aid`)
|
|
298
|
+
- **哪些成员**被添加或移除(`added`、`removed`)
|
|
299
|
+
- 该操作经过了**合法授权**(ECDSA 签名)
|
|
300
|
+
|
|
301
|
+
### 6A.2 Manifest 结构
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"manifest_version": 1,
|
|
306
|
+
"group_id": "g-abc123.agentid.pub",
|
|
307
|
+
"epoch": 2,
|
|
308
|
+
"prev_epoch": 1,
|
|
309
|
+
"member_aids": ["alice.agentid.pub", "bob.agentid.pub", "carol.agentid.pub"],
|
|
310
|
+
"added": ["carol.agentid.pub"],
|
|
311
|
+
"removed": [],
|
|
312
|
+
"initiator_aid": "alice.agentid.pub",
|
|
313
|
+
"issued_at": 1710504000000,
|
|
314
|
+
"signature": "base64(ECDSA-SHA256)"
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
| 字段 | 类型 | 说明 |
|
|
319
|
+
|------|------|------|
|
|
320
|
+
| `manifest_version` | integer | Manifest 格式版本,当前为 `1` |
|
|
321
|
+
| `group_id` | string | 群组标识 |
|
|
322
|
+
| `epoch` | integer | 本次轮换后的 epoch |
|
|
323
|
+
| `prev_epoch` | integer / null | 上一个 epoch(首次创建时为 `null`) |
|
|
324
|
+
| `member_aids` | string[] | 本 epoch 的完整成员列表(排序) |
|
|
325
|
+
| `added` | string[] | 本次新增的成员 |
|
|
326
|
+
| `removed` | string[] | 本次移除的成员 |
|
|
327
|
+
| `initiator_aid` | string | 发起者 AID |
|
|
328
|
+
| `issued_at` | integer | 签发时间戳(ms) |
|
|
329
|
+
| `signature` | string | 发起者对 manifest 内容的 ECDSA-SHA256 签名 |
|
|
330
|
+
|
|
331
|
+
### 6A.3 签名载荷
|
|
332
|
+
|
|
333
|
+
签名覆盖除 `signature` 字段外的所有字段,序列化方式:
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
sign_data = canonical_json(manifest_without_signature)
|
|
337
|
+
// canonical_json: sort_keys=True, separators=(",",":"), ensure_ascii=False
|
|
338
|
+
signature = ECDSA-SHA256(initiator_private_key, sign_data)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### 6A.4 验证流程
|
|
342
|
+
|
|
343
|
+
接收方收到包含 `manifest` 的 `e2ee.group_key_distribution` 消息后 **SHOULD**:
|
|
344
|
+
|
|
345
|
+
1. 从本地缓存获取 `initiator_aid` 的证书公钥
|
|
346
|
+
2. 验证 `signature`
|
|
347
|
+
3. 检查 `member_aids` 与 manifest 中 `added`/`removed` 的一致性
|
|
348
|
+
4. 检查 `epoch` == `prev_epoch + 1`(首次创建时 `prev_epoch` 为 `null`)
|
|
349
|
+
5. 验签失败时 **SHOULD** 拒绝该 distribution(返回 `"distribution_rejected"`)
|
|
350
|
+
|
|
351
|
+
> **注意**:Manifest 验证是**建议性的**(SHOULD),不是强制性的。这是因为在某些场景下(如跨域成员加入),接收方可能尚未缓存发起者证书。实现 **MAY** 在验签失败时仍接受 distribution,但 **MUST** 在后台尝试获取发起者证书并进行延迟验证。
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## 7. 消息格式与 AAD
|
|
356
|
+
|
|
357
|
+
### 7.1 群组密文 payload
|
|
358
|
+
|
|
359
|
+
```json
|
|
360
|
+
{
|
|
361
|
+
"type": "e2ee.group_encrypted",
|
|
362
|
+
"version": "1",
|
|
363
|
+
"encryption_mode": "epoch_group_key",
|
|
364
|
+
"suite": "P256_HKDF_SHA256_AES_256_GCM",
|
|
365
|
+
"epoch": 3,
|
|
366
|
+
"nonce": "base64(12 bytes)",
|
|
367
|
+
"ciphertext": "base64",
|
|
368
|
+
"tag": "base64(16 bytes)",
|
|
369
|
+
"sender_signature": "base64(ECDSA-SHA256 over ciphertext+tag+aad_bytes)",
|
|
370
|
+
"sender_cert_fingerprint": "sha256:hex",
|
|
371
|
+
"aad": {
|
|
372
|
+
"group_id": "g-abc123.agentid.pub",
|
|
373
|
+
"from": "alice.agentid.pub",
|
|
374
|
+
"message_id": "gm-550e8400-...",
|
|
375
|
+
"timestamp": 1710504000000,
|
|
376
|
+
"epoch": 3,
|
|
377
|
+
"encryption_mode": "epoch_group_key",
|
|
378
|
+
"suite": "P256_HKDF_SHA256_AES_256_GCM"
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**发送方签名**:
|
|
384
|
+
|
|
385
|
+
- 发送方 **MUST** 用身份私钥对 `ciphertext_bytes + tag_bytes + aad_bytes` 执行 ECDSA-SHA256 签名
|
|
386
|
+
- `sender_cert_fingerprint` 用于接收方查找发送方证书
|
|
387
|
+
- 接收方 **MUST** 验证 `sender_signature`,缺失或验签失败时 **MUST** 拒绝该消息
|
|
388
|
+
|
|
389
|
+
### 7.2 外层 group.send 信封
|
|
390
|
+
|
|
391
|
+
```json
|
|
392
|
+
{
|
|
393
|
+
"jsonrpc": "2.0",
|
|
394
|
+
"method": "group.send",
|
|
395
|
+
"params": {
|
|
396
|
+
"group_id": "g-abc123.agentid.pub",
|
|
397
|
+
"type": "e2ee.group_encrypted",
|
|
398
|
+
"payload": { "<上述密文 payload>" },
|
|
399
|
+
"encrypted": true
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### 7.3 AAD 字段
|
|
405
|
+
|
|
406
|
+
| 字段 | 类型 | 说明 |
|
|
407
|
+
|------|------|------|
|
|
408
|
+
| `group_id` | string | 群组唯一标识 |
|
|
409
|
+
| `from` | string | 发送方 AID |
|
|
410
|
+
| `message_id` | string | 消息唯一标识(发送方客户端生成,参与密钥派生) |
|
|
411
|
+
| `timestamp` | integer | 发送时间戳(ms) |
|
|
412
|
+
| `epoch` | integer | 当前密钥版本号 |
|
|
413
|
+
| `encryption_mode` | string | 固定为 `epoch_group_key` |
|
|
414
|
+
| `suite` | string | 算法套件标识 |
|
|
415
|
+
|
|
416
|
+
AAD 序列化方式与 P2P E2EE(§8.3)一致:递归键排序 + 紧凑格式 + UTF-8 直接输出(`ensure_ascii=False`),详见 P2P E2EE §8.3 的完整规范。
|
|
417
|
+
|
|
418
|
+
### 7.4 明文信封字段(服务端可见)
|
|
419
|
+
|
|
420
|
+
以下字段保持为 Group Service 可见的明文,用于路由和存储:
|
|
421
|
+
|
|
422
|
+
| 字段 | 说明 |
|
|
423
|
+
|------|------|
|
|
424
|
+
| `group_id` | 群组标识(路由用) |
|
|
425
|
+
| `type` | 固定为 `e2ee.group_encrypted` |
|
|
426
|
+
| `encrypted` | 固定为 `true` |
|
|
427
|
+
|
|
428
|
+
`message_id`、`seq`、`sender_aid`、`created_at` 由 Group Service 自动填充到消息记录中。
|
|
429
|
+
|
|
430
|
+
> **外层与 AAD 绑定校验**:接收方 **MUST** 校验外层 `group_id` 与 AAD 中的 `group_id` 一致(防止跨群路由篡改),外层 `from`/`sender_aid` 与 AAD 中的 `from` 一致(防止发送者冒充)。不一致时 **MUST** 拒绝解密。
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## 8. 密钥恢复机制
|
|
435
|
+
|
|
436
|
+
### 8.1 场景
|
|
437
|
+
|
|
438
|
+
以下场景可能导致成员缺失当前 epoch 的 group_secret:
|
|
439
|
+
|
|
440
|
+
- P2P 分发消息丢失(网络故障)
|
|
441
|
+
- 客户端重启后本地存储损坏
|
|
442
|
+
- 成员离线期间发生了 epoch 轮换
|
|
443
|
+
|
|
444
|
+
### 8.2 Epoch Key Request
|
|
445
|
+
|
|
446
|
+
缺失密钥的成员 **MAY** 向群内候选成员发送密钥请求(SDK 优先从本地成员列表选择,零状态时退化为向当前消息发送者请求):
|
|
447
|
+
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"type": "e2ee.group_key_request",
|
|
451
|
+
"group_id": "g-abc123.agentid.pub",
|
|
452
|
+
"epoch": 3,
|
|
453
|
+
"requester_aid": "bob.agentid.pub"
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
此消息 **MUST** 通过 P2P E2EE 通道发送。
|
|
458
|
+
|
|
459
|
+
### 8.3 Epoch Key Response
|
|
460
|
+
|
|
461
|
+
收到请求的成员 **MUST** 先验证请求者确实是当前群成员(通过 `group.get_members` 或本地缓存),然后 **MAY** 回复:
|
|
462
|
+
|
|
463
|
+
```json
|
|
464
|
+
{
|
|
465
|
+
"type": "e2ee.group_key_response",
|
|
466
|
+
"group_id": "g-abc123.agentid.pub",
|
|
467
|
+
"epoch": 3,
|
|
468
|
+
"group_secret": "base64(32 bytes)",
|
|
469
|
+
"commitment": "sha256hex",
|
|
470
|
+
"member_aids": ["alice.agentid.pub", "bob.agentid.pub", "carol.agentid.pub"]
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
此消息 **MUST** 通过 P2P E2EE 通道发送。
|
|
475
|
+
|
|
476
|
+
### 8.4 安全约束
|
|
477
|
+
|
|
478
|
+
- 响应者 **MUST** 验证请求者是群成员后才能回复
|
|
479
|
+
- 请求者 **MUST** 验证 commitment 和 member_aids 的一致性(§6.3)
|
|
480
|
+
- 实现 **SHOULD** 对 key_request 做频率限制,防止被滥用
|
|
481
|
+
|
|
482
|
+
### 8.5 恢复时序
|
|
483
|
+
|
|
484
|
+
密钥恢复是**异步过程**:
|
|
485
|
+
|
|
486
|
+
1. 成员收到无法解密的群消息(epoch 不匹配或无密钥)
|
|
487
|
+
2. SDK 自动向候选成员发送 `e2ee.group_key_request`(优先本地已知成员,零状态时向消息发送者请求)
|
|
488
|
+
3. 在线成员验证请求者身份后回复 `e2ee.group_key_response`
|
|
489
|
+
4. 请求者收到响应后存储 group_secret,后续 pull 或再次收到消息时才能成功解密
|
|
490
|
+
|
|
491
|
+
> `group.use_invite_code` 加入群组后不保证立即拥有 group_secret。SDK 会在后续群消息解密失败时自动发起恢复请求。
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## 9. 客户端密钥存储
|
|
496
|
+
|
|
497
|
+
### 9.1 存储位置
|
|
498
|
+
|
|
499
|
+
group_secret **MUST** 持久化到本地存储。推荐存储在 FileKeyStore 的 metadata 中:
|
|
500
|
+
|
|
501
|
+
```
|
|
502
|
+
~/.aun/AIDs/{safe_aid}/tokens/meta.json
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### 9.2 存储结构
|
|
506
|
+
|
|
507
|
+
在 metadata 中新增 `group_secrets` 字段:
|
|
508
|
+
|
|
509
|
+
```json
|
|
510
|
+
{
|
|
511
|
+
"e2ee_prekeys": { "..." },
|
|
512
|
+
"group_secrets": {
|
|
513
|
+
"g-abc123.agentid.pub": {
|
|
514
|
+
"epoch": 3,
|
|
515
|
+
"secret_protection": {
|
|
516
|
+
"scheme": "dpapi",
|
|
517
|
+
"name": "group_secrets/g-abc123.agentid.pub/secret",
|
|
518
|
+
"persisted": true,
|
|
519
|
+
"blob": "base64(...)"
|
|
520
|
+
},
|
|
521
|
+
"commitment": "sha256hex...",
|
|
522
|
+
"member_aids": ["alice.agentid.pub", "bob.agentid.pub"],
|
|
523
|
+
"updated_at": 1710504000000,
|
|
524
|
+
"old_epochs": [
|
|
525
|
+
{
|
|
526
|
+
"epoch": 2,
|
|
527
|
+
"secret_protection": { "..." },
|
|
528
|
+
"commitment": "sha256hex...",
|
|
529
|
+
"member_aids": ["alice.agentid.pub", "bob.agentid.pub", "carol.agentid.pub"],
|
|
530
|
+
"updated_at": 1710500000000
|
|
531
|
+
}
|
|
532
|
+
]
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
> **说明**:`old_epochs` 数组保留旧 epoch 的密钥信息,用于解密历史消息。保留期由 `old_epoch_retention_seconds`(默认 7 天)控制,过期后由 SDK 自动清理。
|
|
539
|
+
|
|
540
|
+
### 9.3 敏感字段保护
|
|
541
|
+
|
|
542
|
+
- `group_secret` 的明文 **MUST NOT** 直接写入磁盘
|
|
543
|
+
- **MUST** 通过 SecretStore(DPAPI / Keychain / libsecret)保护
|
|
544
|
+
- 存储格式与 P2P prekey 私钥保护方式一致:明文替换为 `secret_protection` 记录
|
|
545
|
+
|
|
546
|
+
保护流程:
|
|
547
|
+
|
|
548
|
+
```
|
|
549
|
+
写入时:
|
|
550
|
+
secret_name = f"group_secrets/{group_id}/secret"
|
|
551
|
+
record["secret_protection"] = secret_store.protect(scope, secret_name, group_secret)
|
|
552
|
+
// group_secret 明文不落盘
|
|
553
|
+
|
|
554
|
+
读取时:
|
|
555
|
+
group_secret = secret_store.reveal(scope, secret_name, record["secret_protection"])
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### 9.4 旧 Epoch 密钥保留
|
|
559
|
+
|
|
560
|
+
- 实现 **MAY** 保留旧 epoch 的 group_secret 一段时间(建议 7 天),用于解密在途或离线期间的历史消息
|
|
561
|
+
- 旧 epoch 密钥 **SHOULD** 在保留期过后安全擦除
|
|
562
|
+
- 旧 epoch 密钥 **MUST NOT** 用于加密新消息
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## 10. 防重放与防篡改
|
|
567
|
+
|
|
568
|
+
### 10.1 防篡改
|
|
569
|
+
|
|
570
|
+
所有路由关键字段纳入 AAD(§7.3),任何篡改导致 AES-GCM tag 校验失败:
|
|
571
|
+
|
|
572
|
+
- `group_id` 被篡改 → AAD mismatch → 解密异常
|
|
573
|
+
- `from`(sender_aid)被替换 → AAD mismatch → 解密异常
|
|
574
|
+
- `epoch` 被篡改 → HKDF 派生出错误的 msg_key → 解密失败
|
|
575
|
+
- `message_id` 被篡改 → msg_key 派生不同 + AAD mismatch → 解密失败
|
|
576
|
+
|
|
577
|
+
### 10.2 防重放
|
|
578
|
+
|
|
579
|
+
群组消息的防重放与 P2P 一致:
|
|
580
|
+
|
|
581
|
+
- 接收方 **MUST** 维护本地 `seen_messages` 集合
|
|
582
|
+
- 以 `{group_id}:{sender_aid}:{message_id}` 为 key 去重
|
|
583
|
+
- 同一 key 的消息 **MUST** 被拒绝
|
|
584
|
+
|
|
585
|
+
### 10.3 Epoch 降级防护
|
|
586
|
+
|
|
587
|
+
- 接收方 **MUST** 拒绝 epoch 低于本地已知最新 epoch 的加密消息
|
|
588
|
+
- 例外:如果实现保留了旧 epoch 密钥(§9.4),**MAY** 允许解密旧 epoch 消息。实现可选择在解密结果中标记 `historical: true`,但不做强制要求
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## 11. 安全约定
|
|
593
|
+
|
|
594
|
+
### 11.1 通用约定
|
|
595
|
+
|
|
596
|
+
- 加密失败时 **MUST NOT** 静默降级为明文
|
|
597
|
+
- 每条消息使用独立的随机 nonce
|
|
598
|
+
- group_secret **MUST** 由密码学安全随机数生成器生成
|
|
599
|
+
- 实现 **MUST NOT** 在日志中输出 group_secret 或 msg_key
|
|
600
|
+
|
|
601
|
+
### 11.2 分发通道安全
|
|
602
|
+
|
|
603
|
+
- group_secret 的分发 **MUST** 通过 P2P E2EE 通道(prekey_ecdh_v2 或 long_term_key 模式)
|
|
604
|
+
- 分发消息的发送方身份 **MUST** 通过 P2P E2EE 的 AAD 机制验证
|
|
605
|
+
- 分发消息 **SHOULD** 附带签名的 Membership Manifest(§6A)
|
|
606
|
+
|
|
607
|
+
### 11.3 客户端操作签名
|
|
608
|
+
|
|
609
|
+
以下群组操作 **MUST** 附加客户端 ECDSA 签名(`client_signature` 字段),服务端强制验签:
|
|
610
|
+
|
|
611
|
+
- `group.send`
|
|
612
|
+
- `group.add_member`
|
|
613
|
+
- `group.kick`
|
|
614
|
+
- `group.leave`
|
|
615
|
+
- `group.update_rules`
|
|
616
|
+
|
|
617
|
+
#### 11.3.1 签名生成
|
|
618
|
+
|
|
619
|
+
签名数据格式:`"{method}|{aid}|{timestamp}|{params_hash}"`
|
|
620
|
+
|
|
621
|
+
其中:
|
|
622
|
+
- `method`:RPC 方法名(如 `group.send`)
|
|
623
|
+
- `aid`:当前认证的 AID
|
|
624
|
+
- `timestamp`:当前 Unix 时间戳(秒,字符串形式)
|
|
625
|
+
- `params_hash`:业务参数的 SHA-256 哈希(十六进制小写),计算方法见 §11.3.2
|
|
626
|
+
|
|
627
|
+
签名算法:ECDSA-SHA256,使用身份私钥签名上述字符串的 UTF-8 编码。
|
|
628
|
+
|
|
629
|
+
`client_signature` 字段结构:
|
|
630
|
+
|
|
631
|
+
```json
|
|
632
|
+
{
|
|
633
|
+
"aid": "alice.agentid.pub",
|
|
634
|
+
"timestamp": "1775541042",
|
|
635
|
+
"params_hash": "a3b2c1d4e5f6...",
|
|
636
|
+
"signature": "<base64 DER-encoded ECDSA signature>"
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
#### 11.3.2 params_hash 计算(Canonical JSON 规范)
|
|
641
|
+
|
|
642
|
+
`params_hash` 的计算输入是业务参数的**规范化 JSON 序列化**(Canonical JSON for AUN),与 AAD 序列化规则(P2P E2EE §8.3)完全一致:
|
|
643
|
+
|
|
644
|
+
1. **字段筛选**:排除 `client_signature` 字段和所有 `_` 前缀字段(`_auth`、`_session_id` 等由网关/服务端注入的内部字段)
|
|
645
|
+
2. **键排序**:所有对象(包括嵌套对象)的键 **MUST** 按 Unicode 码点升序排列(递归排序)
|
|
646
|
+
3. **紧凑格式**:无多余空白,键值对之间用 `,`,键和值之间用 `:` 分隔
|
|
647
|
+
4. **UTF-8 直接输出**:非 ASCII 字符(如中文)**MUST** 直接以 UTF-8 编码输出,**MUST NOT** 转义为 `\uXXXX`
|
|
648
|
+
5. **数值精度**:整数值 **MUST** 序列化为不带小数点的十进制数(如 `42` 而非 `42.0`)
|
|
649
|
+
6. **布尔值**:`true` / `false`(小写)
|
|
650
|
+
7. **空值**:`null`
|
|
651
|
+
|
|
652
|
+
等价的 Python 实现:`json.dumps(params, sort_keys=True, separators=(",", ":"), ensure_ascii=False)`
|
|
653
|
+
|
|
654
|
+
> **设计决策:** `params_hash` 与 AAD 使用完全相同的 Canonical JSON 规范(`ensure_ascii=False`),避免协议内两套序列化规则导致实现混乱。Go 的 `json.Marshal` 和 JavaScript 的 `JSON.stringify` 天然满足 UTF-8 直接输出,无需额外转义处理。
|
|
655
|
+
|
|
656
|
+
> **跨语言注意事项:**
|
|
657
|
+
> - Go 的 `json.Marshal` 默认满足此规范(UTF-8 直接输出 + 自动递归键排序)
|
|
658
|
+
> - Go 的 `json.Unmarshal` 将 JSON 数字解码为 `float64`,序列化时 **MUST** 避免科学计数法(如 `1.775e+12` 应为 `1775540833687`)
|
|
659
|
+
> - JavaScript/TypeScript 的 `JSON.stringify` 满足 UTF-8 直接输出,但 **MUST** 确保嵌套对象键递归排序(需自定义序列化函数)
|
|
660
|
+
|
|
661
|
+
`params_hash = SHA-256(canonical_json_bytes).hex()`
|
|
662
|
+
|
|
663
|
+
#### 11.3.3 服务端验签
|
|
664
|
+
|
|
665
|
+
服务端 **MUST**:
|
|
666
|
+
|
|
667
|
+
1. 从收到的参数中提取 `client_signature`
|
|
668
|
+
2. 验证 `client_signature.aid` 与当前认证 AID 一致
|
|
669
|
+
3. 验证 `client_signature.timestamp` 在 ±300 秒新鲜度窗口内(防重放)
|
|
670
|
+
4. 用收到的实际参数(排除 `client_signature` 和 `_` 前缀字段)按 §11.3.2 重算 `params_hash`
|
|
671
|
+
5. 常量时间比较重算的 hash 与客户端声称的 `params_hash`
|
|
672
|
+
6. 用客户端注册的公钥验证 ECDSA-SHA256 签名
|
|
673
|
+
7. 所有步骤通过后才允许执行操作;任一步骤失败 **MUST** 返回错误码 `-32051`(ClientSignatureError)
|
|
674
|
+
|
|
675
|
+
> Python SDK、Go SDK、TypeScript SDK 均自动附加 `client_signature`,裸客户端必须自行实现。
|
|
676
|
+
|
|
677
|
+
### 11.4 成员移除后的安全保证
|
|
678
|
+
|
|
679
|
+
- 成员被踢出或退出后 **MUST** 立即触发 epoch 轮换
|
|
680
|
+
- 新的 group_secret **MUST NOT** 分发给已离开的成员
|
|
681
|
+
- 旧的 group_secret 仅用于解密历史消息,不用于加密新消息
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
## 12. 安全属性分析
|
|
686
|
+
|
|
687
|
+
### 12.1 安全属性总览
|
|
688
|
+
|
|
689
|
+
| 属性 | 表现 | 说明 |
|
|
690
|
+
|------|:----:|------|
|
|
691
|
+
| **epoch 间前向安全** | ✅ | 旧 group_secret 安全擦除后,该 epoch 历史消息不可解密 |
|
|
692
|
+
| **epoch 内前向安全** | ❌ | 同一 epoch 内所有消息共享同一 group_secret |
|
|
693
|
+
| **Post-compromise Security** | ⚠️ | 下次 epoch 轮换时恢复;可通过定时轮换缩短窗口 |
|
|
694
|
+
| **防中间人** | ✅ | group_secret 通过已认证的 P2P E2EE 分发 |
|
|
695
|
+
| **防服务端注入** | ✅ 检测 | Membership Commitment 绑定 group_secret(§6),配合 Membership Manifest 签名验证(§6A),提供密钥绑定 + 成员变更授权双重防护 |
|
|
696
|
+
| **防篡改** | ✅ | AAD 覆盖所有路由关键字段 |
|
|
697
|
+
| **防重放** | ✅ | 本地 seen_messages 去重 |
|
|
698
|
+
|
|
699
|
+
### 12.2 与 P2P E2EE 的对比
|
|
700
|
+
|
|
701
|
+
| 维度 | P2P E2EE | Group E2EE |
|
|
702
|
+
|------|---------|-----------|
|
|
703
|
+
| **加密模式** | prekey_ecdh_v2 / long_term_key | epoch_group_key |
|
|
704
|
+
| **密钥来源** | 每消息临时 ECDH | group_secret + HKDF 派生 |
|
|
705
|
+
| **前向安全** | 单消息级(每消息独立临时密钥对) | epoch 级 |
|
|
706
|
+
| **状态** | 零(纯工具类) | 最小(epoch + group_secret) |
|
|
707
|
+
| **可断链** | 不可能 | 不可能 |
|
|
708
|
+
| **密码学操作/条** | 1×ECDH + 1×HKDF + 1×AES-GCM | 1×HKDF + 1×AES-GCM |
|
|
709
|
+
|
|
710
|
+
### 12.3 与其他协议群组方案的对比
|
|
711
|
+
|
|
712
|
+
| 维度 | AUN (Epoch Group Key) | Signal (Sender Keys) | MLS (TreeKEM) | ANP (独立 Sender Keys) |
|
|
713
|
+
|------|:---:|:---:|:---:|:---:|
|
|
714
|
+
| **epoch 轮换分发** | O(n) | O(n²) | O(log n) | O(n²) |
|
|
715
|
+
| **每消息加密代价** | 1×HKDF + AES | 1×HMAC + AES | 1×HMAC + AES | 1×HMAC + AES |
|
|
716
|
+
| **epoch 内前向安全** | ❌ | ✅ | ✅ | ✅ |
|
|
717
|
+
| **状态量** | 2 字段 | n 个 chain state | 二叉树 | n 个 chain state |
|
|
718
|
+
| **断链风险** | 无 | 有 | 有 | 有 |
|
|
719
|
+
| **状态可恢复** | ✅ | ❌ | ❌ | ❌ |
|
|
720
|
+
|
|
721
|
+
### 12.4 设计折中说明
|
|
722
|
+
|
|
723
|
+
本方案选择 **epoch 级前向安全**(而非消息级),换取:
|
|
724
|
+
|
|
725
|
+
1. **零链状态**——不维护 hash chain,不需要 seq 同步
|
|
726
|
+
2. **不可能断链**——消息乱序、丢失、重启均不影响后续消息解密
|
|
727
|
+
3. **状态可恢复**——任何成员均可通过 epoch_key_request 恢复密钥
|
|
728
|
+
4. **O(n) 分发**——无需每个 sender 逐人广播自己的 chain key
|
|
729
|
+
|
|
730
|
+
epoch 内前向安全的缺失通过**缩短 epoch 生命周期**(定时轮换)来弥补。在 Agent 通信场景中,群组通常生命周期短、成员变更频繁,epoch 自然轮换速度快,该折中是合理的。
|
|
731
|
+
|
|
732
|
+
---
|
|
733
|
+
|
|
734
|
+
## 13. 完整交互时序
|
|
735
|
+
|
|
736
|
+
### 13.1 建群并发送加密消息
|
|
737
|
+
|
|
738
|
+
```
|
|
739
|
+
Alice (owner) Group Service Bob (member)
|
|
740
|
+
─────────────────────────────────────────────────────────────────────────
|
|
741
|
+
1. group.create({name: "..."})
|
|
742
|
+
← {group_id: "g-abc123.agentid.pub"}
|
|
743
|
+
|
|
744
|
+
2. group.add_member({group_id, aid: "bob"})
|
|
745
|
+
← {member: {...}}
|
|
746
|
+
|
|
747
|
+
3. 生成 group_secret (epoch=1)
|
|
748
|
+
计算 commitment
|
|
749
|
+
|
|
750
|
+
4. P2P E2EE → Bob:
|
|
751
|
+
{type: "e2ee.group_key_distribution",
|
|
752
|
+
group_id, epoch: 1,
|
|
753
|
+
group_secret, commitment, member_aids}
|
|
754
|
+
5. 验证 commitment
|
|
755
|
+
存储 group_secret
|
|
756
|
+
|
|
757
|
+
6. 发送加密群消息:
|
|
758
|
+
msg_key = HKDF(group_secret,
|
|
759
|
+
info="aun-group:g-abc123.agentid.pub:msg:gm-xxx")
|
|
760
|
+
ciphertext = AES-GCM(msg_key, plaintext, aad)
|
|
761
|
+
|
|
762
|
+
group.send({
|
|
763
|
+
group_id, encrypted: true,
|
|
764
|
+
payload: {type: "e2ee.group_encrypted", ...}
|
|
765
|
+
})
|
|
766
|
+
↓ 透传密文
|
|
767
|
+
→ event/group.message_created
|
|
768
|
+
7. 从信封读取 group_id, message_id
|
|
769
|
+
msg_key = HKDF(group_secret,
|
|
770
|
+
info="aun-group:g-abc123.agentid.pub:msg:gm-xxx")
|
|
771
|
+
plaintext = AES-GCM.decrypt(...)
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### 13.2 踢人触发 Epoch 轮换
|
|
775
|
+
|
|
776
|
+
```
|
|
777
|
+
Alice (owner) Group Service Bob Carol
|
|
778
|
+
────────────────────────────────────────────────────────────────────────────
|
|
779
|
+
1. group.kick({group_id, aid: "bob"})
|
|
780
|
+
← success
|
|
781
|
+
|
|
782
|
+
2. 生成新 group_secret (epoch=2)
|
|
783
|
+
计算新 commitment(不含 Bob)
|
|
784
|
+
|
|
785
|
+
3. P2P E2EE → Carol:
|
|
786
|
+
{type: "e2ee.group_key_distribution",
|
|
787
|
+
epoch: 2, group_secret, ...}
|
|
788
|
+
4. 验证 commitment
|
|
789
|
+
更新本地 epoch=2
|
|
790
|
+
|
|
791
|
+
× Bob 不会收到新 epoch 密钥
|
|
792
|
+
× Bob 的旧 group_secret (epoch=1) 无法解密新消息
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
### 13.3 密钥恢复
|
|
796
|
+
|
|
797
|
+
```
|
|
798
|
+
Carol (成员) Alice (成员)
|
|
799
|
+
────────────────────────────────────────────────────────────────────
|
|
800
|
+
1. 收到 epoch=3 的群消息
|
|
801
|
+
本地只有 epoch=2 的 group_secret
|
|
802
|
+
解密失败
|
|
803
|
+
|
|
804
|
+
2. P2P E2EE → Alice:
|
|
805
|
+
{type: "e2ee.group_key_request",
|
|
806
|
+
group_id, epoch: 3}
|
|
807
|
+
|
|
808
|
+
3. 验证 Carol 是群成员
|
|
809
|
+
P2P E2EE → Carol:
|
|
810
|
+
{type: "e2ee.group_key_response",
|
|
811
|
+
epoch: 3, group_secret, commitment,
|
|
812
|
+
member_aids}
|
|
813
|
+
|
|
814
|
+
4. 验证 commitment
|
|
815
|
+
存储 group_secret (epoch=3)
|
|
816
|
+
重试解密群消息 → 成功
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
## 14. SDK 接口建议
|
|
822
|
+
|
|
823
|
+
### 14.1 加密
|
|
824
|
+
|
|
825
|
+
```python
|
|
826
|
+
# 通过 AUNClient 自动处理
|
|
827
|
+
await client.call("group.send", {
|
|
828
|
+
"group_id": "g-abc123.agentid.pub",
|
|
829
|
+
"payload": {"type": "text", "text": "秘密消息"},
|
|
830
|
+
"encrypt": True,
|
|
831
|
+
})
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
SDK 在发送前 **MUST** 检查本地是否持有该群的 group_secret:
|
|
835
|
+
- 有 → 使用 §4 的流程加密 payload
|
|
836
|
+
- 无 → 抛出 `E2EEError`,提示缺少群组密钥
|
|
837
|
+
|
|
838
|
+
### 14.2 解密
|
|
839
|
+
|
|
840
|
+
SDK 收到 `event/group.message_created` 且 `payload.type == "e2ee.group_encrypted"` 时自动解密:
|
|
841
|
+
- 从信封读取 `group_id`、`message_id`
|
|
842
|
+
- 查本地 group_secret(匹配 epoch)
|
|
843
|
+
- 执行 HKDF 派生 + AES-GCM 解密
|
|
844
|
+
- 解密失败时 **MAY** 触发 epoch_key_request
|
|
845
|
+
|
|
846
|
+
### 14.3 配置项
|
|
847
|
+
|
|
848
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
849
|
+
|--------|------|--------|------|
|
|
850
|
+
| `group_e2ee` | bool | `true` | 群组 E2EE 能力声明(必选能力,始终为 true,非用户开关) |
|
|
851
|
+
| `epoch_auto_rotate_interval` | int | `0` | 自动轮换间隔(秒),0 表示禁用 |
|
|
852
|
+
| `old_epoch_retention_seconds` | int | `604800` | 旧 epoch 密钥保留时间(默认 7 天) |
|
|
853
|
+
|
|
854
|
+
---
|
|
855
|
+
|
|
856
|
+
## 15. 错误码
|
|
857
|
+
|
|
858
|
+
| 错误码 | 名称 | 说明 |
|
|
859
|
+
|--------|------|------|
|
|
860
|
+
| -32040 | `E2EE_GROUP_SECRET_MISSING` | 缺少该群的 group_secret |
|
|
861
|
+
| -32041 | `E2EE_GROUP_EPOCH_MISMATCH` | 消息 epoch 与本地不匹配 |
|
|
862
|
+
| -32042 | `E2EE_GROUP_COMMITMENT_INVALID` | Membership Commitment 验证失败 |
|
|
863
|
+
| -32043 | `E2EE_GROUP_NOT_MEMBER` | 密钥请求者不是群成员 |
|
|
864
|
+
| -32044 | `E2EE_GROUP_DECRYPT_FAILED` | 群消息解密失败 |
|
|
865
|
+
|
|
866
|
+
---
|
|
867
|
+
|
|
868
|
+
## 16. 未来扩展方向
|
|
869
|
+
|
|
870
|
+
以下功能不在本规范范围内,但设计时已预留扩展空间:
|
|
871
|
+
|
|
872
|
+
### 16.1 Sender Keys 叠加(可选增强)
|
|
873
|
+
|
|
874
|
+
如需 epoch 内前向安全,可在 Epoch Group Key 基础上叠加 Sender Keys hash chain:
|
|
875
|
+
|
|
876
|
+
```
|
|
877
|
+
sender_chain_key[0] = HKDF(group_secret, info=f"sender:{sender_aid}:epoch:{epoch}")
|
|
878
|
+
sender_chain_key[i+1] = SHA-256(sender_chain_key[i])
|
|
879
|
+
msg_key[i] = HKDF(sender_chain_key[i], info="msg")
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
此方案从 group_secret 确定性派生,无需额外分发(O(0) 网络开销),但需要维护每个 sender 的 chain_index 状态。详见未来的 `08-AUN-E2EE-Group-SenderKeys.md`。
|
|
883
|
+
|
|
884
|
+
### 16.2 大群优化
|
|
885
|
+
|
|
886
|
+
当群规模超过 500 人时,epoch 轮换的 O(n) P2P 分发可能成为瓶颈。可考虑引入树状分发或分层密钥管理。
|
|
887
|
+
|
|
888
|
+
---
|
|
889
|
+
|
|
890
|
+
## 17. 变更记录
|
|
891
|
+
|
|
892
|
+
| 版本 | 日期 | 变更 |
|
|
893
|
+
|------|------|------|
|
|
894
|
+
| 1.0-draft-r5 | 2026-04 | 成员加入改为 MUST 轮换 epoch;服务端以 `min_read_epoch` 约束新成员加入前历史访问;删除加入轮换配置开关 |
|
|
895
|
+
| 1.0-draft-r4 | 2026-04 | 新增 Epoch CAS 轮换 RPC 的 rotation_signature 要求(§5.2.1);新增客户端操作签名要求(§11.3);补充密钥恢复异步语义(§8.5);补充外层与 AAD 绑定校验说明;升级防服务端注入安全属性 |
|
|
896
|
+
| 1.0-draft-r3 | 2026-04 | Membership Commitment 绑定 group_secret 哈希(§6.2);新增 Membership Manifest 签名机制(§6A);群密文消息新增发送方签名(§7.1);更新本地状态模型含 old_epochs(§9.2);服务端角色明确 epoch CAS 协调(§1.2);分发消息增加 manifest 字段(§5.2, §5.3) |
|
|
897
|
+
| 1.0-draft-r2 | 2026-04 | group_e2ee 默认值改为 true;补充成员加入轮换策略;group.leave 明确离开者不执行轮换 |
|
|
898
|
+
| 1.0-draft-r1 | 2026-04 | 修正:Membership Commitment 去掉 "Signed" 命名,明确为哈希一致性检测而非签名防伪;修正 message_id 来源为客户端生成;新增外层路由字段与 AAD 绑定校验要求 |
|
|
899
|
+
| 1.0-draft | 2026-04 | 初始版本:Epoch Group Key 机制;Membership Commitment;密钥恢复协议 |
|
|
900
|
+
|