以 vodozemac 0.10.0
源码为切入点,把 Matrix 端到端加密(Olm + Megolm)每个组件逐项摆到 Signal 旁边对照——
算法层异同、设计哲学分叉、最大的真实差距。
vodozemac 是 libolm(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):
密码学栈(截至 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 等价——实现层无差距。
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 里找
}
one_time_keys 删除。功能上 ≈ Signal 用 OPK_B 的 DH4 那一支。
所以"真正的差距"应该这样说:
DH(EK_A, OPK_B)),多一段熵进 KDF——这是额外的 defense in depth,不是 categorical 的安全提升Sig(IK_B, SPK_B) 写在 X3DH 算法内(Alice 必须验签才能进握手);Matrix 把 fallback key 的签名外挂在 /keys/upload 的 signed_curve25519: 字段——客户端实现忘了验签,Olm 协议本身不会报错从源码 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。
这里是 Matrix 和 Signal 理念差异最大的地方,不是"谁落后",是 trade-off。
Megolm 的核心机制:一个发送者一个 session,session key 通过 1:1 Olm 单独加密给每台设备。 新设备加入时——比如 Bob 用 iPad 登录——已经在 session 内的成员要给这台新设备补发所有现存 session key, Bob 才能解密之前已发的消息。这就是 Element 大群里启动慢的根本原因,也是支持加密备份的根本原因 (备份的就是这堆 Megolm session keys)。
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 一遍。
Matrix 是联邦架构:任何人可起一个 homeserver,多个 homeserver 通过 server-to-server 协议互联。 Signal 是中心化。这一层差异引入了 Signal 完全没有的威胁面。
Matrix 的跨设备验证分两层:
vodozemac 直接实现了 SAS(src/sas/),cross-signing 在 matrix-sdk 层。
前 4 节大部分差异是设计 trade-off。这一节才是真正的差距——而且差得明显。
Matrix 协议层面没有明显障碍阻止做 PQXDH 等价物——Olm 3DH 在 KDF 输入里再加一段 ML-KEM Encap 出来的 SS, 数学上完全成立。技术上可行,缺的是:
ml-kem crate 拉进依赖,新增 pq_prekeys 模块,扩展 SharedSecret 拼接顺序/keys/upload 增加 signed_kyber_pubkey: 类字段spqr flag)如果 matrix-org 优先级提上去,1 年内 ship 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"OLM_PQ_ROOT" 做 domain separationoptional bytes kyber_ciphertext = 5;预计代码量:300-500 行(参考 libsignal PQXDH 的实现规模)。不复杂。
| 阵营 | 版本 | 时间 | 关键变化 |
|---|---|---|---|
| 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 公告 |