每个密码库都会说,自己处理密钥的路径是常量时间的。这句话写下来不花什么成本——问题基本就出在这儿:它是最容易断言、又最难佐证的那类说法。读者没有任何办法区分,哪个库是真的量过,哪个只是打算这么做。

所以当我要为 gm-crypto-rs(一套纯 Rust 的 SM2 / SM3 / SM4 SDK)交代这件事时,真正难的问题不是「怎么解释常量时间」。在意这件事的人早就懂了;不懂的人,三十秒内能找到比我写得更好的解释。难的是怎么把可信度(warrant)交出去——凭什么让一个带着疑心的读者,相信这一份具体的代码确实做到了这一点。

这需要两样东西,而两样都不是解释。

这个测试真的有牙齿吗?

这个库在 CI 里挂着一套 dudect-bencher harness:20 个目标,每个把输入分成两类,量出两组执行时间的分布,再把「两组差多少」压成一个 t 统计量。多数目标的门禁是 |tau| < 0.20。这道门禁怎么接线、每个 PR 要为它付出多少代价,写在另一篇

一个只会报「没发现泄漏」的检测器,单看这句话是没有信息量的。它坏掉的时候这么报,配错的时候这么报,压根没接上的时候也这么报。所以真正值得摆出来的,不是那 20 个通过的目标,而是那一个必须失败的目标:

fn negative_control(runner: &mut CtRunner, rng: &mut BenchRng) {
    let left = [0u8; 32];
    let mut right = [0u8; 32];
    right[0] = 1;
    for _ in 0..sample_count() {
        let (class, input) = if rng.random::<bool>() {
            (Class::Left, &left)
        } else {
            (Class::Right, &right)
        };
        runner.run_one(class, || leaky_function(input));
    }
}

leaky_function 直接按密钥的某个字节分支:一条路跑一千次空转循环,另一条立刻返回。它就是要漏,而且要漏得很张扬。这个目标的门禁跟其他所有目标反着来——要求 |tau| 大于 1.0——而且取的是多轮里的最小值,不是中位数,也就是说它每一轮都得响。哪一轮它安静了,就说明接线断了,那一轮其他所有的绿勾也就一起失去意义。负控制一旦不再报警,CI 直接失败。

这才是值得展示的东西。它把「我们的时序 harness 跑过了」换成了「我们的时序 harness 现在仍然确实能测到它要找的东西」。

你知道它证明不了什么吗?

有疑心的读者需要的第二样东西,是这句话的边界——而且得由说这话的人自己划出来。README 里是这么写的(原文为英文):

这套 harness 报告的是时序泄漏的检测事件它并不证明常量时间。|tau| 低,只意味着在给定的测量预算下没能检测到泄漏,而不是不存在泄漏。

这段话是直接从 dudect-bencher 自己的文档里搬过来的,原样留着,没有往软的方向改写。同一个文件里还有一张跟成熟方案的对比表,这个库自己那两行写的是:外部安全审计:无,以及生产环境记录:薄——2026 年才首次发布

主动交代自己证据的局限,罕见到本身就成了一种证据。一个读者在自己发现这条但书之前就先读到它,和在之后才读到,更新的信念是不一样的。

为什么这些是链接,而不是叙述

上面那两样东西,本质上都是位置:某个 benchmark 文件的第 167 行,README 里的一段话。它们全部的证据价值,都来自可核验——读者点进去,落在某个固定 tag 上的那一行,不需要信我一个字就能自己确认。

这份材料我最早试过做成自动生成的讲解视频——用的就是已发布那支视频背后的同一套引擎(那支视频讲的是另一个主题)。对这件事来说,视频是个错的媒介,而且理由不只适用于我这次的尝试:叙述会把一个可核验的事实,重新变回一个断言。视频可以说「有一个故意写漏的负控制,CI 要求它必须报警」——引擎的引用校验门甚至会在画出这句话之前,先按源码核对它。但真正到达观众手上的,是校验门的结论,不是证据本身:画面里的 verified @ commit 徽标,没法把第 167 行递到你手上。这个格式所做的一切——节奏、配音、动效——都花在让一个说法更容易被接受上,没有一分花在让它更容易被核验上。对一个信任问题来说,这是错的那根轴;而且越精致越糟:一个制作精良的断言仍然是断言,只是现在看起来比它挣到的更权威。

视频真正擅长的是给概念建立直觉——时序侧信道为什么存在,t 检验在做什么。它不擅长为一份具体代码的说法传递可信度。而常量时间这件事,几乎全是后者。

还有一条实务上的提醒,既然深链在这里是承重的:把链接钉在 tag 上,别钉在分支上。第 167 行在 v1.11.0 上永远是第 167 行;钉在 main 上,只要有人在它上面加一个目标就会漂掉。而一条失效的证据链接比没有更糟——它恰好在你请求别人信任你的那一刻,显得草率。

最后落地的那两条锚点,一共大约四十个词的 HTML 和两个 URL——timing_leaks.rs:167README.md:76。它们为这个说法做的事,比那个视频多;花的时间,还少一个下午。