如何保证 NPU 密码算子算得对?拆解 CANN Crypto 的 KAT 与差分测试机制
【免费下载链接】cryptocrypto SIG 是密码学兴趣小组,围绕昇腾 NPU 打造高性能密码软件库,提供丰富的密码算子与算法实现项目地址: https://gitcode.com/cann/crypto
CANN Crypto 是 crypto SIG 围绕昇腾 NPU 打造的高性能密码软件库,提供 SHAKE、Keccak、AES 等丰富的密码算子实现。NPU 密码算子从代码提交到芯片执行要经过多层编译,任何一个环节出错都会让密文"差一毫、谬以千里"。这篇文章拆解 CANN Crypto 的测试体系,讲清楚它是如何用KAT 已知答案测试与差分测试层层设卡,保证 NPU 密码算子算得对。🔐
为什么 NPU 密码算子容易"算错"?
一个昇腾密码算子的完整链路大致是:
- op_host 层:InferShape 推断输出形状,Tiling 计算分核、workspace 等配置;
- op_kernel 层:Ascend C 代码被设备编译器编译成指令;
- 设备执行:在 NPU 上跑真实的向量/矩阵指令。
每一层都可能出问题:Tiling 边界算错、置换轮常量写错、微块尾部处理漏字节……所以 CANN Crypto 的策略是每一层都有独立的参考实现兜底——参考实现仅参与测试,不链接到生产算子包,天然杜绝"用被测代码自证清白"。🎯
相关测试说明可直接查看 tests/README.md。
第一道防线:KAT 已知答案测试
KAT(Known Answer Test,已知答案测试)的朴素原理是:给定一组固定输入,答案在算法标准里是确定的,跑一遍对不上就是错。
空消息黄金向量
以 SHAKE 为例,空消息的 SHAKE128/SHAKE256 前 16 字节在标准中是固定值。golden.py 用 Python 标准库hashlib独立计算参考结果,并把黄金向量硬编码进用例——7f9c2ba4e88f827d616045507605853e(SHAKE128)和46b9dd2b0ba88d13233b3feb743eeb24(SHAKE256)。这种"答案写死在测试里"的用例一旦回归,问题肉眼可见。
全零初态置换 KAT
Keccak-f[1600] 的核心是 24 轮置换。golden.py 用独立的 Python 置换实现(含完整的轮常量表与 RHO 旋转偏移)对全零初态做 KAT:24 轮后第 0 道必须等于f1258f7940e1dde7。这是 Keccak 规范中最经典的自检向量,等价于"给置换引擎做一次出厂标定"。
第二道防线:差分测试(Differential Testing)
KAT 只能覆盖有限用例,而密码算法要面对任意长度的消息。差分测试的思路是:两套完全独立的实现,喂同样的输入,输出必须逐字节一致。
纯 C 参考 vs OpenSSL 差分
test_reference_diff.cpp 是典型的差分测试:仓库内置的纯 C 参考实现(CannCryptoRefShake)与 OpenSSL 的EVP_shake128/EVP_shake256分别计算同一输入,逐字节比较。
它的用例设计值得新手学习——长度专门踩边界:
- 输入长度:
0、1、63、64、rate-1、rate、rate+1、2*rate-1、2*rate、2*rate+1(SHAKE128 的 rate 是 168,SHAKE256 是 136); - 输出长度:
1、31、32、64、rate-1、rate、rate+1、2*rate+3。
这些"±1"长度正是吸收/挤出跨块、微块尾部出错的重灾区。任意一组不一致即打印differential mismatch并失败。🧪
NPU 设备结果 vs 独立参考差分
真正跑在昇腾 NPU 上的结果也要做差分。test_aclnn_sha3.cpp 包含 5 个 Keccak 用例和 36 个 SHAKE 用例:Keccak 对比纯 C 参考,SHAKE 对比 OpenSSL EVP 独立参考,在真机上逐字节比较,覆盖空消息、rate 前后边界、多块吸收、多块输出及尾组。Keccak 的设备测试则把 batch 压到 64、256、1024、4096、8192 批量级(见 tests/README.md)。
第三道防线:Kernel UT,不用 NPU 也能验证
设备测试需要真机,而日常开发迭代等不起。CANN Crypto 提供了基于tikicpulib的 CPU 调试模式:
- test_shake_tensor.cpp 用 GTest 在 CPU 上执行真实 SHAKE Kernel,覆盖空消息、微块尾部、跨 rate 吸收和多块输出,期望结果由独立纯 C 参考生成;
- Keccak 侧的 test_keccak_f1600_tensor.cpp 通过
ICPU_RUN_KF调用实际 Kernel 与纯 C 参考比较,test_permutation.cpp 则对生产置换头做 Host 算术模型校验(928 个状态),甚至细化到 XorSlot 在"输出与输入槽位重合"时的边界行为。
这套 Kernel UT 默认关闭(-DCANN_BUILD_CPU_DEBUG_TESTS=ON启用),不需要 NPU,每个程序默认限时 1800 秒——让开发者在 CI 排队跑真机之前,先在本地把 90% 的错误拦下来。⚡
第四道防线:压力测试与 CI 门禁
百次重复跑压力测试
正确性问题有时是概率性的(竞争、脏数据)。stress.py 连续启动 100 次独立进程,每次运行两个算法的 4 个代表形状,任一进程失败或超时立即终止,全部日志落盘到results/stress/便于复盘。
CI 分层门禁
CI 按算子层级分门禁(见 scripts/ci/ut.sh):ophost、opapi、opgraph、opkernel四层 UT 各管一段——op_host 层检查算法选择、尺寸校验、Tiling 结构与 workspace;opkernel 层在 smoke.sh 中于带昇腾 NPU 的容器里编译全部组件并实跑设备用例。PR 只改文档时自动跳过,避免无谓消耗算力。
NTT 基线回归
后量子密码相关的 NTT 模块还有一层数据回归:acceptance_baselines.tsv 记录了 ML-KEM、ML-DSA 的验收基线(含来源版本指纹),compare_data.py 和 compare_pqc_data.py 负责与基线及外部参考数据逐组比对,防止内核改动悄悄改变结果。
小结:四层防线一张图
| 层级 | 手段 | 特点 |
|---|---|---|
| KAT 已知答案 | 空消息黄金向量、全零初态置换 | 答案硬编码,回归一目了然 |
| 差分测试 | 纯 C 参考 vs OpenSSL vs NPU 设备结果 | 双独立实现逐字节比较,专打边界长度 |
| Kernel UT | tikicpulib CPU 实跑 Kernel | 无需 NPU,本地快速拦截 |
| 压力 + CI | 100 次独立进程、四层 UT 门禁、NTT 基线 | 概率性缺陷与持续回归双保险 |
核心思想一句话:永远用独立实现验证生产实现,把边界长度当作用例设计的起点。这套 KAT + 差分 + 真机 + 压测的组合拳,就是 CANN Crypto 让 NPU 密码算子"算得对、敢发布"的底气。✅
更多细节可查阅 SHAKE 测试说明 与 Keccak 测试说明。
【免费下载链接】cryptocrypto SIG 是密码学兴趣小组,围绕昇腾 NPU 打造高性能密码软件库,提供丰富的密码算子与算法实现项目地址: https://gitcode.com/cann/crypto
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考