在上一篇文章中详细介绍了椭圆曲线(EC)以及如何使用椭圆曲线的点乘法生成公钥。椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm,缩写作 ECDSA)是一种基于椭圆曲线密码学的非对称式密码学的数字签名算法。它有两个密钥,一个是公开的密钥,另一个是私有密钥;在我们常用的RSA非对称加密算法中,公钥和私钥都可用于加密或解密,如果公钥用于加密,则私钥用于解密;如果私钥用于加密,则公钥用于解密。但在比特币中,所有的区块数据和交易数据都不需要加密和解密,因为这些数据都是公开透明的,任何人都可以通过区块链网络查询和使用。使用ECDSA主要用于数字签名,它的作用是:由于私钥只由用户自己持有,故可以在比特币的交易转账中,签发交易必定出自于拥有私钥的用户;其他节点可以验证用户签发的交易数据是否有效、中途有否曾被篡改,节点接收者可信赖这些数据确实来自于私钥拥有者。对用户来说确保私钥的安全绝对不能泄露,如果私钥一旦泄露,那么就失去了该私钥对应的钱包地址一切。
椭圆曲线签名算法原理
在前面文章中介绍了公钥的计算公式,使用椭圆曲线有限域上的点乘法:
注意这里的乘法运算是指椭圆曲线的点乘法,而不是一般数学意义上的乘法。当使用乘法符号时一定表示点乘法,有些场合不使用该符号就表示普通的数学乘法。通常与椭圆曲线上的点乘法运算时都表示的是点乘法。G是椭圆曲线的基点,d是私钥,Q是公钥。
我们知道dG表示有d个G相加,根据加法结合律可得:
我们在等式的左边dG乘以一个哈希值,可得:
令:
可得出:
令:
可以得出:
上面公式中的m表示是消息,在比特币中可表示交易数据。K是私钥为k的公钥;Hash(m, K)代表对m和K合并后计算哈希值;Q是私钥为d的公钥;G是基点。
所谓签名就是要让别人知道签名的数据一定是拥有私钥的人签发的,其他人拥有的公钥是可以验证的,在公式 中,消息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创建了一个密钥对,私钥是,该私钥在区间
内随机选择,在比特币中,私钥可用随机数生成器生成,它必要具有很强的熵源以确保高安全。只要满足
就行了。n就是前一篇文章中提到的基点G的数阶。因此它的公钥是:
为了让 Alice 签署消息 m,遵循以下步骤:
- 计算
, 这里的哈希算法使用HASH256。
- 令 z 是e 的L(n) 个最左边的位,其中L(n) 是群阶n. (注意 z 可以大于 n 但不能更长)。意思是z的值bit位数与群阶n的bit位一样长,其值可大于n。这里的n就是基点G的阶。它是256位的大整数,同样的SHA256计算的结果也是256位的大整数。在椭圆曲线Secp256k1中,
n = fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
- 从区间
中选择一个安全的随机整数k。比特币使用随机的算法来生成该数。
- 计算k对应的公钥K, 它返回:
。
- 计算
,如果r == 0, 返回第3步。
- 计算
如果s == 0, 返回第3步。
- 签名(r,s)和 (r, -s mod n)是有效的签名对。
在上述公式中,不仅要求 k 是秘密的,而且为不同的签名选择不同的 k 也是至关重要的,否则步骤 6 中的方程可以解出私钥:给定两个签名 (r,s) 和 (r,s') ,对不同的已知消息 m 和 m' 使用相同的未知 k,攻击者可以计算 z 和 z' ,并且由于
(本段中的所有操作都是模n) 攻击者可以找到
由于
,攻击者可以计算出私钥
签名验证
上述Alice使用了他的私钥对数据进行了签名,为了让他人(在比特币网络中其他节点)验证的签名,先假设Bob需对签名进行验证,他必须拥有Alice的公钥的副本。 Bob 可以验证
是一个有效的曲线点,如下所示:
- 检查
不等于单位元素 O(无穷远点),否则其坐标无效 ;
- 检查
是否位于曲线上,将
代入曲线方程即可验证:
return (y * y - x * x * x - curve.a * x - curve.b) % curve.p == 0
- 检查
然后根据下列步骤来验证:
- 验证r和s在范围 [1, n-1]之间,否则签名无效
- 计算e = HASH(m)
- 确保z是e且与n阶的位数一致
- 计算
- 计算
,如果(x,y) = 0 签名无效
- 如果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是计算公钥的函数,该函数相当于计算
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;
}
其重点是要学会计算逆元的模。