@agentunion/fastaun-browser 0.5.0 → 0.5.2

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.
Files changed (140) hide show
  1. package/CHANGELOG.md +550 -464
  2. package/_packed_docs/CHANGELOG-validators.md +147 -0
  3. package/_packed_docs/CHANGELOG.md +550 -464
  4. package/_packed_docs/INDEX.md +201 -177
  5. package/_packed_docs/KITE_DOCS_GUIDE.md +38 -32
  6. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +169 -116
  7. package/_packed_docs/aun-perf-audit-critical-bugs.md +315 -0
  8. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +263 -260
  9. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
  10. package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +2 -2
  11. 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 +1 -1
  12. 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 +39 -16
  13. package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +8 -5
  14. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +18 -19
  15. 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 +1 -1
  16. package/_packed_docs/protocol/08-AUN-E2EE-Group.md +139 -746
  17. package/_packed_docs/protocol/08-AUN-E2EE.md +12 -10
  18. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +117 -171
  19. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +6 -0
  20. 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 +1 -1
  21. package/_packed_docs/protocol/16-/347/263/273/347/273/237/347/233/256/345/275/225/344/277/235/346/212/244/346/226/271/346/241/210.md +177 -0
  22. package/_packed_docs/protocol/README.md +5 -4
  23. package/_packed_docs/protocol/aun-docs-guide.md +2 -2
  24. package/_packed_docs/protocol/index.md +12 -7
  25. 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 +1 -1
  26. package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +13 -13
  27. package/_packed_docs/protocol//351/231/204/345/275/225L-E2EE/345/256/236/347/216/260/346/214/207/345/215/227.md +9 -9
  28. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +15 -13
  29. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +26 -18
  30. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +35 -284
  31. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +473 -442
  32. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +11 -7
  33. package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
  34. package/_packed_docs/sdk/09-group-rpc-manual.md +367 -433
  35. package/_packed_docs/sdk/09-message-rpc-manual.md +50 -28
  36. package/_packed_docs/sdk/09-payload-reference.md +3 -3
  37. package/_packed_docs/sdk/09-storage-rpc-manual.md +57 -20
  38. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +18 -17
  39. package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +3 -2
  40. package/_packed_docs/sdk/INDEX.md +25 -24
  41. package/_packed_docs/sdk/Notify/351/200/232/347/237/245/346/226/271/346/241/210.md +6 -2
  42. package/dist/agent-md.d.ts.map +1 -1
  43. package/dist/agent-md.js +18 -9
  44. package/dist/agent-md.js.map +1 -1
  45. package/dist/bundle.js +1661 -1180
  46. package/dist/client/delivery.d.ts +13 -2
  47. package/dist/client/delivery.d.ts.map +1 -1
  48. package/dist/client/delivery.js +251 -46
  49. package/dist/client/delivery.js.map +1 -1
  50. package/dist/client/group-state.d.ts.map +1 -1
  51. package/dist/client/group-state.js +36 -14
  52. package/dist/client/group-state.js.map +1 -1
  53. package/dist/client/lifecycle.js +2 -2
  54. package/dist/client/lifecycle.js.map +1 -1
  55. package/dist/client/rpc-pipeline.d.ts +1 -0
  56. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  57. package/dist/client/rpc-pipeline.js +166 -50
  58. package/dist/client/rpc-pipeline.js.map +1 -1
  59. package/dist/client/v2-e2ee.d.ts +14 -1
  60. package/dist/client/v2-e2ee.d.ts.map +1 -1
  61. package/dist/client/v2-e2ee.js +376 -126
  62. package/dist/client/v2-e2ee.js.map +1 -1
  63. package/dist/client.d.ts +5 -4
  64. package/dist/client.d.ts.map +1 -1
  65. package/dist/client.js +187 -46
  66. package/dist/client.js.map +1 -1
  67. package/dist/collab/client.d.ts +8 -0
  68. package/dist/collab/client.d.ts.map +1 -1
  69. package/dist/collab/client.js +12 -0
  70. package/dist/collab/client.js.map +1 -1
  71. package/dist/errors.d.ts +0 -20
  72. package/dist/errors.d.ts.map +1 -1
  73. package/dist/errors.js +8 -51
  74. package/dist/errors.js.map +1 -1
  75. package/dist/facades.d.ts +9 -4
  76. package/dist/facades.d.ts.map +1 -1
  77. package/dist/facades.js +192 -31
  78. package/dist/facades.js.map +1 -1
  79. package/dist/group-fs.d.ts +17 -0
  80. package/dist/group-fs.d.ts.map +1 -1
  81. package/dist/group-fs.js +54 -11
  82. package/dist/group-fs.js.map +1 -1
  83. package/dist/group-id.d.ts +9 -12
  84. package/dist/group-id.d.ts.map +1 -1
  85. package/dist/group-id.js +41 -63
  86. package/dist/group-id.js.map +1 -1
  87. package/dist/index.d.ts +4 -2
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +4 -1
  90. package/dist/index.js.map +1 -1
  91. package/dist/keystore/index.d.ts +2 -54
  92. package/dist/keystore/index.d.ts.map +1 -1
  93. package/dist/keystore/indexeddb-identity-store.d.ts +3 -0
  94. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  95. package/dist/keystore/indexeddb-identity-store.js +65 -0
  96. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  97. package/dist/keystore/indexeddb-shared.d.ts +3 -17
  98. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  99. package/dist/keystore/indexeddb-shared.js +4 -47
  100. package/dist/keystore/indexeddb-shared.js.map +1 -1
  101. package/dist/keystore/indexeddb-token-store.d.ts +1 -64
  102. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  103. package/dist/keystore/indexeddb-token-store.js +45 -774
  104. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  105. package/dist/logger.d.ts +2 -0
  106. package/dist/logger.d.ts.map +1 -1
  107. package/dist/logger.js +4 -0
  108. package/dist/logger.js.map +1 -1
  109. package/dist/storage/lowlevel.d.ts +9 -1
  110. package/dist/storage/lowlevel.d.ts.map +1 -1
  111. package/dist/storage/lowlevel.js +12 -1
  112. package/dist/storage/lowlevel.js.map +1 -1
  113. package/dist/storage/vfs.d.ts +22 -0
  114. package/dist/storage/vfs.d.ts.map +1 -1
  115. package/dist/storage/vfs.js +54 -0
  116. package/dist/storage/vfs.js.map +1 -1
  117. package/dist/tools/cross-sdk-agent.js +336 -49
  118. package/dist/tools/cross-sdk-agent.js.map +1 -1
  119. package/dist/transport.d.ts +2 -0
  120. package/dist/transport.d.ts.map +1 -1
  121. package/dist/transport.js +96 -3
  122. package/dist/transport.js.map +1 -1
  123. package/dist/types.d.ts +39 -56
  124. package/dist/types.d.ts.map +1 -1
  125. package/dist/v2/session/session.d.ts +2 -0
  126. package/dist/v2/session/session.d.ts.map +1 -1
  127. package/dist/v2/session/session.js +58 -22
  128. package/dist/v2/session/session.js.map +1 -1
  129. package/dist/v2/state/commitment.d.ts +1 -1
  130. package/dist/v2/state/commitment.d.ts.map +1 -1
  131. package/dist/v2/state/commitment.js +5 -3
  132. package/dist/v2/state/commitment.js.map +1 -1
  133. package/dist/validators.d.ts +35 -0
  134. package/dist/validators.d.ts.map +1 -0
  135. package/dist/validators.js +127 -0
  136. package/dist/validators.js.map +1 -0
  137. package/dist/version.d.ts +1 -1
  138. package/dist/version.js +1 -1
  139. package/package.json +1 -1
  140. package/_packed_docs/collab-gateway-boundary-test-report.md +0 -164
