Appearance
ECC
数学基础
椭圆曲线
形如的曲线被称为椭圆曲线,其中。
常见曲线参数:
secp256k1:
P-256(FIST):
BLS128_381: 二次扩域:
SM2:
BN128(zk-snark):
一些特殊曲线
Baby Jubjub Elliptic Curve(扭曲爱德华曲线方程):
- 对于运算 ,运算如下:
点加法
对于椭圆曲线上的两个不同点 和 ,其和 计算如下:
斜率计算:
新点坐标:
数学推导
直线方程:通过 和 的直线:
其中
求交点:将直线方程代入椭圆曲线 :
展开得:
整理为三次方程:
根的关系:设三个根为 ,由韦达定理:
因此:
y坐标计算:将 代入直线方程得到交点,然后取其关于x轴的对称点:
倍乘运算
当 时(),进行点倍乘操作:
斜率计算:
新点坐标:
数学推导同上
标量乘法
标量乘法 是计算点 自身相加 次的结果。通常使用二进制展开法(类似于快速幂)来高效计算:
输入:点 P,标量 k
输出:k·P
1. 初始化 Q = O (无穷远点)
2. 将 k 表示为二进制
3. 从最低位开始:
- 如果当前位为1,则 Q = Q + P
- P = 2P (点倍乘)
- 移到下一位
4. 返回 QECDH(密钥交换)
密钥生成
私钥生成
私钥 是一个随机整数,满足:
公钥生成
公钥 通过标量乘法计算:
密钥交换
Alice 和 Bob 通过以下步骤交换密钥:
- Alice 生成私钥 和公钥 ,将 发送给 Bob。
- Bob 生成私钥 和公钥 ,将 发送给 Alice。
- Alice 接收到 后,计算共享密钥:
- Bob 接收到 后,计算共享密钥:
- 最终得到相同共享密钥 。
ECIES(公钥加密)
密钥生成
私钥生成
私钥 是一个随机整数,满足:
公钥生成
公钥 通过标量乘法计算:
加密过程
给定消息 和公钥 ,ECIES加密过程如下:
- 生成随机数 ,满足 。
- 计算点 。
- 计算公钥 。
- 生成对称密钥 ,其中 是哈希函数。
- 加密消息 。
- 输出密文 。
- 发送密文 给接收方。
解密过程
给定密文 和私钥 ,ECIES解密过程如下:
- 计算共享密钥 。
- 生成对称密钥 。
- 解密消息 。
- 输出明文 。
ECDSA(签名)
域参数
- :有限域的大小
- :椭圆曲线参数
- :基点(生成元)
- :基点的阶(即 )
密钥生成
私钥生成
私钥 是一个随机整数,满足:
公钥生成
公钥 通过标量乘法计算:
其中 是椭圆曲线的基点。
签名生成
给定消息的哈希值 和私钥 ,ECDSA签名过程如下:
步骤1:生成随机数
生成一个随机数 ,满足:
!!!:这里是简化的,具体参照这里,简单说就是
步骤2:计算点
计算椭圆曲线点:
步骤3:计算 r
!!!:取点 的 坐标模 ,作为签名的第一部分。如果 ,则重新选择 。
步骤4:计算 s
!!!:
- 是 在模 下的乘法逆元
- 将消息哈希与私钥信息结合
- 整个表达式确保了签名与私钥和消息的绑定关系
如果 ,则重新选择 。
步骤5:输出签名
签名为 。
签名验证
给定消息哈希 、签名 和公钥 ,验证过程如下:
步骤1:参数验证
验证 和 在有效范围内:
步骤2:计算逆元
计算 的乘法逆元:
步骤3:计算标量
步骤4:计算验证点
步骤5:验证结果
如果 ,则签名有效;否则无效。
数学原理
从签名生成有:
重排得到:
在验证过程中:
替换 和 :
由于 :
根据签名生成中(式)的关系:
这证明了验证点 的 坐标确实等于 ,从而验证了签名的有效性。
Add-On v的生成与公钥恢复
由于压缩传输空间的需要以及ECDSA可以从签名恢复公钥的性质,往往只传输 ,而不传输公钥 。
// TODO
进阶:椭圆曲线计算加速
Jacobian坐标
由于椭圆曲线是 的,并且显然在计算 时需要在 中求逆,故而需要较大的计算量。
在椭圆曲线上,我们定义单位元为无穷远点 ,但是在仿射坐标系 中,而在射影坐标系中,方程可以表示为 ,于是,存在如下转换:(别问我是怎么来的,我也不是很明白,都是从py_ecc.secp256k1里看的)
- 仿射 射影:
- 射影 仿射:
而对于实际应用中常使用Jacobian坐标,其转换如下:
- 仿射 Jacobian:
- Jacobian 仿射:
注意这里所有的运算都是在mod p下的
加法
注意这里所有的运算都是在mod p下的
对于 ,其和 计算如下:
如果 直接用倍乘
倍乘
注意这里所有的运算都是在mod p下的
(里面的是椭圆曲线的参数)
注意倍乘时可以用类似平方快速幂的方式加速
python
if a[1] == 0 or n == 0:
return cast("PlainPoint3D", (0, 0, 1))
if n == 1:
return a
if n < 0 or n >= N:
return jacobian_multiply(a, n % N)
if (n % 2) == 0:
return jacobian_double(jacobian_multiply(a, n // 2))
if (n % 2) == 1:
return jacobian_add(jacobian_double(jacobian_multiply(a, n // 2)), a)
raise ValueError("Unexpected case in jacobian_multiply: This should never happen.")扩展域
// TODO