← E2EE 专题 · MATRIX × SIGNAL COMPARISON

Matrix · vodozemac 对照 Signal

vodozemac 0.10.0 源码为切入点,把 Matrix 端到端加密(Olm + Megolm)每个组件逐项摆到 Signal 旁边对照—— 算法层异同、设计哲学分叉、最大的真实差距。

Olm = Signal DR + 3DH Megolm ≠ Sender Keys Federation vodozemac 0.10.0 PQ 落后 2.5 年
01 vodozemac 在 Matrix 加密栈中的位置 对位 libsignal

vodozemaclibolm(C 实现,2014)的纯 Rust 重写,由 matrix.org 自己维护。 当前版本 0.10.0,最近一次 push 在 2026-05-06——活跃维护中。 Apache-2.0 协议、Least Authority 做过一次安全审计无重大发现、Cargo.toml 里 unsafe_code = "deny"、所有 panic 路径(unwrap/expect/panic/unreachable)被 Clippy lint 全部禁掉,工程纪律与 libsignal-rust 旗鼓相当。

生态对位(左 Matrix / 右 Signal):

栈分层 · 同位关系
MATRIX 栈 SIGNAL 栈 Element · Cinny · FluffyChat 客户端应用 Signal · WhatsApp · Wire 客户端应用 matrix-rust-sdk · matrix-js-sdk 高级会话 / 房间 / 同步 Signal client SDKs 高级会话 / 群 / 同步 vodozemac 0.10.0 纯 Rust · Olm + Megolm + SAS + MSC4108 libsignal (Rust) 纯 Rust · X3DH/PQXDH + DR/SPQR + Sender Keys Olm spec · Megolm spec · matrix-spec 独立标准化(matrix-org GitLab) signal.org/docs/specifications/* 单一 spec 源 两栈结构对位 · 但中间橙色层(密码学核心)实现的是不同的协议 下面 4 节逐项摊开
展开 · vodozemac Cargo.toml 关键依赖与工程纪律

密码学栈(截至 0.10.0):

curve25519-dalek = "4.1.3"   # 经典 ECDH
ed25519-dalek    = "2.1.1"   # 经典签名
x25519-dalek     = "2.0.1"   # 经典 ECDH
aes              = "0.8.4"   # 用于 AES-CBC (Olm 消息加密)
cbc              = "0.1.2"
chacha20poly1305 = "0.10.1"  # 用于较新路径
hkdf             = "0.12.4"
hmac             = "0.12.1"
sha2             = "0.10.9"
subtle           = "2.6.1"   # 常时间比较
zeroize          = "1.8.2"   # 敏感数据清零

注意:零后量子依赖。没有 ml-kem / pqcrypto-* / x-wing。GitHub 代码搜索 kyber OR pqxdh OR MLKEM OR post_quantum 在整个 repo 命中 0 文件。

Cargo.toml 里的 lints:

[lints.rust]
unsafe_code = "deny"
missing_docs = "deny"

[lints.clippy]
panic = "deny"
unreachable = "deny"
expect_used = "deny"
unwrap_used = "deny"
mem_forget = "deny"

这套纪律和 libsignal-rust 等价——实现层无差距

02 Olm 1:1 · 3DH vs X3DH 含 fallback key 修正

Olm 基于 Signal Double Ratchet 论文做的独立实现,主体棘轮算法几乎等价——CK/RK 链、Symmetric Ratchet 每条消息派生 MK、 DH Ratchet 每回合刷新 root key——和 Signal DR 同构。差异只在初始握手。

Olm 用的是 3DH(Triple Diffie-Hellman),不是 X3DH。 关键修正:Olm fallback key 机制(功能等价 Signal SPK),不是"OTK 耗尽就建不起会话"。 证据直接来自 vodozemac 源码 src/olm/account/mod.rs:221-224

fn find_one_time_key(&self, public_key: &Curve25519PublicKey)
    -> Option<&Curve25519SecretKey>
{
    self.one_time_keys
        .get_secret_key(public_key)
        .or_else(|| self.fallback_keys.get_secret_key(public_key))
        //         ↑ OTK 查不到时,从 fallback key 里找
}
3DH vs X3DH · 算法层并排
MATRIX Olm 3DH SIGNAL X3DH (有 OPK 时) Bob's Pre-key Bundle /keys/claim 拉取 IK_B 设备身份 — 服务器二选一 — OTK_B 优先 用一次即删 Fallback Key 兜底 可重用 · ≤2 个 两者都由设备 Ed25519 签名 (在 matrix-spec 信令层) 3 个 DH DH(IK_A, X_B) DH(EK_A, IK_B) DH(EK_A, X_B) X_B ∈ {OTK_B, Fallback} SK = HKDF(0, S, "OLM_ROOT") Bob's Pre-key Bundle prekey fetch IK_B 长身份 SPK_B + Sig(IK_B) 长期 · 周轮换 OPK_B 一次性 · 可选 SPK 必有,OPK 可同时存在 SPK 签名在 X3DH 协议内验 (不是外挂在信令层) 4 个 DH DH(IK_A, SPK_B) DH(EK_A, IK_B) DH(EK_A, SPK_B) DH(EK_A, OPK_B) ↑ 第 4 个 · 仅有 OPK 时 SK = HKDF(...) 关键观察 Olm 3DH (X = OTK 或 Fallback) ≅ Signal X3DH 没有 OPK 的简化版 真正的差距 = Signal 多了一个 DH (DH4) + SPK 签名在协议内强制,不是 fallback 的有/无
交互 · Bob 当前可用的 prekey 状态
有 OTK:vodozemac 用 OTK_B 算 3DH,用后从 one_time_keys 删除。功能上 ≈ Signal 用 OPK_B 的 DH4 那一支。

所以"真正的差距"应该这样说:

  1. 多 1 个 DH:Signal 用 OPK 时跑 4 DH(多一个 DH(EK_A, OPK_B)),多一段熵进 KDF——这是额外的 defense in depth,不是 categorical 的安全提升
  2. 签名层级:Signal 把 Sig(IK_B, SPK_B) 写在 X3DH 算法内(Alice 必须验签才能进握手);Matrix 把 fallback key 的签名外挂在 /keys/uploadsigned_curve25519: 字段——客户端实现忘了验签,Olm 协议本身不会报错
  3. Fallback key 轮换:Signal SPK 在 spec 内建议周级轮换;Matrix fallback 由 client 自己决定(vodozemac 这层只持有 current + previous 两份),轮换疏漏会弱化 forward secrecy
展开 · vodozemac shared_secret.rs 完整 3DH 公式

从源码 src/olm/shared_secret.rs 顶部 doc comment 直接抄录:

//! S = ECDH(Ia, Eb) || ECDH(Ea, Ib) || ECDH(Ea, Eb)
//!
//! R0, C0,0 = HKDF(0, S, "OLM_ROOT", 64)

其中 Ia / Ib 是双方身份密钥,Ea 是 Alice 的临时密钥, Eb 是 Bob 的 prekey(OTK 或 fallback,由 find_one_time_key 决定)。

对比 Signal X3DH (PQXDH 之前版本):

DH1 = DH(IK_A,  SPK_B)
DH2 = DH(EK_A,  IK_B)
DH3 = DH(EK_A,  SPK_B)
DH4 = DH(EK_A,  OPK_B)   // optional
SK  = KDF(DH1 || DH2 || DH3 [|| DH4])

把 Olm 的 X_B 替换成 SPK_B,三式完全对齐——剩下的差距就是 Signal 的 DH4。

03 群组:Megolm vs Sender Keys 设计哲学分叉

这里是 Matrix 和 Signal 理念差异最大的地方,不是"谁落后",是 trade-off

Signal Sender Keys

  • 每个发送者一条独立的 Sender Key 链
  • 加入群后看不到加入前的历史
  • 退出 / 设备被踢 → 全员重置 Sender Key
  • 状态轻 · 实现简单
  • 不支持服务端加密历史备份(设计如此)
  • Forward Secrecy 强 · PCS 强

Matrix Megolm

  • 每个发送者维护一个 outbound Megolm session
  • Session key 经 1:1 Olm 加密分发到所有成员的所有设备
  • 持 session key 者能解整段 session 历史
  • 这就是 "Share keys with new device" / 备份 的根本
  • 支持服务端 Megolm session 备份(用 recovery key 加密)
  • Forward Secrecy · PCS 需主动 rotate
Megolm group fan-out · 一个发送者的 session 分发到多设备
👩 Alice 发送者 Outbound Megolm sess 房间成员 · 每个用户多设备 @bob:matrix.org 📱 手机 💻 桌面 🖥 服务器 +1 @carol:another.org 📱 手机 💻 桌面 @dave:matrix.org 📱 手机 每条 = 一次 Olm 1:1 加密 分发同一个 Megolm session key (虚线 = 新设备 · 后补发)

Megolm 的核心机制:一个发送者一个 session,session key 通过 1:1 Olm 单独加密给每台设备。 新设备加入时——比如 Bob 用 iPad 登录——已经在 session 内的成员要给这台新设备补发所有现存 session key, Bob 才能解密之前已发的消息。这就是 Element 大群里启动慢的根本原因,也是支持加密备份的根本原因 (备份的就是这堆 Megolm session keys)。

展开 · Megolm 棘轮细节

Megolm session 的 ratchet 是 4 路 HKDF 链(不是 Signal Symmetric Ratchet 那样的单路):

R(0) = (R0, R1, R2, R3)        # 初始 128 字节 × 4 路
R(2^24·a + 2^16·b + 2^8·c + d)
  = HKDF-advance 4 路的特定组合

四路设计的目的是允许接收方跳跃式前进(比如从 message 100 跳到 message 1000 不必逐步 HKDF 一千次),代价是 forward secrecy 弱于 Signal——拿到任一路 R 都能向前生成所有后续路径。

消息加密用的密钥从棘轮当前状态派生,AEAD 选 AES-256-CBC + HMAC-SHA256(EtM)。

退出 / 设备移除时,发送者主动 rotate session:换一个全新 session key 再 fan-out 一遍。

04 多设备 + 联邦威胁面 Matrix 独有

Matrix 是联邦架构:任何人可起一个 homeserver,多个 homeserver 通过 server-to-server 协议互联。 Signal 是中心化。这一层差异引入了 Signal 完全没有的威胁面。

Signal · 集中威胁模型

  • 单一公司运营服务器
  • 元数据:sealed sender 隐藏发送者,但收件人和时序仍可见
  • 多设备:主设备配对挂从设备(Sesame)
  • 验证:safety number(双方对照 60 位数字)
  • 需要信任 = Signal 公司本身

Matrix · 联邦威胁模型

  • 任意人起 homeserver,互联
  • 元数据:分散在每个相关 homeserver
  • 多设备:原生一等公民 · 每设备独立加入
  • 验证:cross-signing + emoji SAS + per-device fingerprint
  • 需要信任 = (对 homeserver 设计为零信任)
恶意 homeserver 能看到什么 / 看不到什么
homeserver A @alice 所在 良好运营 Alice 的设备列表 homeserver B ⚠ @eve 所在 (恶意) 想偷 Alice 内容 在房间内 Server-to-server 联邦协议 同步房间事件 / 设备列表 / 加密密文 看不见 ✓ • 消息明文 • Megolm session 私钥 • 用户身份私钥 看得见 ⚠ (元数据) • Alice 的设备公钥列表 • 房间成员 / 加入时间 / 消息时序 • 消息密文 (可以存 · 等量子来再解)
展开 · 验证机制 cross-signing 与 SAS

Matrix 的跨设备验证分两层:

  • Cross-signing:每个用户有 3 把 Ed25519 跨设备密钥(master / self-signing / user-signing)。Master key 用于离线认证你的所有设备;self-signing key 签自己的设备;user-signing key 签别人的 master key。这套让你信任过一次别人,就能传递信任到他们所有设备
  • SAS (Short Authentication String):基于 emoji 的现场验证。双方交换 ephemeral ECDH,从共享密钥派生 7 个 emoji(共 49 bit 熵),双方读出来对比。等价于 Signal safety number 但 UX 更友好。

vodozemac 直接实现了 SAS(src/sas/),cross-signing 在 matrix-sdk 层。

05 后量子差距 · 最大的实质差距 ≈ 2.5 年

前 4 节大部分差异是设计 trade-off。这一节才是真正的差距——而且差得明显。

演进时间线对比 · Signal vs Matrix
2013 2016 2022 2023 2025-10 2026 SIGNAL Double Ratchet X3DH 上线 PQXDH ⚛ hybrid X25519+Kyber SPQR ⚛⚛ Triple Ratchet · iOS+Android ship MATRIX libolm (C) Megolm vodozemac 0.1 vodozemac 0.10 ⚠ 仍纯 X25519 · 无 PQ cargo grep "kyber|pqxdh|MLKEM" = 0 命中 这段时间 Signal 已经做了两轮 PQ 升级 截至 2026-05 实际差距 Signal 已在 iOS + Android 两端无条件声明 spqr = true· Matrix 整个 vodozemac 0.10.0 依赖图无任何后量子原语 harvest-now-decrypt-later 模型下:今天发的 Matrix 消息,CRQC 到来时全部能被解

Matrix 协议层面没有明显障碍阻止做 PQXDH 等价物——Olm 3DH 在 KDF 输入里再加一段 ML-KEM Encap 出来的 SS, 数学上完全成立。技术上可行,缺的是:

  • Spec 提案:matrix-spec-proposals 仓库截至本文写作时未见公开 PQ MSC(我没核实过完整列表,可能漏掉),需要起一个 MSC 走流程
  • vodozemac 实现:把 ml-kem crate 拉进依赖,新增 pq_prekeys 模块,扩展 SharedSecret 拼接顺序
  • matrix-spec 信令层/keys/upload 增加 signed_kyber_pubkey: 类字段
  • 客户端 UX:cross-version 兼容、capability 协商(学 Signal 的 spqr flag)

如果 matrix-org 优先级提上去,1 年内 ship PQ-Olm 没有技术难度。 差距是产品优先级 + 资源问题,不是算法问题。

展开 · 推演一个 "PQ-Olm" 设计草案

类比 PQXDH 的扩展方式,Olm 3DH 可以直接升级为 hybrid:

// Bob 上传到 /keys/upload 的字段
device_keys: { IK_B, ... }
one_time_keys:           [OTK_B_1, OTK_B_2, ...]
signed_curve25519:       [OTK_B_i + Ed25519 签名]
signed_kyber:            [PQ_OTK_B_1, ...]    // NEW
signed_kyber_fallback:   [PQ_FB_B]            // NEW

// Alice 端建会话
S_ecc  = ECDH(IK_A, X_B) || ECDH(EK_A, IK_B) || ECDH(EK_A, X_B)
(CT, S_pq) = MLKEM.Encap(PQ_X_B)              // NEW

SK = HKDF(0, S_ecc || S_pq, "OLM_PQ_ROOT", 64)
                 ↑
              hybrid 拼接

// PreKey message 多发一个字段
{ ..., kyber_ciphertext: CT }                 // NEW

对应 vodozemac 改动:

  • 新模块 src/olm/account/pq_keys.rs(仿照 one_time_keys.rs
  • shared_secret.rs 增加 Hybrid3DH_PQSecret 类型,96 字节 ECC + 32 字节 PQ = 128 字节 IKM
  • HKDF info 改为 "OLM_PQ_ROOT" 做 domain separation
  • PreKeyMessage protobuf 加 optional bytes kyber_ciphertext = 5;

预计代码量:300-500 行(参考 libsignal PQXDH 的实现规模)。不复杂

06 演进时间表 · References 2013 – 2026
阵营 版本 时间 关键变化
Signal Double Ratchet 2013 DH Ratchet + Symmetric Ratchet
Matrix Olm (libolm C) 2014 3DH + 基于 DR 论文的独立实现
Matrix Megolm 2016 群组:可分发 session key 的棘轮
Signal X3DH 2016 SPK + OPK 异步握手
Matrix vodozemac 0.1 2022 libolm 的 Rust 重写 · LA 审计
Signal PQXDH 2023-09 X3DH ⊕ Kyber-1024 hybrid
Signal SPQR / Triple Ratchet 2025-10 DR + sparse ML-KEM · iOS+Android ship
Matrix vodozemac 0.10.0 2026-05 仍纯 X25519 · 无 PQ roadmap 公告

📚 References