@@ -1,70 +1,50 @@
1
1
  # AUN-E2EE 群组扩展规范
2
2
 
3
- > 版本:1.0-draft
3
+ > 版本:2.0-draft
4
4
  > 状态:规范性文档
5
5
  > 适用范围:AUN 客户端 SDK、客户端应用、跨语言实现
6
6
  > 不适用范围:Group Service 服务端加解密实现
7
- > 前置依赖:[08-AUN-E2EE](./08-AUN-E2EE.md)(P2P E2EE 规范)、[10-Group-子协议](./10-Group-子协议.md)
8
- > 定位:**群组消息端到端加密层**,基于 Epoch Group Key 机制
7
+ > 前置依赖:当前 P2P V2 链路见 [E2EE_V2消息通信时序图](../sdk/E2EE_V2消息通信时序图.md) 和 [05-E2EE加密通信](../sdk/05-E2EE加密通信.md);旧 P2P 信封见 [08-AUN-E2EE](./08-AUN-E2EE.md);群组基础语义见 [10-Group-子协议](./10-Group-子协议.md)
8
+ > 定位:**群组消息端到端加密层**,基于 per-message 密钥 + per-recipient 包裹机制
9
9
 
10
10
  ---
11
11
 
12
12
  ## 1. 目标与边界
13
13
 
14
- 本规范定义 AUN 群组成员之间的端到端消息加密协议。
14
+ 本规范定义 AUN 群组成员之间的端到端消息加密协议(V2,当前唯一在用版本)。
15
15
 
16
16
  ### 1.1 目标
17
17
 
18
- - 让群组内的 N 个成员在现有 `group.send` / `event/group.message_created` 之上实现端到端加密
18
+ - 让群组内的 N 个成员在现有 `group.send` / `group.v2.send` / `event/group.message_created` 之上实现端到端加密
19
19
  - 让 Group Service 仅看到最小必要路由元数据和密文 payload
