比特币源码分析--深入理解区块链15.椭圆曲线签名算法 ECDSA)

        在上一篇文章中详细介绍了椭圆曲线(EC)以及如何使用椭圆曲线的点乘法生成公钥。椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm,缩写作 ECDSA)是一种基于椭圆曲线密码学的非对称式密码学的数字签名算法。它有两个密钥,一个是公开的密钥,另一个是私有密钥;在我们常用的RSA非对称加密算法中,公钥和私钥都可用于加密或解密,如果公钥用于加密,则私钥用于解密;如果私钥用于加密,则公钥用于解密。但在比特币中,所有的区块数据和交易数据都不需要加密和解密,因为这些数据都是公开透明的,任何人都可以通过区块链网络查询和使用。使用ECDSA主要用于数字签名,它的作用是:由于私钥只由用户自己持有,故可以在比特币的交易转账中,签发交易必定出自于拥有私钥的用户;其他节点可以验证用户签发的交易数据是否有效、中途有否曾被篡改,节点接收者可信赖这些数据确实来自于私钥拥有者。对用户来说确保私钥的安全绝对不能泄露,如果私钥一旦泄露,那么就失去了该私钥对应的钱包地址一切。

椭圆曲线签名算法原理

在前面文章中介绍了公钥的计算公式,使用椭圆曲线有限域上的点乘法:

Q\ =\ d\times G

        注意这里的乘法运算是指椭圆曲线的点乘法,而不是一般数学意义上的乘法。当使用乘法符号时一定表示点乘法,有些场合不使用该符号就表示普通的数学乘法。通常与椭圆曲线上的点乘法运算时都表示的是点乘法。G是椭圆曲线的基点,d是私钥,Q是公钥。 

我们知道dG表示有d个G相加,根据加法结合律可得: 

dG\ +\ kG=(d\ +\ k)\ G 

我们在等式的左边dG乘以一个哈希值Hash(m,\ kG),可得: 

Hash(m,\ kG)dG\ +\ kG=(Hash(m,kG)d\ +\ k)G 

令: 

Q=dG 

可得出: 

Hash\left(m,kG\right)Q+kG=\left(Hash\left(m,kG\right)d+k\right)G 

令: 

K\ =kG \\s =\ Hash(m,\ K)d\ +\ k 

可以得出: 

Hash\left(m,K\right)Q+\ K=sG 

上面公式中的m表示是消息,在比特币中可表示交易数据。K是私钥为k的公钥;Hash(m, K)代表对m和K合并后计算哈希值;Q是私钥为d的公钥;G是基点。 

        所谓签名就是要让别人知道签名的数据一定是拥有私钥的人签发的,其他人拥有的公钥是可以验证的,在公式Hash\left(m,K\right)Q+\ K=sG 中,消息m是已知的,这里规定私钥k可随机生成。如果我们知道私钥d,通过d计算s就可以使上述公司成立。如果不知道d是没有办法使上述公式成立的,所以证明了只有拥有d才能计算出s。在实际应用中,m是要签名的数据,我们注意到除了用户的私钥d之外,还有每次签名随机生成的私钥k,如果每次签名都使用相同的k,或者选择一个没那么随机的k,根据上述计算s的公式可知,在用户私钥d固定的情况下,黑客有可能根据签名数据反推求得k,一旦k遭到泄露,用户的私钥d也可被推算出来。针对此, RFC6979 提案的就是针对 k 值选择改进提案,以确保私钥不会遭人破解。关于RFC6979的提案内容在RFC 6979 - Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA)可以看到。 

 签名计算过程

        假设Alice创建了一个密钥对,私钥是d_A,该私钥在区间\left[1,\ n-1\right]内随机选择,在比特币中,私钥可用随机数生成器生成,它必要具有很强的熵源以确保高安全。只要满足 1\le d_{A}\le(n-1)就行了。n就是前一篇文章中提到的基点G的数阶。因此它的公钥是:

 Q_A=d_A\times G

为了让 Alice 签署消息 m,遵循以下步骤: 

  1. 计算e\ =\ HASH(m), 这里的哈希算法使用HASH256。
  2. 令 z 是e 的L(n) 个最左边的位,其中L(n) 是群阶n. (注意 z 可以大于 n 但不能更长)。意思是z的值bit位数与群阶n的bit位一样长,其值可大于n。这里的n就是基点G的阶。它是256位的大整数,同样的SHA256计算的结果也是256位的大整数。在椭圆曲线Secp256k1中,

n = fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141

  1. 从区间 \left[1,\ n-1\right]中选择一个安全的随机整数k。比特币使用随机的算法来生成该数。
  2. 计算k对应的公钥K, 它返回:K_{(x,\ y)}=\ k\times G
  3. 计算r\ =\ x\ mod\ n\ ,如果r == 0, 返回第3步。
  4. 计算\ s=\ k^{-1}(z\ +\ rd_A)\ mod\ n, 如果s == 0, 返回第3步。
  5. 签名(r,s)和 (r, -s mod n)是有效的签名对。

        在上述公式中,不仅要求 k 是秘密的,而且为不同的签名选择不同的 k 也是至关重要的,否则步骤 6 中的方程可以解出d_A私钥:给定两个签名 (r,s) 和 (r,s') ,对不同的已知消息 m 和 m' 使用相同的未知 k,攻击者可以计算 z 和 z' ,并且由于s\ -\ s'\ =\ k^{-1}(z\ -\ z')(本段中的所有操作都是模n) 攻击者可以找到k=\ \frac{z\ -\ z'}{s\ -\ s'} 由于s\ =\ k^{-1}(z\ +\ rd_A),攻击者可以计算出私钥d_A\ =\ \frac{sk\ -\ z}{r}

签名验证 

        上述Alice使用了他的私钥对数据进行了签名,为了让他人(在比特币网络中其他节点)验证的签名,先假设Bob需对签名进行验证,他必须拥有Alice的公钥Q_A的副本。 Bob 可以验证Q_A是一个有效的曲线点,如下所示:

  1. 检查 Q_A不等于单位元素 O(无穷远点),否则其坐标无效 ;
  2. 检查Q_A是否位于曲线上,将Q_A代入曲线方程即可验证:

return (y * y - x * x * x - curve.a * x - curve.b) % curve.p == 0

  1. 检查 n\times Q_A=\ 0 

然后根据下列步骤来验证:

  1. 验证r和s在范围 [1, n-1]之间,否则签名无效
  2. 计算e = HASH(m)
  3. 确保z是e且与n阶的位数一致
  4. 计算u_1\ =zs^{-1}\ mod\ n\ and\ u_2=\ rs^{-1}\ mod\ n
  5. 计算(x,y)=u_1\times\ G\ +\ u_2\times\ Q_A\ \,如果(x,y) = 0 签名无效
  6. 如果r = x mod n证明签名有效。 

 Bitcoin中Secp256k1相关类图:

 

 部分代码:

CurvePoint ECDSAManager::ScalarMultiply(BigInteger k, CurvePoint point)
{
    CurvePoint result;
    while (k) {
        if (k & 1) {
            result = ECCAlgorithm::Add(result, point);
        }
        point = ECCAlgorithm::Add(point, point);
        k = k >> 1;
    }

    return result;
}

ScalarMultiply是计算公钥的函数,该函数相当于计算 k\ \times\ point

CurvePoint ECCAlgorithm::Add(const CurvePoint& p, const CurvePoint& q)
{
    if (p.IsNull()) {
        // 0 + q = q
        return q;
    }

    if (q.IsNull()) {
        // p + 0 = p
        return p;
    }
    
    // p == -q
    if (p == Negative(q)) {
        return CurvePoint();
    }

    // p != -q
    // CalcCoefficient 动态计算点乘法或者加法的系数
    BigInteger m;
    if (p == q) {
        m = CalcCoefficient( BIG(3) * boost::multiprecision::pow(p.m_x, 2) + SECP256K1.a, BIG(2) * p.m_y, SECP256K1.P);
    }
    else {
        m = CalcCoefficient( q.m_y - p.m_y, q.m_x - p.m_x, SECP256K1.P);
    }

    BigInteger x = m * m  - p.m_x - q.m_x;
    BigInteger y = m * (p.m_x - x) - p.m_y;

    x = Mod(x,  SECP256K1.P);
    y = Mod(y, SECP256K1.P);
    return CurvePoint(x, y);
}

BigInteger ECCAlgorithm::CalcCoefficient(BigInteger a, BigInteger b, const BigInteger& p)
{
    // 先计算分母的逆元的模(将分子设置为1)
    BigInteger x = ModularMultiplicativeInverse(b, p);

    a = a * x;
    return a;
}

 其重点是要学会计算逆元的模。

猜你喜欢

转载自blog.csdn.net/dragon_trooquant/article/details/125449879