Meshline
简体中文规范下载 Markdown 原文

中文为规范基准;英文为维护译本。

群组模型与密钥

群组托管协议 · 群组核心对象

固定托管

每个群在创建时选择一个托管中继,此后不迁移也不自动接管。中继是当前群状态、成员投影、事件顺序和密钥版本顺序的权威来源;客户端保存长期消息历史和自身秘密。群状态不经过账户归属中继,也不使用 DHT。

角色权限

群成员角色为 owner、administrator 或 member。每个群始终只有一名 owner。

角色表示群内业务权限。权限如下:

操作 owner administrator member
发送、同步和订阅 可以 可以 可以
修改群内昵称 仅本人 仅本人 仅本人
创建邀请 可以 可以 由 invite_policy 决定
查看邀请 全部邀请 全部邀请 自己创建的邀请
撤销邀请 全部邀请 自己创建的邀请 自己创建的邀请
审批入群申请 可以 可以 不可以
修改群属性或角色、转让所有权或关闭群 可以 不可以 不可以
移除成员 任意非 owner 成员 仅普通成员 不可以
封禁账户 任意非 owner 账户 普通成员或非成员 不可以
解除封禁 可以 可以 不可以
批准成员密钥重置 包括自己在内的任意成员 仅其他普通成员 不可以
轮换客户端群秘密 可以 不可以 不可以
主动离群 先转让所有权 可以 可以

中继执行方法时必须按当时的当前角色判断权限。事件或列表结果只证明中继曾经接受过某项内容,不会把当时的管理权限延续到以后。

客户端秘密与中继秘密

群使用两份 32-byte 随机秘密,均必须由密码学安全随机源生成:

  • client_group_secret 由当前成员共享,托管中继不得取得;
  • relay_epoch_secret 由托管中继为每个 epoch 独立生成。

密钥版本推进

epoch 从 0 开始,在群内严格递增但不要求连续。创建、批准申请、成员离开、成员移除、成员密钥重置批准、客户端秘密轮换和中继主动轮换都会建立新的密钥版本。封禁批次包含当前成员时,整批移除这些成员并建立一个新密钥版本;全部目标均非当前成员时不推进密钥。修改群属性或角色、转让所有权、解除封禁和普通消息不会推进密钥版本。

客户端秘密承诺

客户端秘密承诺按网络绑定 JSON 输入计算:

client_secret_commitment =
  network_bound_json_hash({
    $type: "meshline.group.client_secret.commitment",
    group_id,
    secret: base64url(client_group_secret)
  })

创建群时保存初始承诺,只有客户端秘密轮换成功时才能替换它。批准申请、成员变化和中继秘密轮换可以推进 epoch,但继续沿用当前客户端秘密承诺。

中继不知道 client_group_secret,因此不能自行重新计算承诺,也不能确认客户端秘密盒中实际封装的秘密;取得盒的成员负责完成这项密码学验证。中继处理入群申请批准或成员密钥重置批准请求时,检查请求携带的承诺是否与群的当前承诺一致;不一致时拒绝请求。

群秘密盒与封装规则

GroupSecretBox

字段 类型 必需 语义与约束
alg string 是 密钥封装构造,固定为 X25519-HKDF-SHA256-AES256GCM;接收方不支持或不匹配时必须拒绝该盒
enc string 是 发送方为该盒生成的 32-byte 临时 X25519 公钥,使用无 padding base64url
sealed_secret string 是 12-byte AES-GCM nonce、32-byte ciphertext 和 16-byte tag 的顺序拼接,使用无 padding base64url;解密结果必须恰为 32-byte 秘密

客户端秘密盒和中继秘密盒均使用 GroupSecretBox,按以下步骤封装各自的 32-byte 秘密:

  1. 发送方为每个盒生成临时 X25519 密钥对,以临时私钥和目标公钥计算共享秘密;接收方使用自己的目标私钥和盒中的 enc 计算同一共享秘密。双方均须按 X25519 共享秘密校验拒绝全零结果,通过后才能继续封装或解封。
  2. 使用共享秘密作为 HKDF-SHA-256 的 IKM,salt 为 UTF-8 字符串 Meshline/keybox-salt/v1 的 SHA-256,info 为下文对应盒类型的网络绑定 JSON 输入,派生 32-byte 封装密钥。
  3. 使用该封装密钥、独立生成的 12-byte 随机 nonce 和与 info 相同的 AAD,通过 AES-256-GCM 加密秘密,按 sealed_secret 的布局封装结果。