20
- - 保持与 P2P E2EE 一致的**无状态设计**——每条群消息独立可解密,不依赖任何历史状态
21
- - 为各语言 SDK 提供统一的群组密文格式、密钥分发协议和恢复机制
20
+ - 每条消息使用独立密钥,接收方按设备分别持有密钥包裹(wrap),不依赖群级共享对称密钥
21
+ - 为各语言 SDK 提供统一的群组密文格式、成员状态签名机制和恢复语义
22
22
 
23
23
  ### 1.2 服务端职责
24
24
 
25
25
  Group Service **只做**:
26
26
 
27
- - 认证发送方(JWT token
27
+ - 认证发送方(JWT token / AID 证书)
28
28
  - 校验群成员权限
29
- - 透传 `encrypted: true` 的 payload
30
- - 存储和广播密文 payload
31
- - **Epoch 版本协调**:提供 `group.e2ee.get_epoch` 和 `group.e2ee.rotate_epoch` RPC,作为 epoch 版本的 CAS(Compare-And-Swap)同步点,确保并发轮换不冲突
29
+ - 存储共享密文体(`v2_group_messages`)和逐设备密钥包裹(`v2_group_wraps`)
30
+ - 校验消息结构完整性(recipients 排序、digest、必要审计包裹等)
31
+ - 维护群成员状态的签名版本链(`state_version`/`state_chain`),提供分叉检测的比对基准
32
+ - 广播加密消息通知,按设备分发对应的密钥包裹
32
33
 
33
34
  Group Service **绝不做**:
34
35
 
35
36
  - 加解密群消息
36
- - 持有或管理 group_secret 明文
37
+ - 持有或管理消息密钥、成员密钥明文
37
38
  - 参与密钥协商或密钥派生
38
39
 
39
40
  ### 1.3 设计原则
40
41
 
41
42
  | 原则 | 说明 |
42
43
  |------|------|
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 生命周期**(定时轮换)来弥补。
44
+ | **消息级密钥** | 每条消息使用独立随机密钥,发送方一次性会话密钥用完即弃,不维护群级长期对称密钥 |
45
+ | **复用 V2 设备密钥体系** | 接收方设备密钥包裹基于当前 E2EE V2 IK/SPK 机制;群内优先使用 `group_device_prekey`,缺少群内设备 Prekey 时可回退到该设备已注册的 P2P Prekey |
46
+ | **不信任服务端** | 消息密钥从未经过服务端明文;接收方按设备分别持有的密钥包裹只有对应设备的私钥能解开 |
47
+ | **成员状态可验证** | 群成员集合与权限的变更通过签名的状态版本链(state_version/state_chain)記录,接收方可据此检测状态分叉或篡改 |
68
48
 
69
49
  ---
70
50
 
@@ -72,11 +52,11 @@ AUN Group E2EE 选择 **Epoch Group Key**——每条消息从 group_secret + me
72
52
 
73
53
  AUN-E2EE-Group 建立在以下核心能力之上:
74
54
 
75
- - **P2P E2EE**(08-AUN-E2EE):group_secret 分发通道
76
- - **Group 子协议**(10-Group-子协议):群组管理、成员管理、消息传输
55
+ - **P2P E2EE V2**:设备 Prekey(IK/SPK)注册与管理机制、recipient wrap、AAD 序列化规则(Canonical JSON)均与 SDK V2 多设备 wrap 保持一致
56
+ - **Group 子协议**([10-Group-子协议](./10-Group-子协议.md)):群组管理、成员管理、消息传输
77
57
  - **AID + 证书链身份体系**:成员身份验证
78
58
 
79
- 群组密文消息通过 `group.send` 承载;Group Service 无需识别 payload 内部字段。
59
+ 群组密文消息通过 `group.v2.send` 提交,`group.send` 承载明文/兼容路径;Group Service 无需识别密文 payload 内部字段。
80
60
 
81
61
  ---
82
62
 
@@ -84,482 +64,146 @@ AUN-E2EE-Group 建立在以下核心能力之上:
84
64
 
85
65
  ### 3.1 Epoch
86
66
 
87
- 群组密钥的版本号。每次需要轮换 group_secret 时,epoch 递增。epoch 1 开始。
67
+ 群密钥版本号(uint32,从 1 开始)。用于在消息 AAD 中标记消息所属的密钥轮换周期,服务端据此拒绝使用过期版本发送的消息,与下方 State Version 是两条独立轨道。
88
68
 
89
- ### 3.2 Group Secret
69
+ ### 3.2 State Version / State Chain
90
70
 
91
- 群组对称密钥。256 位随机字节,用于派生每条群消息的加密密钥。每个 epoch 对应一个独立的 group_secret。
71
+ 群成员状态(成员列表、管理员集合、审计接收者等)的签名版本号(uint32,从 0 开始,未签名任何状态时为 0)。每次 owner/admin 提交并确认一次成员状态变更,版本递增。`state_chain` 是历次已确认状态的链式记录,客户端可据此检测服务端是否篡改历史或出现并发分叉。
92
72
 
93
- ### 3.3 Epoch Key Distribution
73
+ ### 3.3 State Commitment
94
74
 
95
- 通过 P2P E2EE 通道向群组成员分发 group_secret 的过程。
75
+ 对当前群成员集合、管理员集合、审计接收者等状态字段的摘要值,绑定到 `epoch`,写入每条加密消息的 AAD 中,使消息与发送时刻的群状态可验证关联。
96
76
 
97
- ### 3.4 Membership Commitment
77
+ ### 3.4 Recipients Digest
98
78
 
99
- 群成员列表的 SHA-256 摘要,用于防止服务端篡改成员列表。
79
+ 一条群消息的所有设备密钥包裹(recipient 行)经排序后计算得到的摘要根值,纳入发送方签名覆盖范围,用于防止服务端在按设备拆分投递时篡改或替换某个接收方的密钥包裹。
100
80
 
101
81
  ### 3.5 密文群消息
102
82
 
103
- 通过 `group.send` 传输的加密群消息,`encrypted` **MUST** 为 `true`,`payload.type` **MUST** 为 `e2ee.group_encrypted`。
83
+ 通过 `group.v2.send` 传输的加密群消息,`payload.type` **MUST** 为 `e2ee.group_encrypted`。
104
84
 
105
85
  ---
106
86
 
107
- ## 4. 算法套件
108
-
109
- 与 P2P E2EE 保持一致:
87
+ ## 4. 密钥体系概述
110
88
 
111
- - **MUST** 支持 `P256_HKDF_SHA256_AES_256_GCM`
112
- - **MAY** 支持其他套件
89
+ ### 4.1 密钥层次
113
90
 
114
- ### 4.1 密钥派生
91
+ | 密钥 | 生成方 | 生命周期 | 说明 |
92
+ |------|--------|---------|------|
93
+ | 消息密钥(对称) | 发送方,每条消息独立生成 | 单条消息 | 用于加密消息正文,从不直接传输,按接收方设备分别用密钥包裹机制加密后随消息一起发送 |
94
+ | 发送方一次性会话密钥 | 发送方,每条消息独立生成 | 单条消息 | 用于与接收方设备的长期身份/Prekey 做密钥协商,加密后即可丢弃 |
95
+ | 接收方设备身份密钥(IK) | 接收方,AID 级别 | 长期 | 与 AID 身份证书绑定 |
96
+ | 接收方设备 Prekey(SPK) | 接收方,设备级别 | 定期轮换 | 群内设备 Prekey(`group_device_prekey`)通过 `group.v2.put_group_pk` 注册;缺失时回退到该设备已注册的 P2P Prekey,此时安全强度降级为仅身份密钥参与协商 |
115
97
 
116
- 每条群消息的加密密钥从 group_secret 独立派生:
98
+ 具体的密钥协商组合方式(是否使用 SPK、密钥派生的哈希函数与输入构成)与当前 P2P V2 recipient wrap 机制一致,本规范不重复列出精确公式,实现请参照 SDK V2 时序文档及各语言 SDK 的密码学模块源码。
117
99
 
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
- ```
100
+ ### 4.2 每条消息的密钥包裹
126
101
 
127
- - `group_id`:群组唯一标识
128
- - `message_id`:消息 UUID,由**发送方客户端**在加密前生成(格式 `gm-{uuid}`),写入 AAD 并参与密钥派生。注意:Group Service 会在外层消息记录中填充自己的 `message_id`,该值与 AAD 中的 `message_id` 可能不同。解密时 **MUST** 使用 AAD 中的 `message_id`。
102
+ 一条加密群消息只加密一次消息正文,但会为每个当前应接收该消息的设备分别生成一份密钥包裹(recipient wrap),使得只有对应设备的私钥能解出消息密钥。密钥包裹按角色分类:
129
103
 
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` 一致,不一致时拒绝解密。
104
+ - `member`:普通群成员设备
105
+ - `self_sync`:发送方自己在其他设备上的同步副本
106
+ - `audit`:群配置的审计接收者(如有),用于合规场景下的旁路可见性,不改变成员关系
143
107
 
144
108
  ---
145
109
 
146
- ## 5. Epoch 生命周期
110
+ ## 5. 消息生命周期
147
111
 
148
- ### 5.1 Epoch 轮换触发条件
112
+ ### 5.1 Bootstrap(发送前的成员/密钥快照)
149
113
 
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`) | 不适用 | 群组不再存在 |
114
+ 发送方在加密消息前调用 `group.v2.bootstrap` 获取:
158
115
 
159
- ### 5.2 Epoch 轮换流程
116
+ - 当前所有(committed)成员的设备身份/Prekey 公钥列表
117
+ - 当前 `epoch`
118
+ - 当前 `state_version` / `state_hash` / `state_chain`
119
+ - `committed_member_aids`:已通过状态签名确认的成员
120
+ - `pending_adds` / `pending_removes`:尚未签名确认的成员变更
121
+ - `e2ee_security_level`:`end_to_end`(正常受控群)或 `transport`(open/邀请码等任何人可自由加入的群,此时密钥包裹接收方集合不与"被授权成员"强绑定,安全强度退化为等价于传输层加密,不构成严格端到端)
160
122
 
161
- ```
162
- 1. 触发者(admin/owner)生成新的 group_secret:
163
- group_secret = random(32 bytes)
164
- epoch += 1
123
+ 对于设置了成员审批(private/approval/closed)的群,尚未完成状态签名确认的新成员(pending)不会出现在 bootstrap 返回的设备列表中,发送方因而不会为其生成密钥包裹,实现了"未确认成员收不到密文"的天然隔离。SDK 通常对 bootstrap 结果做短期缓存,成员或状态变更后需要刷新。
165
124
 
166
- 2. 计算 Membership Commitment(§6)
125
+ ### 5.2 加密与发送
167
126
 
168
- 3. 构建并签名 Membership Manifest(§6A)
127
+ 1. 发送方生成本条消息的消息密钥与一次性会话密钥
128
+ 2. 构造 AAD(含 `group_id`、`epoch`、`message_id`、发送方身份、`state_commitment` 等,详见 §7)
129
+ 3. 用消息密钥对正文做认证加密,AAD 参与认证但不加密
130
+ 4. 为 bootstrap 返回的每个目标设备生成密钥包裹(消息密钥被目标设备的密钥协商结果加密)
131
+ 5. 对所有密钥包裹排序后计算 Recipients Digest,随同密文一起纳入发送方签名
132
+ 6. 调用 `group.v2.send` 提交完整信封(含密文、AAD、Recipients Digest、发送方签名、所有密钥包裹)
169
133
 
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
- })
134
+ 服务端收到后校验成员资格、`epoch` 是否为当前值、密钥包裹排序与 Recipients Digest 是否自洽、必要审计包裹是否齐全,通过后落库并按设备拆分推送。
183
135
 
