从公钥生成 Ethereum 地址
Ethereum 地址是公钥的 keccak256 哈希值的最后 20 个字节。公钥算法是 secp256k1,与 Bitcoin 中使用的算法相同。
因为它是一种椭圆曲线算法,所以公钥是一个 (x, y) 坐标对,对应于椭圆曲线上的一点。
生成椭圆曲线公钥
公钥是 x 和 y 的拼接,我们正是对这个拼接后的结果进行哈希处理。
以下是具体的代码。
from ecpy.curves import Curve
from ecpy.keys import ECPublicKey, ECPrivateKey
from sha3 import keccak_256
private_key = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
cv = Curve.get_curve('secp256k1')
pv_key = ECPrivateKey(private_key, cv)
pu_key = pv_key.get_public_key()
# equivalent alternative for illustration:
# concat_x_y = bytes.fromhex(hex(pu_key.W.x)[2:] + hex(pu_key.W.y)[2:])
concat_x_y = pu_key.W.x.to_bytes(32, byteorder='big') + pu_key.W.y.to_bytes(32, byteorder='big')
eth_addr = '0x' + keccak_256(concat_x_y).digest()[-20:].hex()
print('private key: ', hex(private_key))
print('eth_address: ', eth_addr)
ecpy 库位于 https://github.com/cslashm/ECPy。该库在 Python 中实现了椭圆曲线数学计算,因此它的速度不会像 Bitcoin C 语言实现的包装器那么快,后者被 coincurve 库所使用。
然而,Python 实现允许你逐步查看用于推导公钥的椭圆曲线数学运算过程。
你可以使用这段代码通过暴力破解生成一个 Ethereum 靓号地址(vanity address),但请注意,如果你的随机性来源不安全或随机性位数太少,你可能会遭遇类似于 this 的黑客攻击。
使用 Python 和抛硬币或掷骰子生成私钥
你可以通过抛硬币 256 次自己生成私钥从而生成 Ethereum 地址,如果掷出正面则在字符串中写入 1,反面则写入 0。
假设你得到以下结果:
result = b'1100001011001101010001001100101000001111101101111011001000110001101100011101101011010001011000101111100110010101001001101110111011001000100001010101111100001100100110010010111110110100000010011111100000110101001110000101100101011111001101010001100001000'
你可以使用以下 Python 代码将其转换为私钥:
# 2 means base 2 for binary
private_key = hex(int(result, 2))
# private key is 0x1859a89941f6f646363b5a2c5f32a4ddd910abe19325f6813f06a70b2be6a308
然后,将该私钥代入上一节的代码中,你就用自己生成的随机性创建了你的地址。
同样的事情可以通过掷 16 面骰子 64 次并逐字写下产生的十六进制字符串来更快地完成。请注意,大多数骰子没有代表数字零的标识,因此你必须从每次的结果中减去 1。
如果你只有传统的六面骰子,你可以写出一个六进制的字符串(不要忘记从每次掷出的结果中减去 1)并进行进制转换得到二进制。你需要不断掷骰子,直到你的私钥至少有 256 位。如果你对随机性特别偏执,你可以使用赌场级别的骰子。
请谨慎使用 Python 内置的随机数库。它的设计初衷并不是为了保证密码学安全。如果你对这个主题很陌生,我们建议你熟悉一下 cryptographically secure randomness。
通常,你无法使用这种方法初始化硬件钱包,因为它们使用的 24 个单词助记词与用于签署交易的私钥并不是同一回事。这 24 个单词的助记词用于派生出多个私钥,供钱包持有的不同类型加密货币使用。
数学原理:关于 secp256k1:从私钥推导公钥
曲线的形状
secp256k1 定义了椭圆曲线 (mod p) 的形状,其中 p 是素数 115792089237316195423570985008687907853269984665640564039457584007908834671663
或者表示为
不应将素数 p 与曲线的阶(order)混淆。并非每个满足 的值都能使上述等式成立。然而,曲线上进行的运算保证是闭合的(closed)。也就是说,如果将两个有效点相加或相乘,结果将是曲线上的一个有效数值。
起始点
secp256k1 中另一个重要的参数是起始点 G。因为 G 是椭圆曲线上的一点,它是二维的并包含以下参数:
x = 55066263022277343669578718895168534326250603453777594175500187360389116729240
y = 32670510020758816978083085130507043184471273380659243275938904335757337482424
为了验证 G 是一个有效点,我们可以将这些数字代入 Python 中计算:
x = 55066263022277343669578718895168534326250603453777594175500187360389116729240
y = 32670510020758816978083085130507043184471273380659243275938904335757337482424
p = 115792089237316195423570985008687907853269984665640564039457584007908834671663
assert pow(y, 2, p) == (pow(x, 3) + 7) % p
为了创建公钥 / 私钥对,首先生成一个随机数 s(这就是私钥)。然后将点 G 自身相加 s 次,得到的新点 (x, y) 即为公钥。如果 s 足够大,实际上是无法通过 G 和 (x, y) 反推出 s 的。
在此方案中签名消息本质上是为了证明你知道 s,同时又不会暴露它。
将 G 自身相加 s 次等同于计算 s * G。实际上,我们可以剥离库所提供的一些抽象层,在更底层的视角观察这个操作。
from ecpy.curves import Curve
from sha3 import keccak_256
private_key = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
cv = Curve.get_curve('secp256k1')
pu_key = private_key * cv.generator # just multiplying the private key by generator point (EC multiplication)
concat_x_y = pu_key.x.to_bytes(32, byteorder='big') + pu_key.y.to_bytes(32, byteorder='big')
eth_addr = '0x' + keccak_256(concat_x_y).digest()[-20:].hex()
print('private key: ', hex(private_key))
print('eth_address: ', eth_addr)
公钥仅仅是私钥与 secp256k1 椭圆曲线上的点 G 相乘的结果。仅此而已。
了解更多
立即查看我们高级的 blockchain bootcamp,成为一名掌握其他程序员所不知的硬核知识的区块链开发者。
最初发布于 2023 年 1 月 30 日