3. 数学基础(下):点加与倍点运算、标量乘法与快速幂算法、射影坐标系优化

好,咱们接着聊 SM2 的数学基础。上一章我们把椭圆曲线上的点、群、阶这些概念理清了。这一章,我们要动真格的了——怎么在计算机里真正算起来。

说白了,SM2 的所有密码操作,最终都归结到一件事:标量乘法。也就是给定一个整数 k 和一个点 P,算出 kP。听起来简单,但这里面坑不少。我当年第一次实现 SM2 时,就因为在点加运算上没处理好,导致签名验证死活过不去……嗯,后来才发现是坐标系统的问题。

3.1 点加与倍点:椭圆曲线的“原子操作”

在椭圆曲线上,两个点相加有一套几何规则。你想想看,给定曲线上两点 P 和 Q,连接它们的直线与曲线交于第三点 R',然后关于 x 轴对称得到 R,这就是 P+Q 的结果。

但计算机不认几何,只认代数。所以我们要把几何规则翻译成代数公式。

3.1.1 仿射坐标系下的点加公式

对于 SM2 使用的曲线 y² = x³ + ax + b (mod p),设 P = (x₁, y₁),Q = (x₂, y₂),且 P ≠ ±Q:

λ = (y₂ - y₁) / (x₂ - x₁) mod p
x₃ = λ² - x₁ - x₂ mod p
y₃ = λ * (x₁ - x₃) - y₁ mod p

这里有个关键点:除法是在有限域中进行的,实际上是乘以分母的模逆元。模逆元运算很慢,这是后面我们要优化的核心原因。

3.1.2 倍点公式

当 P = Q 时,就是倍点运算。几何意义是过 P 点作切线:

λ = (3x₁² + a) / (2y₁) mod p
x₃ = λ² - 2x₁ mod p
y₃ = λ * (x₁ - x₃) - y₁ mod p
我的经验: 注意 SM2 的曲线参数 a = p - 3,这个设计不是随意的。它让倍点公式中的 3x₁² + a 可以优化为 3(x₁² - 1) + p,减少一次大数运算。国密标准的设计者考虑得很周到。

3.2 标量乘法与快速幂:从 kP 到高效实现

现在问题来了:要算 kP,难道要一个一个点加?k 是 256 位的整数,最大约 2²⁵⁶,一个一个加,宇宙毁灭了都算不完。

这里就要用到快速幂算法,也叫“倍点-点加”法。它的核心思想很简单:把 k 写成二进制,然后从左到右扫描。

3.2.1 从左到右的二进制方法

输入:整数 k = (k_{n-1} ... k₁ k₀)₂,点 P
输出:kP

Q = 无穷远点 O
for i from n-1 down to 0:
    Q = 2Q          // 倍点
    if k_i == 1:
        Q = Q + P   // 点加
return Q

举个例子,k = 13 = 1101₂:

步骤 当前比特 操作 Q 的值
初始化 - Q = O O
1 1 Q = 2O = O; Q = O + P P
2 1 Q = 2P; Q = 2P + P 3P
3 0 Q = 2(3P) 6P
4 1 Q = 2(6P); Q = 12P + P 13P

你看,只用 4 次倍点和 3 次点加就算出了 13P。对于 256 位的 k,最多需要 256 次倍点和 256 次点加,效率提升了天文数字级别。

避坑指南: 我曾经在实现时忽略了一个细节——当 Q 是无穷远点时,倍点运算要特殊处理。如果直接套用倍点公式,会出现除零错误。一定要在代码里先判断 Q 是否为 O。

3.3 射影坐标系:为什么需要它?

好,现在算法效率上来了,但还有一个大问题:模逆运算。

你想想看,每次点加都要算一次模逆(求 λ 时的除法)。一次模逆的开销大约是模乘的 10-100 倍,具体取决于实现。256 位大数的模逆,用扩展欧几里得算法也要跑好几百次循环。

那怎么办?换个坐标系。

3.3.1 雅可比射影坐标

在雅可比射影坐标下,每个点用三个坐标表示 (X, Y, Z),对应仿射坐标:

x = X / Z²
y = Y / Z³

无穷远点 O 对应 Z = 0。

这样做的好处是:点加和倍点运算中,完全不需要模逆。所有运算只涉及模加、模减、模乘。只在最后需要输出结果时,才做一次模逆把射影坐标转回仿射坐标。

3.3.2 射影坐标下的点加公式

设 P = (X₁, Y₁, Z₁),Q = (X₂, Y₂, Z₂),且 P ≠ ±Q:

U₁ = X₁ * Z₂²
U₂ = X₂ * Z₁²
S₁ = Y₁ * Z₂³
S₂ = Y₂ * Z₁³
H = U₂ - U₁
R = S₂ - S₁

X₃ = R² - H³ - 2 * U₁ * H²
Y₃ = R * (U₁ * H² - X₃) - S₁ * H³
Z₃ = H * Z₁ * Z₂

看着复杂,但全是模乘,没有模逆。我算过一笔账:一次射影坐标点加大约需要 12 次模乘 + 4 次模平方,而仿射坐标需要 1 次模逆 + 若干模乘。当模逆比模乘慢 20 倍以上时,射影坐标就全面胜出了。

3.3.3 混合加法

实际实现中还有一个常用技巧:混合加法。如果一个点是用射影坐标,另一个点是用仿射坐标(Z=1),公式可以简化,省掉一些乘法。

我个人习惯在标量乘法的主循环里用混合加法:把累加器 Q 保持为射影坐标,而基点点 P 保持为仿射坐标。这样每次点加都能省 4-5 次模乘。

3.4 本章知识体系总览

下面这张图把本章的核心逻辑串起来了:

SM2 标量乘法核心流程与坐标系优化 输入:整数 k,点 P k 的二进制展开 (k_{n-1}...k₀)₂ 主循环:for i = n-1 down to 0 Q = 2Q(倍点); if k_i==1: Q = Q + P(点加) 坐标系选择:仿射坐标 vs 射影坐标(雅可比) 仿射坐标 (x, y) 每次点加/倍点需要 1 次模逆 模逆开销大,整体效率低 雅可比射影坐标 (X, Y, Z) 全程无模逆,仅模乘/模加 最后 1 次模逆转回仿射 输出:kP(仿射坐标) 混合 加法 优化

3.5 实战建议与性能对比

说了这么多,到底用哪种方案?我直接给结论:

方案 每次点加模逆次数 每次倍点模逆次数 256位标量乘法总模逆 推荐场景
纯仿射坐标 1 1 ~512 教学演示、资源极度受限
纯射影坐标 0 0 1(最后转换) 通用高性能实现
混合加法 0 0 1(最后转换) 推荐:SM2 标准实现

核心结论: 在实际的 SM2 工程实现中,几乎清一色使用雅可比射影坐标 + 混合加法。我参与过的几个国密项目,包括某银行的 CA 系统和某政务平台的签名服务器,底层都是这个方案。性能差距不是一点半点——同样的硬件,射影坐标方案比仿射坐标快 5-10 倍。