sm2_z256_loong64.S 代码分析 - emmansun/gmsm GitHub Wiki

代码来源

代码来自GmSSL的一个未合并的PR:https://github.com/DengJianbo-loongson/GmSSL/blob/2497946ac6458ae1fb6931b66804dbc62cfffe44/src/sm2_z256_loong64.S

函数说明

__sm2_z256_modp_add

实现(t0, t1, t2, t3) = (t0, t1, t2, t3) + (a4, a5, a6, a7),每个代表64位整数,从低到高排列,也就是说t0a4是最低64位数,也就是多字(Multi-Word)加法。

总体实现思路

  1. 在RISC(如龙芯/LoongArch、MIPS)架构中,没有x86那样的进位标识寄存器,所以进位都要用sltu (无符号小于)等指令手动计算。容易出错!
  2. 使用加1来实现mod P,这里的1为 $2^{256} - P$ 。
  3. 使用or合并进位(carry)。
  4. 使用masknezmaskeqz来选择最终结果。

__sm2_z256_modp_dbl

实现(t0, t1, t2, t3) = (t0, t1, t2, t3)*2,double运算。

总体实现思路

  1. 使用移位操作:srli.dsrli.d和算术运算alsl.d实现double运算。alsl.d $t3, $t3, $t5, 1表示t3 = t3<<1 + t5
  2. 其它和__sm2_z256_modp_add实现一致。

https://github.com/DengJianbo-loongson/GmSSL/blob/2497946ac6458ae1fb6931b66804dbc62cfffe44/src/sm2_z256_loong64.S#L94

	add.d	$t5, $t1, $t8	//no carry
	add.d	$t5, $t5, $a1

改为

	add.d	$t5, $a1, $t8	//no carry
	add.d	$t5, $t5, $t1

改完后的 //no carry 注释才成立 (这里t8的值为0x00000000ffffffff,a1为上一个字加法进位)。

https://github.com/DengJianbo-loongson/GmSSL/blob/2497946ac6458ae1fb6931b66804dbc62cfffe44/src/sm2_z256_loong64.S#L104

add.d	$a2, $a2, $a1

改为

or	$a2, $a2, $a1

也可以,其实我们只关注a2的值是0还是非0。

__sm2_z256_modp_sub

计算 (t0, t1, t2, t3) = (t0, t1, t2, t3) - (a4, a5, a6, a7)

总体实现思路

  1. 和加法一样,在RISC(如龙芯/LoongArch、MIPS)架构中,没有x86那样的借位标识寄存器,所以借位都要用sltu (无符号小于)等指令手动计算。
  2. 如果最后一个字的减法有借位,则再减1来实现mod P,这里的1为 $2^{256} - P$ ;否则再减的就是0。
  3. 使用maskeqz来选择减数是0还是1

__sm2_z256_modp_neg_sub

计算(t0, t1, t2, t3) = (t0, t1, t2, t3) - (a4, a5, a6, a7)

和上面方法类似。

__sm2_z256_modp_haf

计算 (t0, t1, t2, t3) = (t0, t1, t2, t3) / 2

总体实现思路

  1. 如果该数是奇数,则先减1,这里的1为 $2^{256} - P$ ,相当于先加P;否则先减的就是0。
  2. 通过andi $a2, $t0, 1取最低位值。
  3. 使用maskeqz来选择减数是0还是1
  4. 通过srli.dbstrins.d进行除2操作。bstrins.d $t0, $t1, 63, 63表示t0[63] = t1[0]t0的其它位值保持不变。

__sm2_z256_modp_mont_mul

按字蒙哥马利模约减乘法(WW-MM),计算 (t0,t1,t2,t3) = (a4,a5,a6,a7) * (t0,t1,t2,t3)

实现思路

  1. 可参考https://github.com/emmansun/gmsm/wiki/SM2-WWMM-(2)
    $T_2=T_1 \ast P=t_0 \ast P= t_0 \ast (2^{256}-(2^{32} \ast 2^{192} + 0 \ast 2^{128} + (2^{32} - 1) \ast 2^{64} + 1))$
    $T_2=(t_0-t_0>>32) \ast 2^{256}+(0 - t_0<<32) \ast 2^{192} + (0 - t_0>>32) \ast 2^{128} + (t_0 - t_0<<32) \ast 2^{64} - t_0$
    $T_3=T + T_2=(t_4+t_0-t_0>>32) \ast 2^{256}+(t_3 - t_0<<32) \ast 2^{192} + (t_2 - t_0>>32) \ast 2^{128} + (t_1 + t_0 - t_0<<32) \ast 2^{64} $
    注释:这里 $t_0<<32$ 是 $t_0 \ast 2^{32}$ 的低64位, $t_0>>32$ 是 $t_0 \ast 2^{32}$ 的高64位。 它这里先计算T的系数(WORD),再计算 $T_2$ 的系数。
  2. 几个no carry判断的准确性:
    1. https://github.com/DengJianbo-loongson/GmSSL/blob/2497946ac6458ae1fb6931b66804dbc62cfffe44/src/sm2_z256_loong64.S#L268 add.d $t5, $a1, $t5,这里$a1是可能的进位,这一点是不一定成立的。但是,如果把它和261行add.d $t5, $t5, $s2交换一下顺序,则成立,因为两个64位字的乘法产生的高64位字,不可能是 $2^{64}-1$ 。所以如果不交换顺序,结果是否正确,需要进一步证明,并没那么直观。
    2. https://github.com/DengJianbo-loongson/GmSSL/blob/2497946ac6458ae1fb6931b66804dbc62cfffe44/src/sm2_z256_loong64.S#L273 这里是成立的。
  3. 约简还没完成,也就是上一步 $T_2 + T$ 还没算完,就开始了下一个字的乘法,代码可读性降低,为了性能?
  4. 最后,依然使用加1来实现mod P,这里的1为 $2^{256} - P$ 。

__sm2_z256_modp_mont_sqr

计算 (t0, t1, t2, t3) = (t0, t1, t2, t3)^2

总体实现思路

  1. 先完整计算出平方结果,用8个64位字表示,算完后再进行蒙哥马利约简计算。
  2. 最后,依然使用加1来实现mod P,这里的1为 $2^{256} - P$ 。

总结

  1. 进位、借位要格外小心、无进位判断要严谨。
  2. 使用加(或者减)1来实现mod P,这里的1为 $2^{256} - P$ 。
  3. 使用masknez和(或)maskeqz来选择最终结果。