临时 X25519 私钥和 nonce 必须来自密码学安全随机源。两类盒必须使用各自的上下文,不得互换。

客户端秘密盒

客户端秘密盒的 KDF info 与 AES-GCM AAD 使用以下网络绑定对象:

{
  "$type": "meshline.group.client_secret_box.aad",
  "group_id": "grp_...",
  "account": "neo:860833102:...",
  "member_encryption_public_key": "base64url...",
  "client_secret_commitment": "sha256:..."
}

生成盒时,客户端必须按相应方法确定目标账户和 member_encryption_public_key。批准新成员或重置公钥时,批准者先验证成员完整签名请求及设备证书,账户取自已验证设备证书,公钥取自完整签名请求。管理链、批准权限、请求签名或账户绑定验证失败时,不得封装或上传秘密。

中继处理包含客户端秘密盒的请求时,按方法要求检查目标账户和密钥盒覆盖范围,以及每个盒的算法标识和编码长度。中继没有成员私钥,不能解开客户端秘密盒,也不能确认其明文、AAD 目标或客户端秘密承诺是否正确。

解密时,接收方必须使用封装目标对应的成员私钥派生公钥,并依据预期上下文按上述定义重建 AAD。历史盒使用生成时的目标公钥。AEAD 验证或解密后的客户端秘密承诺校验失败时,必须拒绝该盒。

客户端秘密轮换时的密钥盒准备规则见 group.secret.rotation.prepare。更换成员公钥不重新封装历史客户端秘密盒,历史盒仍需对应的旧成员私钥解开。

客户端秘密盒不绑定 epoch;客户端秘密和目标成员公钥均未变化时,同一盒可以跨密钥版本复用。盒作为写方法的外层投递材料,不进入签名事件。提交盒的 owner 或 administrator 必须保证秘密和目标上下文正确。

中继秘密盒

中继秘密盒的 KDF info 与 AES-GCM AAD 使用以下网络绑定对象:

{
  "$type": "meshline.group.relay_secret_box.aad",
  "group_id": "grp_...",
  "account": "neo:860833102:...",
  "device_id": "dev_...",
  "epoch": 7
}

中继根据 group.key.sync 的设备会话确认调用账户、调用设备 ID 和该设备的加密公钥,并据此封装中继秘密;接收方按本设备对应的上下文重建 AAD。

群应用秘密派生

客户端取得两份秘密并验证承诺后,按下式派生 32 字节的群应用秘密:

epoch_application_secret = HKDF-SHA-256(
  IKM  = client_group_secret || relay_epoch_secret,
  salt = SHA-256(UTF8("Meshline/group-epoch-salt/v1")),
  info = network_bound_json_bytes({
    $type: "meshline.group.epoch_secret",
    group_id,
    epoch,
    client_secret_commitment
  }),
  L = 32
)

拼接两份固定长度共享秘密后再使用 HKDF,与 RFC 9954 第 3.3 节描述的固定长度组合方式一致。

安全边界

中继单独只有 relay_epoch_secret,成员单独只有 client_group_secret 和中继发给其设备的中继秘密。中继可以拒绝服务、裁剪有限期历史或向不同设备隐藏材料,但不能只凭自身数据解密消息。中继与任何仍持有客户端秘密的一方串谋时,可以结合中继仍保留的相应版本的中继秘密,派生群应用秘密并解密对应消息。

成员或设备失去访问权后,轮换中继秘密可以阻止其继续取得新的群消息密钥,但不能修复客户端秘密本身的泄露。

发现或怀疑客户端秘密泄露时,建议 owner 执行客户端秘密轮换。轮换时只向当前仍受信任的成员提供新秘密盒。如果某个当前成员已经不再可信,owner 必须先将其移除,再生成全新客户端秘密并完成轮换;新客户端秘密启用后,没有取得该秘密的一方不能再派生后续版本的群应用秘密。轮换不能使此前已经泄露或解密的历史消息重新保密,也不能防止仍获授权的当前成员主动与中继串谋。

若要排除已离群或被移除成员与中继串谋,owner 必须在其成员资格结束后生成全新客户端秘密和承诺,重新准备轮换;继续原准备不能视为已经完成这种安全排除。

封禁同时移除当前成员并禁止其重新加入;其秘密轮换与成员移除遵循相同规则。

新设备的历史起点也是中继访问边界:若它从其他来源取得同一 epoch 的旧密文和中继秘密,协议不能阻止解密。

本协议不声明前向保密或入侵后安全性质。