184
- 4. 本地持久化新的 group_secret 和 epoch
185
- 5. 安全擦除旧的 group_secret(MAY 保留至旧 epoch 消息超时)
186
- ```
136
+ ### 5.3 拉取与解密
187
137
 
188
- ### 5.2.1 Epoch CAS 轮换 RPC
138
+ 接收方通过 `group.v2.pull` 按设备拉取属于自己的消息(服务端只返回该设备自己的那一份密钥包裹,可附带 Merkle 证明用于验证该包裹确实是发送方原始签名集合的一部分而未被服务端替换)。接收方:
189
139
 
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 分钟新鲜度窗口 |
140
+ 1. 验证发送方签名(覆盖密文、AAD、Recipients Digest)
141
+ 2. 用本机私钥解出分配给自己的消息密钥
142
+ 3. 用消息密钥解密正文并校验 AAD 一致性
200
143
 
201
- **签名输入**:`"{group_id}|{current_epoch}|{new_epoch}|{aid}|{rotation_timestamp}"`
144
+ 解密失败的常见原因:使用了已轮换的旧 Prekey、bootstrap 快照过期导致密钥包裹面向错误的设备、或密文/AAD 被篡改。恢复方式通常是刷新 bootstrap 后等待下一条消息,或依赖服务端 `group.v2.pull` 的历史消息重新拉取。
202
145
 
203
- 服务端 **MUST** 验证签名有效性、时间戳新鲜度,并拒绝重复签名。
146
+ ### 5.4 拉取确认
204
147
 
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。
148
+ 接收方通过 `group.v2.ack` 提交已消费到的消息序号游标,用于服务端做 retention 与增量拉取范围计算。
246
149
 
247
150
  ---
248
151
 
249
- ## 6. Membership Commitment
152
+ ## 6. 成员状态签名(State Version)
250
153
 
251
154
  ### 6.1 目的
252
155
 
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 不变
156
+ 群成员集合、管理员集合等状态信息如果只由服务端记录,无法防止服务端篡改或伪造。V2 引入 owner/admin 对状态变更进行签名确认的两阶段流程,使状态变更可追溯、可验证、且能检测并发冲突。
269
157
 
270
- ### 6.3 验证流程
158
+ ### 6.2 两阶段流程
271
159
 
272
- 接收方收到 `e2ee.group_key_distribution` 消息后 **MUST**:
160
+ 1. **提案**(`group.v2.propose_state`):owner/admin 提交目标 `state_version`、状态摘要(`state_hash`)、成员快照,并附上覆盖这些字段的签名。首次状态签名(`state_version = 1`)**MUST** 由 owner 发起,此后 owner/admin 均可发起。服务端记录为待确认提案,设有自动确认超时。
161
+ 2. **确认**(`group.v2.confirm_state`):发起者(或后续管理员)提交提案 ID 完成确认,服务端以比较并交换(CAS)方式原子推进 `state_version`,若确认前已有其他提案抢先生效则确认失败,需要基于最新状态重新发起。
273
162
 
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 获取服务端成员列表进行比对(如果可用)
163
+ 该机制保证并发的状态变更提案中至多一个能生效,避免服务端或多个管理员产生分叉的成员视图。
278
164
 
279
- ### 6.4 防护效果
165
+ ### 6.3 分叉检测
280
166
 
281
- | 攻击 | 防护级别 | 说明 |
282
- |------|:-------:|------|
283
- | 成员列表不一致 | ✅ 检测 | 所有人收到同一个 commitment,可互相比对 |
284
- | 幽灵成员注入(分发者被骗) | ⚠️ 辅助检测 | 合法成员看到完整列表后有机会发现异常 AID,但需要额外的可信成员列表源进行比对 |
285
- | 幽灵成员注入(分发者串通) | ❌ 无法防御 | commitment 由分发者构造,串通场景下哈希无意义 |
286
- | epoch 轮换阻断 | ⚠️ 需检测 | 需要客户端检测:成员变更事件后应在合理时间内收到新 epoch |
287
- | 选择性消息丢弃 | ❌ 无法防御 | 加密层无法解决,需消息确认/回执机制 |
167
+ 客户端在 bootstrap 时获得当前 `state_chain`,与本地已知的历史链做连续性比对;若发现不连续或矛盾,视为潜在的服务端篡改或并发冲突信号,应用层应提示用户或拒绝基于该状态发送敏感消息。
288
168
 
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"`)
169
+ ### 6.4 Pending 成员语义
350
170
 
