本文是一份详细的技术分析,涵盖数学原理、算法流程、源码实现和常见问题。基于NIST FIPS 204标准和libcrux项目的最新实现。
导读:ML-DSA 是什么?
ML-DSA(Module-Lattice-Based Digital Signature Standard,模格密码学数字签名标准)是由美国国家标准与技术研究院(NIST)在2024年正式发布的后量子密码学签名方案。
核心特性
| 特性 | 说明 |
|---|---|
| 用途 | 数字签名(身份认证、消息完整性),非加密 |
| 安全基础 | 基于格密码学的MLW(E)和MSIS难题 |
| 抗量子性 | 对量子计算机具有抗性 |
| 标准化 | NIST FIPS 204正式标准 |
| 三个安全等级 | ML-DSA-44(轻量)、ML-DSA-65(中等)、ML-DSA-87(强) |
区别与联系
在libcrux项目中:
- ML-DSA:数字签名(FIPS 204)✍️
- ML-KEM:密钥交换/封装(FIPS 203)🔑
- 两者配合:完整的后量子密码学解决方案
背景知识:为什么需要 ML-DSA?
量子威胁
传统密码学算法 ────────────────────► 对经典计算机:安全
└─► 对量子计算机:不安全 ❌
RSA、ECDSA 面临的威胁:
- Shor算法可以在多项式时间内破解
- 量子计算机一旦成熟,现有的HTTPS、区块链等都会瓦解
格密码学的优势
格密码学基于最短向量问题(SVP)和学习误差问题(LWE),这些问题:
- 被认为对量子计算机也是困难的
- 具有较好的性能和尺寸特性
- 已有20年的学术研究积累
第一部分:数学基础
1. 多项式环
ML-DSA的所有运算都在一个特定的多项式环上进行:
参数解释:
- :整数模 的域,其中 (素数)
- :分圆多项式,定义了模多项式
- 元素表示:256个系数的多项式 ,其中
为什么选择这个环?
- NTT友好:存在高效的数论变换,使多项式乘法加速到
- 格的结构:完美对应于模格密码学的数学结构
- 安全性:充分的安全参数下,学习误差问题是困难的
2. 向量和矩阵
签名过程涉及矩阵-向量运算:
矩阵 (公开,从公钥中确定生成):
其中 和 是参数(根据安全等级不同)。
向量(都在 或 中):
- :列向量( 个多项式)
- :行向量( 个多项式)
3. 范数和约束
在拒绝采样中,需要检查向量的无穷范数:
其中 表示多项式 中系数的最大绝对值。
例子:如果 (两个多项式),则
第二部分:完整的算法流程
1. 密钥生成(Algorithm 2 in FIPS 204)
输入与输出
输入:ξ ∈ {0,1}^256 [32字节随机数]
输出:(pk, sk) [公钥与私钥]
详细步骤
第一步:种子展开
从单个32字节的种子生成三个独立的种子:
分割为:
其中:
- :32字节,用于生成矩阵
- :64字节,用于采样秘密向量
- :32字节,用于签名时的随机化
第二步:采样秘密向量
使用拒绝采样,从 生成:
- ,每个系数在 范围
- ,每个系数在 范围
其中 依赖于安全等级:
- ML-DSA-44:
- ML-DSA-65/87:
代码实现:samplex4::sample_s1_and_s2() 使用4路并行采样。
第三步:生成矩阵 并计算
矩阵 是确定性从 生成,每个元素通过拒绝采样得到。
计算:
其中乘法在NTT域进行(见下文):
第四步:分解 为 和
使用Power2Round函数:
其中:
- :高13位,用于公钥(压缩后10位)
- :低13位,用于私钥(13位)
数学上:
第五步:输出密钥
公钥:
私钥:
其中 是公钥的哈希。
2. 签名(Algorithm 3 in FIPS 204)
输入与输出
输入:sk [私钥]
M [消息]
ctx [上下文,最多255字节]
randomness [32字节随机数]
输出:σ [签名]
高层概览
签名采用拒绝采样方法:
- 采样掩码向量
- 计算中间值 ,提取其高位
- 计算承诺哈希
- 采样挑战多项式
- 计算响应向量
- 多次范数检查,如果失败则重新采样
- 计算Hint位修正舍入误差
- 序列化签名
详细步骤
初始化:消息预处理
将消息规范化为消息代表 :
其中:
- 代表 SHAKE256
- 域分离前缀确保不同上下文的签名不能混淆
掩码种子生成
用 生成掩码向量的种子,支持多次采样。
拒绝采样循环(最多814次迭代)
attempt ← 0
while attempt < 814 do
┌─ 采样
├─ 检验
├─ 继续或拒绝
└─ attempt++
循环内第一步:采样掩码向量
从 采样:
其中 依赖安全等级:
- ML-DSA-44:
- ML-DSA-65/87:
循环内第二步:计算
在NTT域实现:
循环内第三步:分解 为 和
使用Decompose函数:
其中 , 依赖安全等级:
- ML-DSA-44:
- ML-DSA-65/87:
具体计算:
循环内第四步:计算承诺哈希
输出32字节的哈希值。这是签名中公开的部分(后续验证会重新计算)。
循环内第五步:采样挑战多项式
从 确定性采样多项式 :
特性: 的256个系数中,恰好有 个为 ,其余为 0。
- ML-DSA-44:
- ML-DSA-65:
- ML-DSA-87:
循环内第六步:计算响应向量
其中 在NTT域计算:
循环内第七步:范数检查1 - 响应向量范围
检查响应向量的无穷范数:
其中 :
- ML-DSA-44:
- ML-DSA-65:
- ML-DSA-87:
如果失败,拒绝此次迭代,重新采样 。原因:确保签名与秘密密钥解耦,防止侧信道攻击。
循环内第八步:计算低位 和范数检查2
检查:
如果失败,拒绝此次迭代。
循环内第九步:检查高位和范数检查3
计算:
检查:
如果失败,拒绝此次迭代。
循环内第十步:计算Hint
计算Hint向量以修正舍入误差:
其中MakeHint的作用是:在验证时,使用 能够从近似的 恢复出正确的 。
检查Hint的1个数:
其中 是最大允许Hint个数:
- ML-DSA-44:
- ML-DSA-65:
- ML-DSA-87:
如果所有检查通过,签名生成成功,退出循环。
输出签名
序列化后的大小见下一章节。
3. 验证(Algorithm 4 in FIPS 204)
输入与输出
输入:pk [公钥]
M [消息]
ctx [上下文]
σ [签名]
输出:True/False [签名有效性]
验证流程
第一步:反序列化签名
从签名 提取:
第二步:快速范数检查
验证 在合法范围内:
如果失败,直接拒绝(不需要复杂计算)。
第三步:重建消息代表
与签名时相同。
第四步:从签名中恢复挑战
从 采样:
第五步:重构 的近似值
计算:
在NTT域实现:
关键观察:
签名时:,所以
所以 应该接近原签名中的 。
第六步:使用Hint修正高位
应用Hint进行舍入修正:
其中UseHint函数利用Hint向量来修正由于舍入导致的误差。
第七步:重新计算承诺哈希
第八步:比较哈希
如果相等,签名有效;否则签名无效。
第三部分:参数对比与性能
参数表
| 参数 | ML-DSA-44 | ML-DSA-65 | ML-DSA-87 |
|---|---|---|---|
| 矩阵维度 | |||
| 秘密范围 | 2 | 4 | 4 |
| 掩码范围 | |||
| 分解参数 | 95,232 | 261,888 | 261,888 |
| 挑战密度 | 39 | 49 | 60 |
| 最大Hint个数 | 80 | 55 | 75 |
密钥与签名大小
密钥大小计算
公钥:
( 按10比特编码,256个系数需 字节/多项式)
| 参数集 | 公钥大小 |
|---|---|
| ML-DSA-44 | 32 + 4×320 = 1,312 字节 |
| ML-DSA-65 | 32 + 6×320 = 1,952 字节 |
| ML-DSA-87 | 32 + 8×320 = 2,592 字节 |
私钥:
| 参数集 | 私钥大小 |
|---|---|
| ML-DSA-44 | 32 + 32 + 64 + (4+4)×96 + 4×416 = 2,560 字节 |
| ML-DSA-65 | 32 + 32 + 64 + (6+5)×128 + 6×416 = 4,880 字节 |
| ML-DSA-87 | 32 + 32 + 64 + (8+7)×96 + 8×416 = 4,896 字节 |
签名大小计算
签名 = 承诺哈希 + 响应向量 + Hint
其中
| 参数集 | 承诺哈希 | 响应向量 | Hint | 总大小 |
|---|---|---|---|---|
| ML-DSA-44 | 32 | 4×576 = 2,304 | 80+4 = 84 | 2,420 字节 |
| ML-DSA-65 | 48 | 5×640 = 3,200 | 55+6 = 61 | 3,309 字节 |
| ML-DSA-87 | 64 | 7×640 = 4,480 | 75+8 = 83 | 4,627 字节 |
与传统方案对比
| 方案 | 签名大小 | 公钥大小 | 抗量子 | 硬件友好 |
|---|---|---|---|---|
| ECDSA P-256 | 64 字节 | 65 字节 | ❌ | ✅ |
| RSA-2048 | 256 字节 | 294 字节 | ❌ | ✅ |
| ML-DSA-44 | 2,420 字节 | 1,312 字节 | ✅ | ✅ |
| ML-DSA-65 | 3,309 字节 | 1,952 字节 | ✅ | ✅ |
| ML-DSA-87 | 4,627 字节 | 2,592 字节 | ✅ | ✅ |
第四部分:NTT优化详解
为什么需要NTT?
多项式乘法的朴素方法需要 的时间复杂度。但在签名过程中需要大量矩阵-向量乘法,每个需要多个多项式乘法:
每个 都是一个多项式乘法。
数论变换(NTT)
NTT是FFT在有限域上的推广:
其中 是在NTT域的值。
关键性质:
其中 表示逐系数相乘,这个操作是 !
步骤:
- 将 变换到NTT域:
- 逐系数相乘:
- 逆变换回原域:
总时间复杂度:,比 快一个数量级。
libcrux中的实现
在ntt.rs中:
pub(crate) fn ntt_multiply_montgomery(lhs, rhs) {
// lhs 和 rhs 都已在NTT域
// 逐SIMD单元进行蒙哥马利乘法
for i in 0..lhs.simd_units.len() {
SIMDUnit::montgomery_multiply(&mut lhs[i], &rhs[i]);
}
}
蒙哥马利乘法是一种避免昂贵除法的技巧,通过预计算常数,使模运算只需移位和加法。
第五部分:libcrux 源码实现分析
项目结构
libcrux-ml-dsa/src/
│
├── lib.rs # 公共接口入口
├── types.rs # MLDSASigningKey, MLDSASignature 等
├── constants.rs # 参数常量
│
├── ml_dsa_44/65/87.rs # 三个安全等级的公共API
├── ml_dsa_generic.rs # 参数化泛型算法实现
│
├── 数学基础
│ ├── polynomial.rs # PolynomialRingElement 多项式
│ ├── ntt.rs # NTT变换
│ ├── arithmetic.rs # 字段算术
│ └── matrix.rs # 矩阵运算
│
├── 采样和哈希
│ ├── sample.rs # 拒绝采样矩阵A
│ ├── samplex4.rs # 4路并行采样s1, s2
│ └── hash_functions.rs # SHAKE256/128
│
├── 编码
│ ├── encoding/ # 各部分编码模块
│ └── encoding.rs # 入口
│
└── SIMD优化
└── simd/ # AVX2/Neon/便携式实现
关键设计模式
1. 参数化设计
使用Rust宏:
#[libcrux_macros::ml_dsa_parameter_sets(44, 65, 87)]
pub(crate) mod generic {
// ROWS_IN_A, COLUMNS_IN_A 等参数自动注入
// 编译时为44/65/87生成专用代码
}
2. SIMD 抽象
定义统一的Operations trait:
pub trait Operations {
fn ntt(&mut [...]);
fn montgomery_multiply(...);
fn rejection_sample_less_than_field_modulus(...);
// ...
}
两个实现:
PortableSIMDUnit:单个i32,可移植到任何平台AVX2SIMDUnit:4个i32并行,提供4倍性能提升
3. 拒绝采样循环
在ml_dsa_generic.rs L229-L401中实现,核心是:
while attempt < REJECTION_SAMPLE_BOUND_SIGN {
// 采样
// 范数检查(三层)
// 计算Hint
// 全部通过则设置返回值并退出
}
为了兼容Eurydice(形式化验证工具),使用Option<T>而非early return。
形式化验证
- NTT变换的正确性
- 字段算术运算的正确性
- 序列化/反序列化的正确性
这确保了实现没有数学错误,不仅仅是没有bug。
第六部分:常见问题(FAQ)
Q1:ML-DSA 是用来加密的吗?
A:不是。 ML-DSA 是数字签名方案,用于:
- ✍️ 对消息进行签名(证明你是签名者)
- ✔️ 让别人验证签名的有效性(确认签名者身份)
- 🔒 保证消息完整性(消息未被篡改)
对比:
- 加密:明文 → 密文 → 明文(只有持有密钥的人能解密)
- 签名:消息 → 签名(任何人都能验证)
如果要建立加密通道,应使用 ML-KEM(密钥交换)+ AES(对称加密)。
Q2:为什么 libcrux 项目同时有 ML-DSA 和 ML-KEM?
A: 它们配合使用完整的密码学方案:
TLS 握手过程:
├─ 密钥交换(ML-KEM):客户端和服务器建立共享密钥
├─ 身份认证(ML-DSA):服务器用私钥签名握手信息,客户端用公钥验证
└─ 数据加密:使用共享密钥和AES等对称算法
结果:后量子安全的端到端加密
Q3:ML-DSA 的签名为什么这么大(2420+ 字节)?
A: 这是格密码学的必然代价:
响应向量 (占比最大):
- 响应向量 有 个多项式(4~7个)
- 每个多项式 256 个系数
- 每个系数 18~20 比特
- 总计 4×576 ~ 7×640 = 2304~4480 字节
为什么需要这么大?
- 格密码学基于SVP问题,最短向量很难找
- 要达到与RSA-2048等同的安全强度,需要更大的维数和系数
- 因此,为了抗量子,必须付出签名大小的代价
为什么不能缩小?
- 缩小响应向量 = 降低安全强度
- 对量子计算机的抗性也会下降
Q4:拒绝采样为什么要循环最多 814 次?
A: 这是安全性与性能的平衡:
NIST 设计参数使得:
这意味着:
- 平均失败很少(期望失败次数约 1.5 次)
- 失败 814 次的概率低于 (与AES-256的安全强度等同)
- 用 814 这个数字是为了 FIPS 204 标准化
参考:FIPS 204 Appendix C
Q5:Hash-ML-DSA 和 ML-DSA 有什么区别?
A: libcrux 支持两种预哈希模式:
// 模式1:标准 ML-DSA(无预哈希)
ml_dsa_65::sign(sk, message, context, randomness)
// 模式2:HashML-DSA(带预哈希)
ml_dsa_65::sign_pre_hashed(sk, message, context,
pre_hash_buffer, randomness)
区别:
| 特性 | 标准 | 预哈希 |
|---|---|---|
| 消息大小 | 无限制 | 任意 |
| 预处理 | SHAKE256直接处理 | SHA256先哈希,再签名 |
| 用途 | 一般签名 | 与现有系统兼容 |
| 时间 | 消息大时可能慢 | 始终快(固定大小输入) |
在代码中:
fn sign_pre_hashed_mut(...) {
PH::hash::<Shake128>(message, pre_hash_buffer);
// 对pre_hash_buffer(固定大小)进行签名
}
Q6:为什么NTT这么重要?
A: 没有NTT,ML-DSA 会慢100倍:
多项式乘法耗时:
朴素方法(逐系数相乘累加):
a(x) × b(x) = Σ(a[i] * b[j]) x^(i+j)
时间复杂度:O(n²) = O(256²) = 65,536 次乘法
NTT方法:
1. NTT(a):O(n log n) = O(256 × 8) 计算
2. NTT(b):O(n log n)
3. 逐系数乘:O(n) = 256 次乘法 ← 关键优化!
4. iNTT:O(n log n)
总计:O(n log n) = 约 2000 次乘法
加速比:65,536 / 2000 ≈ 30 倍
在签名中,需要计算 ( 次多项式乘法),NTT的加速能从几秒降到几毫秒。
Q7:Hint是什么,为什么需要它?
A: Hint 是一个舍入修正向量,解决验证中的舍入误差:
问题:
不是精确等于 ,因为 是 的压缩版本(只存储高13位)。
解决方案: 在签名时,计算 Hint 记录哪些位需要修正:
h = MakeHint(-c*t0, w - c*s2 + c*t0)
验证时,利用 Hint 修正:
w1' = HighBits(UseHint(h, w'), 2*gamma2)
效果: 即使只有近似的 ,用 Hint 也能恢复出正确的 。
代价: 个额外字节的签名(ML-DSA-44为80字节,ML-DSA-65为55字节)。
Q8:libcrux 的优势是什么?
A: 相比参考实现:
形式化验证
- 关键部分用F*证明正确性
- 不仅找bug,还证明没有bug
SIMD优化
- AVX2版本快4倍
- 通过trait抽象,代码可移植
常时性设计
- 防止时序攻击
- ct_classify/ct_declassify标记秘密/公开数据
参数化
- 一份代码支持44/65/87三个安全等级
- 编译时代码生成,零运行时开销
无标准库
- 支持嵌入式系统(IoT、硬件钱包)
- #[no_std] 属性
总结:为什么选择 ML-DSA?
| 维度 | 传统RSA/ECDSA | ML-DSA | 结论 |
|---|---|---|---|
| 抗量子 | ❌ | ✅ | 必须迁移 |
| 签名大小 | 64~256B | 2420~4627B | 💾 代价较大 |
| 性能 | 毫秒级 | 毫秒~秒 | 🟡 可接受 |
| 标准化 | ✅ 成熟 | ✅ FIPS204 | ✅ 有保障 |
| 硬件支持 | ✅ 广泛 | 🟡 新增 | 📈 不断完善 |
适用场景:
- ✅ 政府、金融、医疗等需要长期安全的系统
- ✅ 区块链、证书链
- ✅ 需要未来十年可信的签名
- ❌ 对签名大小敏感的场景(但通常可以压缩传输)
参考资源
- 官方标准:NIST FIPS 204
- 开源实现:libcrux 项目
- 学术论文:Dilithium原始论文
- 形式化验证:hax 项目、F* 语言
作者注:本文基于对libcrux项目源码的深度分析和FIPS 204标准的理解。所有数学公式、算法步骤和参数都经过源码验证。
最后更新:2026年6月1日
没有评论:
发表评论