在 STM32 平台接入 ML‑DSA 后量子签名算法时,很多开发人员会遇到一类迷惑现象:同一组消息、公钥、签名,开源密码库验签可以正常通过,移植到 ST X‑CUBE‑PQC 库却持续验签失败。 很多工程师第一反应会怀疑 ST 库存在私有数据格式,需要对公钥或者签名做特殊转换。但根据 NIST FIPS 204 标准以及 ST 官方文档,X‑CUBE‑PQC 完全遵循公开标准,不存在私有格式,问题根源大多来自 ML‑DSA 的 Context(ctx)参数处理不一致。
本文结合真实调试案例,讲清楚 Context 参数的标准定义、ST 库 Header 接口的设计逻辑、开发易错点、标准调用流程以及代码示例,解决跨库 ML‑DSA 签名验证不兼容问题。
资料获取:实战经验 | LAT1716 正确理解并应用PQC ML-DSA算法中的Context参数
1. ML‑DSA 标准中 Context 参数的作用
ML‑DSA 是 NIST 在 2024 年发布 FIPS 204 确立的后量子模块格数字签名算法,包含原生 ML‑DSA 与预哈希版本 HashML‑DSSA 两套接口,Context 是签名算法的标准输入参数,不是厂商扩展字段。
1.1 Context 核心用途:域分离(Domain Separation)
Context 的设计目的,是把 “这条签名归属哪一个业务场景、哪一套用途” 编码进待签名的数据内部。
签名与验证必须使用完全一致的 Context,否则验签直接失败。
标准给出两组算法原型:
- 普通 ML‑DSA 签名:
ML‑DSSA.Sign(sk, M, ctx) - 预哈希 HashML‑DSA 签名:
HashML‑DSA.Sign(sk, M, ctx, PH)
FIPS 204 标准明确两条约束:
- Context 最大长度 255 字节,允许传入空字符串;
- 如果签名时使用非空 Context,验签方必须拿到完全相同的 Context 值参与计算,否则验证无法通过。
很多开发者会拿 ECDSA 的使用习惯套用到 ML‑DSSA。ECDSA 不需要上下文参数,而 ML‑DSSA 哪怕不使用自定义业务上下文,也必须把空 Context 纳入算法输入流程,这是绝大多数跨库兼容 bug 的来源。
2. ST X‑CUBE‑PQC 库验证外部签名完整流程
当需要在 STM32 设备上,使用 X‑CUBE‑PQC 库校验外部开源库生成的 ML‑DSSA 签名,完整执行步骤一共四步,该流程同样适用于空 Context 场景。
- 准备输入素材:外部来源公钥、原始消息、签名数据、对应的 Context(允许为空);
- 初始化 CMOX/PQC 算法运行上下文;
- 调用
cmox_pqc_dsa_prepareHeader()生成标准 Header; - 将生成的 Header 传入
cmox_pqc_dsa_verify()执行验签。
重点:Header 是什么
不少开发者初次接触 ST API,误以为 Header 是 ST 私有的额外签名字段。实际并非如此。 按照 FIPS204 标准,消息不会直接裸数据进入哈希运算;标准要求把域分离标识与 Context 做前缀编码之后,再和消息一起参与密码运算。ST 把这个标准要求的前缀编码步骤,封装并且显式开放成cmox_pqc_dsa_prepareHeader()接口,输出数据即 Header,Header 完全对应标准定义,不属于 ST 私有扩展。
关键易错点:即使业务不使用任何上下文(Context 为空),也必须调用cmox_pqc_dsa_prepareHeader()生成空上下文对应的 Header,再送入验签函数,不能跳过该接口直接调用 verify。
正确空上下文调用代码片段
retval = cmox_pqc_dsa_prepareHeader(&Pqc_Ctx,
NULL,
0,
CMOX_PQC_DSA_STANDARD,
Header,
&Header_Length);
开发时需要规避两类错误写法:
- 无上下文场景,传递非 NULL 指针,但长度参数写 0;
- 直接跳过 prepareHeader,把原始消息直接送入
cmox_pqc_dsa_verify函数。
只要保证开源库与 X‑CUBE‑PQC 使用完全相同的 Context(包括空上下文),构造对应 Header,公钥、签名无需任何格式转换就可以完成跨库验签。
3. 开源库与 ST 库 API 差异,为什么容易踩坑
主流开源 ML‑DSSA 库对外提供两套 API,两套 API 对开发者的暴露程度不一样,很容易造成认知偏差:
- 简易接口:
crypto_sign_signature/crypto_sign_verify,默认使用空上下文,接口不对外暴露 ctx 参数,调用简单; - 完整接口:
crypto_sign_signature_ctx()/crypto_sign_verify_ctx(),显式传入 ctx 上下文参数。
大部分开发者优先接触简易接口,主观上形成认知:ML‑DSSA 验签只需要消息、公钥、签名三样输入,忽略标准强制的 ctx 输入环节。 而 ST X‑CUBE‑PQC 库没有隐藏前缀编码逻辑,把 Header 生成步骤独立成函数,需要开发者主动调用,两套库 API 形态不一致,就会出现 “开源验签成功,STM32 端验签失败” 现象。
| 实现库 | 空上下文使用方式 | 开发者体感 |
|---|---|---|
| 开源简易 API | 内部自动处理空 ctx,用户无感知 | 和 ECDSA 调用方式接近,隐藏上下文逻辑 |
| X‑CUBE‑PQC | 必须显式调用 prepareHeader,传入 NULL、长度 0 生成空上下文 Header | 步骤更多,容易被开发者省略 |
核心等价关系:开源库简易接口 = ST 库调用 prepareHeader 传入 NULL,0 长度得到 Header,再执行 verify。
4. 工程常见故障排查清单
针对 STM32H5 系列使用 X‑CUBE‑PQC 做 ML‑DSSA 验签,出现持续返回验证失败,按照下面顺序逐项排查:
- 确认签名生成端与验证端的 Context 字节内容、长度完全一致;没有使用 Context,两边都要使用空上下文。
- 空上下文场景,确认代码已经调用
cmox_pqc_dsa_prepareHeader(),入参为NULL、长度 0,拿到输出 Header,再调用 verify,没有跳过函数。 - 禁止空上下文时传入有效指针、置长度为 0,该调用属于非法参数。
- 确认公钥、签名、消息的二进制原始数据完整,没有字节截断、转码、字节序修改。
- 区分 ML‑DSSA 与 HashML‑DSSA,签名生成使用预哈希版本,验签端也要对应选择 HashML‑DSSA 模式。
5. 小结
- Context (ctx) 是 NIST FIPS204 标准规定的 ML‑DSSA 算法输入,用于域分离,不是 ST 库新增的私有参数;最大 255 字节,可以为空串,签名验签必须保持一致。
- X‑CUBE‑PQC 库的 Header 不是私有扩展字段,对应标准规定的前缀编码,无论上下文是否为空,都必须调用
cmox_pqc_dsa_prepareHeader()生成 Header。 - 跨开源库联调时,开源库简易 API 等价于 ST 库空上下文 Header 流程,公钥、签名本身不需要格式转换。
- 不能直接沿用 ECDSA 开发思维,忽略 ML‑DSSA 上下文输入逻辑,这是嵌入式后量子密码项目高频踩坑点。
6. 参考信息
- 技术文档编号:ST LAT1716,版本 V1.0,发布 2026‑09‑02
- 对应硬件平台:STM32H533 开发板
- 软件组件:X‑CUBE‑PQC 密码扩展包,CMOX 密码底层库
- 参考标准:NIST FIPS 204(2024)
7. FAQ
Q:我业务不需要域分离,是否就不需要处理 Context?
A:业务层面可以不传入自定义上下文,但算法层面仍然要处理空 Context,在 ST 库中必须调用 prepareHeader 生成空上下文 Header,不可省略步骤。
Q:验签失败,是否代表 ST 库存在私有格式,需要转换签名或者公钥?
A:根据 LAT1716 文档,X‑CUBE‑PQC 遵循 NIST 公开标准,没有私有格式。绝大多数情况为 Context/Header 处理不一致,不需要修改公钥、签名字节。
Q:Context 超过 255 字节会发生什么? A:依据 FIPS204 标准,Context 最大 255 字节,超出标准长度输入属于非法参数,算法执行会报错。
免责声明:本文全部内容基于 ST LAT1716 文档,实际开发以 ST 官方文档、固件包源码为准。
109