351
- > **注意**:Manifest 验证是**建议性的**(SHOULD),不是强制性的。这是因为在某些场景下(如跨域成员加入),接收方可能尚未缓存发起者证书。实现 **MAY** 在验签失败时仍接受 distribution,但 **MUST** 在后台尝试获取发起者证书并进行延迟验证。
171
+ 新加入但尚未被状态签名确认的成员(`pending_adds`)在成员审批类群组(private/approval/closed)中不会出现在 bootstrap 的设备列表里,因而无法接收此前及此后(直到状态确认)发出的加密消息;在开放加入类群组(open/邀请码)中不做此限制,此时群组的 `e2ee_security_level` 会被标记为 `transport`(见 §5.1),提示这类群组的加密强度弱于严格端到端。
352
172
 
353
173
  ---
354
174
 
355
175
  ## 7. 消息格式与 AAD
356
176
 
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 可见的明文,用于路由和存储:
177
+ ### 7.1 AAD 关键字段
421
178
 
422
179
  | 字段 | 说明 |
423
180
  |------|------|
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 优先从本地成员列表选择,零状态时退化为向当前消息发送者请求):
181
+ | `group_id` | 群组唯一标识 |
182
+ | `from` / `from_device` | 发送方 AID 与设备标识 |
183
+ | `message_id` | 消息唯一标识,发送方生成,参与认证 |
184
+ | `epoch` | 消息所属的密钥版本号 |
185
+ | `state_commitment` | 绑定发送时刻的成员状态摘要(`state_version`/`state_hash`/`state_chain`) |
447
186
 
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
- ```
187
+ AAD 采用 Canonical JSON 序列化(递归键排序、UTF-8 直出、紧凑格式),规则与当前 SDK V2 recipient wrap 实现保持一致。
456
188
 
457
- 此消息 **MUST** 通过 P2P E2EE 通道发送。
189
+ ### 7.2 外层信封
458
190
 
459
- ### 8.3 Epoch Key Response
191
+ 密文 payload 通过 `group.v2.send` `envelope` 参数提交;接收方通过 `group.v2.pull` 按设备取回,返回内容包含该设备自己的密钥包裹与(如适用)该包裹在原始签名集合中的完整性证明。
460
192
 
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
- 密钥恢复是**异步过程**:
193
+ ---
485
194
 
486
- 1. 成员收到无法解密的群消息(epoch 不匹配或无密钥)
487
- 2. SDK 自动向候选成员发送 `e2ee.group_key_request`(优先本地已知成员,零状态时向消息发送者请求)
488
- 3. 在线成员验证请求者身份后回复 `e2ee.group_key_response`
489
- 4. 请求者收到响应后存储 group_secret,后续 pull 或再次收到消息时才能成功解密
195
+ ## 8. 密钥恢复与历史访问
490
196
 
491
- > `group.use_invite_code` 加入群组后不保证立即拥有 group_secret。SDK 会在后续群消息解密失败时自动发起恢复请求。
197
+ - 接收方若因设备 Prekey 轮换、bootstrap 快照过期等原因暂时无法解密某条消息,通常的恢复路径是刷新 bootstrap 快照后重试,而非向对端请求补发密钥(V2 不存在需要跨端口头传递的群级共享密钥)。
198
+ - 新成员加入前发出的历史消息,其密钥包裹集合本就不包含新成员设备,因此新成员天然无法解密加入前的历史消息,无需额外的"历史隔离"机制。
492
199
 
493
200
  ---
494
201
 
495
202
  ## 9. 客户端密钥存储
496
203
 
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** 用于加密新消息
204
+ - 发送方一次性会话密钥用后即弃,不持久化。
205
+ - 接收方的设备身份密钥(IK)与设备 Prekey(SPK)私钥由本地 V2 key store 保存,并按平台能力使用本地密钥保护;明文私钥不得落入服务端。
206
+ - 消息密钥本身不需要持久化——每条消息独立生成、独立解出、用后即弃;除非应用层自行缓存明文消息以支持离线查看。
563
207
 
564
208
  ---
565
209
 
@@ -567,25 +211,17 @@ group_secret **MUST** 持久化到本地存储。推荐存储在 FileKeyStore
567
211
 
568
212
  ### 10.1 防篡改
569
213
 
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 → 解密失败
214
+ AAD 覆盖所有路由关键字段(`group_id`、`from`、`epoch`、`message_id`、`state_commitment`),任何篡改导致认证加密校验失败而拒绝解密;发送方签名进一步覆盖密文、AAD Recipients Digest,防止服务端在拆分投递时替换或增删某个接收方的密钥包裹。
576
215
 
577
216
  ### 10.2 防重放
578
217
 
579
- 群组消息的防重放与 P2P 一致:
218
+ - 接收方 **MUST** 维护本地去重集合,以 `{group_id}:{from}:{message_id}` 为 key 拒绝重复消息。
219
+ - `group.v2.send` 的时间戳需在合理新鲜度窗口内,超出窗口的请求会被服务端拒绝。
580
220
 
581
- - 接收方 **MUST** 维护本地 `seen_messages` 集合
582
- - 以 `{group_id}:{sender_aid}:{message_id}` 为 key 去重
583
- - 同一 key 的消息 **MUST** 被拒绝
221
+ ### 10.3 Epoch/State 校验
584
222
 
585
- ### 10.3 Epoch 降级防护
586
-
587
- - 接收方 **MUST** 拒绝 epoch 低于本地已知最新 epoch 的加密消息
588
- - 例外:如果实现保留了旧 epoch 密钥(§9.4),**MAY** 允许解密旧 epoch 消息。实现可选择在解密结果中标记 `historical: true`,但不做强制要求
223
+ - 服务端拒绝 `epoch` 与当前群密钥版本不一致的发送请求,客户端遇到此类拒绝应刷新 bootstrap 后重试。
224
+ - 接收方应校验消息 AAD 中的 `state_commitment` 与本地已知状态链一致,不一致时视为潜在分叉信号。
589
225
 
590
226
  ---
591
227
 
@@ -594,91 +230,25 @@ group_secret **MUST** 持久化到本地存储。推荐存储在 FileKeyStore
594
230
  ### 11.1 通用约定
595
231
 
596
232
  - 加密失败时 **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 仅用于解密历史消息,不用于加密新消息
233
+ - 每条消息使用独立的随机密钥与随机 nonce
234
+ - 密钥材料 **MUST** 由密码学安全随机数生成器生成
235
+ - 实现 **MUST NOT** 在日志中输出消息密钥或密钥包裹解密结果
236
+
237
+ ### 11.2 客户端操作签名
238
+
239
+ `client_signature` Gateway / 服务端用于校验客户端业务操作来源的签名,不等同于 E2EE 信封里的 `sender_signature`。当前 Gateway 语义如下:
240
+
241
+ - `group.send` / `group.v2.send` / `group.v2.pull` / `group.v2.ack` 等常规消息 RPC 在连接级认证身份与 RPC 参数声明身份一致时,使用连接级身份 fast path,不重复执行 ECDSA 验签。
242
+ - 群管理、成员变更、状态提交、E2EE bootstrap / state proposal 等敏感操作仍可要求或验证 `client_signature`。
243
+ - 如果 RPC 参数中的 `aid` / `device_id` / `slot_id` 等身份声明与连接级身份不一致,Gateway 必须要求 `client_signature` 并完成验签;签名 AID 与连接 AID 不一致时只允许明确支持能力身份的命名空间。
244
+ - 客户端主动携带 `client_signature` 时,Gateway 会按当前缓存策略验证签名并向下游注入已验证上下文。
245
+
246
+ E2EE 安全性仍由接收端验证 `sender_signature`、AAD、recipient proof / digest 和 state commitment 保证;Gateway 不应移除 E2EE 信封中的签名字段。
247
+
248
+ ### 11.3 成员移除后的安全保证
249
+
250
+ - 成员被踢出或退出后,后续消息 bootstrap 不再包含其设备,因而无法为其生成新的密钥包裹;已离开成员无法解密移除后发出的消息。
251
+ - 已离开成员此前收到的历史消息密钥包裹不受影响(历史消息本就已经解密完成或本地缓存)。
682
252
 
683
253
  ---
684
254
 
@@ -688,215 +258,38 @@ group_secret **MUST** 持久化到本地存储。推荐存储在 FileKeyStore
688
258
 
689
259
  | 属性 | 表现 | 说明 |
690
260
  |------|:----:|------|
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 解密
261
+ | **消息级前向安全** | ✅ | 每条消息使用独立密钥,发送方一次性会话密钥用后即弃 |
262
+ | **防中间人** | | 密钥协商基于已认证的设备身份密钥与 Prekey |
263
+ | **防服务端注入** | ⚠️ | State Commitment 绑定消息到特定的已签名成员状态,State Chain 提供分叉检测,但完整防护依赖应用层对状态链连续性的主动校验 |
264
+ | **防篡改** | ✅ | AAD 覆盖所有路由关键字段,发送方签名覆盖密文与 Recipients Digest |
265
+ | **防重放** | ✅ | 本地去重 + 时间戳新鲜度窗口 |
266
+ | **成员变更后的密钥隔离** | ✅ | 密钥包裹按当前 bootstrap 快照生成,新成员收不到历史消息包裹,被移除成员收不到后续消息包裹 |
839
267
 
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
268
+ ### 12.2 V1(已禁用)的本质差异
845
269
 
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 天) |
270
+ V1 机制(Epoch Group Key:群级共享对称密钥 `group_secret` + 版本号 `epoch` + 通过 P2P 通道向每个成员分发同一把密钥)已在线上全面禁用,不再是当前协议的一部分。V2 用"每条消息独立密钥 + 逐设备密钥包裹"取代了群级共享密钥模型,因此不再需要 V1 中用于同步 `epoch` 的一次性 CAS 轮换 RPC;群成员/权限的变更改由 §6 的状态签名两阶段流程记录和验证。
853
271
 
854
272
  ---
855
273
 
856
- ## 15. 错误码
274
+ ## 13. 错误码
857
275
 
858
276
  | 错误码 | 名称 | 说明 |
859
277
  |--------|------|------|
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` | 密钥请求者不是群成员 |
278
+ | -32040 | `E2EE_GROUP_KEY_MISSING` | 找不到属于本设备的密钥包裹 |
279
+ | -32041 | `E2EE_GROUP_EPOCH_MISMATCH` | 消息 epoch 与服务端当前值不匹配 |
280
+ | -32042 | `E2EE_GROUP_STATE_MISMATCH` | State Commitment / State Chain 校验失败 |
864
281
  | -32044 | `E2EE_GROUP_DECRYPT_FAILED` | 群消息解密失败 |
