群组核心对象
群组 ID
创建者生成新的 16-byte nonce;同一创建者在同一中继下的 nonce 不得重复,推荐使用密码学安全随机源生成。对以下群标识对象的网络绑定 JSON 输入计算 SHA-256:
{
"$type": "meshline.group.identity",
"creator": "neo:860833102:...",
"nonce": "base64url...",
"relay_id": "0x..."
}
creator 是首次创建群的 owner,relay_id 是选定的托管中继;二者和 nonce 在群生命周期内不变。群 ID 按以下公式派生,其中 group_identity_input 表示上述群标识对象:
group_id = "grp_" + base64url(first_16_bytes(SHA-256(network_bound_json_bytes(group_identity_input))))
group_id 必须匹配 ^grp_[A-Za-z0-9_-]{22}$。名称、简介、成员、容量、邀请策略、事件位置和密钥版本不参与派生。群 ID 与托管中继绑定,但不能从 ID 反推出中继。
GroupState
GroupState 是托管中继维护的当前群状态:
| 字段 | 类型 | 必需 | 语义与约束 |
|---|---|---|---|
group_id |
string | 是 | 当前状态所描述的群 ID |
name |
string | 是 | 当前群的名称;必须至少包含一个非空白字符,UTF-8 编码后最多 256 bytes |
description |
string | 否 | 当前群的简介;省略表示没有简介;非空时不得仅包含空白字符,UTF-8 编码后最多 4 KiB(4,096 bytes) |
status |
string | 是 | 群的生命周期状态:active 表示群仍在存续;closed 表示群已永久关闭 |
owner |
string | 是 | 群主账户;必须在当前成员投影中恰好出现一次,且对应角色必须为 owner |
member_capacity |
integer | 是 | 该群的成员数上限,必须为正;可以暂时小于成员数,此时保留现有成员但拒绝继续准入 |
member_count |
integer | 是 | 当前成员数量,包括 owner 和 administrator;必须为正 |
invite_policy |
string | 是 | 群邀请策略;owner 和 administrator 不受该字段限制 |
invite_policy 的含义为:
| 值 | 普通成员可以创建的邀请 |
|---|---|
administrators |
不可以创建邀请 |
members_targeted |
只能创建指定 invitee 的定向邀请 |
members_shareable |
可以创建定向邀请,也可以创建公开邀请 |
成员投影按群 ID 和账户 ID 区分成员,包含 account、role 和 member_encryption_public_key。客户端从已验证的管理链建立成员资格、角色和公钥。
群存续期间,中继必须保留当前状态和成员投影。改变状态的方法必须使投影更新与对应事件追加原子生效。
GroupEvent
GroupEvent 是托管中继追加的权威时间线条目,用于记录群管理操作、密钥轮换和群消息:
| 字段 | 类型 | 必需 | 语义与约束 |
|---|---|---|---|
sequence |
integer | 是 | 中继为该事件分配的唯一时间线位置;创建事件固定为 0,后续值必须严格大于此前时间线头但不要求连续。客户端用它排序、分页和引用群消息 |
epoch |
integer | 是 | 该事件完成后群所处的密钥版本;创建事件为 0,消息事件必须等于其信封版本;未推进密钥的事件保持完整群时间线中前一事件的版本 |
payload |
object | 是 | 完整事件正文,通过 payload.$type 识别类型;正文结构与处理规则见对应对象或方法定义 |
accepted_at |
integer | 是 | 中继接受该事件的 UTC Unix 秒;用于保留期限,不替代 sequence 的排序语义 |
signer_device_id |
string | 条件 | 中继接受客户端写请求时验证的设备 ID;客户端发起的事件必须提供并引用 group.sync 响应中的证书,中继主动轮换必须省略 |
完整群时间线中的 epoch 单调不减,但不要求连续。
消息事件是 payload.$type = meshline.group.message 的事件,可以按群组数据保留与访问规则到期清理。其他事件属于管理事件,连同其验签证书永久保留。
消息过滤或裁剪、以及客户端从中途读取,可能使相邻可见事件并非时间线中的相邻事件。管理事件不受消息访问区间限制且不得裁剪;从创建开始完整同步时,客户端可以取得全部管理事件。密钥版本的处理遵循 group.sync。
邀请创建与撤销、申请提交或拒绝、成员密钥重置请求的提交、替换、撤回或拒绝,以及客户端秘密轮换 prepare 不写入时间线。这些待处理记录由批准者在操作时验证;入群批准事件记录已经授权的账户和公钥,成员密钥重置批准事件记录账户和新公钥。封禁和解除封禁影响后续操作权限,成功调用必须将完整签名请求写入管理链。
中继秘密轮换事件
中继主动更换中继秘密时生成以下 payload:
| 字段 | 类型 | 必需 | 语义与约束 |
|---|---|---|---|
$type |
string | 是 | 固定为 meshline.group.key.rotated |
新密钥版本由外层 GroupEvent.epoch 表达。该正文由托管中继生成,不携带 device_signature,外层事件必须省略 signer_device_id。客户端验证正文的固定值及事件的密钥版本推进关系。该事件永久保留,但不改变成员、角色、封禁、客户端秘密承诺或管理链头。
管理链
参与事件与前序引用
管理链只连接客户端签署并实际写入时间线的管理事件。消息事件和中继主动轮换不推进这条链。下列正文类型参与管理链:
正文 $type |
生效内容 |
|---|---|
meshline.group.create |
建立群、初始 owner 和客户端秘密承诺 |
meshline.group.application.approval |
批准成员加入 |
meshline.group.update |
修改群属性 |
meshline.group.role.update |
修改管理角色 |
meshline.group.owner.transfer |
转让所有权 |
meshline.group.member.leave |
签署账户离群 |
meshline.group.member.removal |
移除目标成员 |
meshline.group.member.recovery.approval |
批准成员新公钥 |
meshline.group.secret.rotation |
更新客户端秘密承诺及可选 owner 公钥 |
meshline.group.member.ban |
封禁目标账户并移除其中的当前成员 |
meshline.group.member.unban |
解除封禁 |
meshline.group.close |
永久关闭群 |
创建请求是管理链起点,不携带 prev_hash。其余上述请求的签名正文必须携带 prev_hash,其值为本地已验证的上一项管理链摘要,匹配 ^sha256:[A-Za-z0-9_-]{43}$,解码结果必须为 32 bytes 且符合无 padding base64url 的规范编码。prev_hash 与全部业务字段一同由调用设备签署,不能由中继在验签后补充或改写。未写入时间线的请求不推进管理链;消息信封及中继主动轮换正文不定义此字段。
管理链摘要计算
管理事件 E 对完整 payload 的网络绑定 JSON 输入计算哈希,输入保留根 device_signature 和所有未知属性:
management_hash(E) = network_bound_json_hash(E.payload)
输入构造遵循网络绑定 JSON 输入规则。摘要包括正文签名;计算正文签名时只排除根 device_signature。signer_device_id 用于定位验签证书,客户端仍须验证证书、正文签名及所属账户的操作权限;其余外层字段继续按排序、时间和密钥规则校验。下一项管理操作引用 management_hash(E)。
中继提交与并发控制
中继必须验证会话、设备签名及调用权限;创建请求按 group.create 的规则建立链起点。对其他请求,中继必须确认 prev_hash 等于当前管理链头,且操作生效时调用权限与该匹配关系仍然成立。违反 prev_hash 字段约定时返回 bad_request。
上述请求成功时必须追加管理事件并更新管理链头。请求证据、状态、事件,以及时间线头和管理链头必须原子提交。
两个请求引用同一链头时,只能有一个成功;另一个如需重新提交,应先同步新事件、重新核对权限与操作内容,再重新签名。客户端不得只替换摘要而复用旧签名。
客户端验证与恢复
管理链验证与状态重建
客户端第一次从创建事件开始验证:核对预期群 ID 的派生关系、创建者账户、设备证书和建群签名,建立初始状态并计算链头;随后逐项验证 prev_hash、签名、签署账户在前序状态中的成员资格、角色及封禁状态,再按对应方法应用结果并计算新链头。
成员按账户区分,同一账户不得重复;角色为 owner、administrator 或 member。建群事件建立初始 owner;入群批准建立普通成员;角色更新、所有权转让、成员离开、移除或封禁、密钥重置批准和客户端秘密轮换按各方法改变成员状态。成员重新加入时使用新批准的公钥,不能恢复其此前的成员记录。待审批申请或重置请求不改变已生效状态。
封禁集合在创建时为空。客户端通过 group.sync 取得管理事件,按已验证管理链中的封禁和解除封禁请求分别加入和移除目标账户,由此重建封禁名单。
申请者和请求重置密钥的成员签署包含公钥的完整业务请求。批准者独立验证请求签名、设备证书及请求账户绑定后,在批准对象中签署该账户和公钥。其他客户端验证批准事件及批准者在前序状态中的权限。封装客户端秘密时,目标成员公钥的来源与验证必须遵循客户端秘密盒规则。
验证中断与恢复
中间管理事件缺失、无效签名、无权限操作或不能解释的管理扩展会阻止后续状态验证,不得按普通消息的拒绝规则越过它。遇到未知的客户端管理正文类型时,客户端必须暂停管理投影;扩展定义其哈希参与和状态规则后才能继续。客户端可以保留相应事件,便于后续重新处理。未知属性仍参与哈希和签名,但仅按已知方法定义的字段改变状态。
客户端重新加载本地状态时,必须保证已验证管理链头、成员投影、封禁状态及同步位置保持一致;后续处理继续已验证的管理链,不能以中继快照覆盖。管理链校验失败时,停止应用后续管理事件并报告错误;缺失记录可以通过 group.sync 重新请求。