L 记事本

二进制扩展欧几里得算法(binary extended euclidean algorithm)

   #RSA #ECC #素数 #模逆元 

最近实现了下椭圆曲线Secp251k1,频繁用到的一个算法是求模逆元,也就是 $ax = 1 \mod p$ 这里,p是素数,我使用的前面所写的算法,把尾递归改成迭代。发现速度仍然比tomlib慢了10倍,内存也是大了很多,矩阵乘法局部变量太多,我猜测是这样。 这里,看了他们源码,实现的是一个叫做 binary extended euclidean algorithm 的算法, 在扩展欧几里得的基础上做了优化,不用每次做商求余,只需要每次右移。而移位操作的代价是很小的。

前文中 扩展欧几里得算法中所描述的,最终是要找到 $ax + by = 1$的等式。

二进制扩展欧几里得算法用到了下面几个事实 ,gcd 最大公约数

  1. a b 都是偶数,那么 gcd(a,b) = 2 gcd(a/2,b/2),
  2. a 是偶数,b是奇数,那么 gcd(a,b) = gcd(a/2,b)
  3. a b 都是奇数,那么 gcd(a,b) = gcd((a-b)/2,b)

根据这两个,我们可以不断的把 \(\begin{bmatrix} a\\ p \end{bmatrix} = A \begin{bmatrix} c\\ x \end{bmatrix}\) 这里,c 是最大公约数,本例中,p是素数,c = 1,而扩展欧几里得算法可以表示为一系列的线性变换。 这里以,求 12,13 的过程为例,所有运算都是在素数域13下,mod 13, 求2的逆元很简单,就是(13 + 1) / 2 = 7

a p 步骤 状态          备注
12 13 初始状态 | 1 ,0 |
| 0 ,1 |
 
6 13 a = a / 2 | 7 ,0 |
| 0 ,1 |
矩阵列第一列同时乘以2的逆元 ,这里是(13 + 1) / 2 = 7
3 13 a = a / 2 | 10 ,0|
| 0 ,1 |
矩阵列第一列同时乘以2的逆元7, 7 * 7 % 13 = 10
3 10 p = p - a | 10 ,4 |
| 0 ,1 |
第二列减去第一列, 0 - 10 % 13 = 3
3 5 p = p - a | 10 ,8 |
| 0 ,7 |
第2列除以2, 3 * 7 % 13 = 8
3 2 p = p - a | 10 ,11 |
| 0 ,7 |
第2列除以2, (8 -10) % 13 = 11
2 3 交换 a p | 11 ,10 |
| 7 ,0 |
交换 12 列
1 3 a = a/2 | 12 ,10 |
| 10 ,0 |
11 * 7 % 13 = 12

那么有

\[\begin{bmatrix}1\\3\end{bmatrix} = \begin{bmatrix}12&10\\10&0\end{bmatrix} \cdot \begin{bmatrix} 12\\13 \end{bmatrix} \mod 13\]

所以 12 模13 逆元是 12,也就是他本身。

可以看到,矩阵$A$只有第一行起到了作用,实现过程中,我们可以忽略第二行,只需要两个变量就能表示中间状态。js 代码如下

避免使用乘法

经过测试之后,发现如果 所有a/2 都写成乘以2的逆元,那么会运用到大量乘法操作,即使采用分治法,速度也不令人满意。 下面就运用一个技巧快速计算$a/2 \mod p$

  • 如果a是偶数,那么直接取a的一半 $a/2$
  • 如果a是奇数,$a = 2n + 1$ , 那么 $a/2 = (2n + 1) * i = 2 * i * n + i = n + i \mod p$ 这样,就不需要乘法操作,直接移位和加法就能快速的完成运算。

    i 是 2 的逆元, i = (p+1)/2, p 是奇数

 

function binaryExGcd(a,b){


    function div2(a,p,invers2){
        /***
         *   a/2 mod p ,
         *   如果 a 是偶数,那么直接 a/2
         *   如果 a 是奇数, 那么 a = 2n + 1
         *   a/2  =  a * I  =  2 * I * n + I = n + I 
         *   这里 I 是2 的逆元, I = (p + 1) / 2
         */
        if((a & 1n) == 0){
            return a>>1n;
        }else{
            const half = a >> 1n;
            return half + invers2
        }
    }


    if(a >= b){
        console.error("a must less than b")
        return [0,0]
    }
    if((b & 1n) == 0n){
        console.error(b, "is not odd number")
        return;
    }

    var invers2 = (b + 1n) >> 1n;
    var A = a , B = b 
    var u = 1n, v = 0n

    var gcd = 1n;
    while(true){
        console.log(A,B,u,v)
        if(A == 1n){
            break;
        }
        if(B == 0n){
            gcd *= A;
            return [gcd,u];
        }

        if (A > B){
            var c = A 
            A = B
            B = c 
            
            c = u 
            u = v 
            v = c 

            continue;
        }

        let isAOdd = (A & 1n) == 1n
        let isBOdd = (B & 1n) == 1n

        if(!isBOdd && ! isAOdd ){
            gcd <<= 1n;
        }
        
        if (isAOdd && isBOdd){
            B =  (B - A) >> 1n;
            v = (v - u) 
            v = div2(v,b,invers2);
            
            if(v < 0){
                v += b
            }
            
           
            


        }
        else if(!isAOdd){
            A >>= 1n;
            u = div2(u,b,invers2);
            if(u < 0){
                u += b
            }

        }
        else if(!isBOdd){
            B >>= 1n;
            v = div2(v,b,invers2);

            if(v < 0){
                v += b
            }
        }
    }
    u %= b 
    if(u < 0n){
        u += b
    }
    return [gcd,u];
}
 
var count  = 1;
while (count --)
{
    var a = BigInt(Math.ceil(Math.random() * 100000000))
    var b =  BigInt(Math.ceil(Math.random() * 100000000000))
    if( (b & 1n) == 0){
        b += 1n;
    }

    a = 311n 
    b = 997n

    var cc = binaryExGcd(a,b);
    var gcd = cc[0]
    var c = cc[1]
    if(gcd){
        console.log("result:",c ,gcd, (a * c )% b)
    }
    else{
    }
    
    if(gcd && (a * c )% b != gcd){
        console.log("error:",a,b,c ,gcd, (a * c )% b)
        break;
    }
}

module.exports = binaryExGcd


TODO

上面只处理了,p是奇数的情况,无法处理偶数的情况,(偶数,2没有逆元)