865
282
 
866
283
  ---
867
284
 
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. 变更记录
285
+ ## 14. 变更记录
891
286
 
892
287
  | 版本 | 日期 | 变更 |
893
288
  |------|------|------|
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
-
901
-
902
-
289
+ | 2.0-draft | 2026-07 | 重写为 V2 机制(消息级密钥 + 逐设备密钥包裹 + 状态签名两阶段流程),移除已禁用的 V1 Epoch Group Key 机制描述 |
290
+ | 1.0-draft-r5 | 2026-04 | (V1,已废弃)成员加入改为 MUST 轮换 epoch;服务端以 `min_read_epoch` 约束新成员加入前历史访问 |
291
+ | 1.0-draft-r4 | 2026-04 | (V1,已废弃)新增 Epoch CAS 轮换 RPC rotation_signature 要求 |
292
+ | 1.0-draft-r3 | 2026-04 | (V1,已废弃)Membership Commitment 绑定 group_secret 哈希;新增 Membership Manifest 签名机制 |
293
+ | 1.0-draft-r2 | 2026-04 | (V1,已废弃)group_e2ee 默认值改为 true;补充成员加入轮换策略 |
294
+ | 1.0-draft-r1 | 2026-04 | (V1,已废弃)修正 Membership Commitment 命名与 message_id 来源 |
295
+ | 1.0-draft | 2026-04 | (V1,已废弃)初始版本:Epoch Group Key 机制 |