如何用比特币私钥创建以太坊地址

区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以BTC为例的话,在这个过程中最重要的角色的就是“椭圆曲线加密算法”。

有些人会以为BTC跟ETH是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的secp256k1曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明ETH如何从地址验证由私钥生成的公钥。

如何用比特币私钥创建以太坊地址

Vitalik曾经在Ethereum Community Forum上回复过为何不使用其他曲线

以下整理自 Timur Badretdinov 在 freeCodeCamp 上的一系列文章。
私钥的规格
secp256k1曲线上点的个数约有2²⁵⁶个,每个点可由一组256位代表,而256位正好是32个位元组,所以我们需要提供这个曲线算法32个位元组的数据。并且因为我们使用ECDSA,密钥必须是正数且小于该曲线。
换句话说,BTC及ETH的私钥都是一组32字节的字串,但它也可以是二进制字串、Base64字串、WIF密钥、助记码(mnemonic phrase)、十六进制字串。

相同的私钥,以不同的格式编写。

安全的私钥生成
既然都知道他们使用的是同一条曲线,那我们其实就可以使用BTC社群比较信任的bitaddress.org来生成我们的私钥,(用MEW或Metamask也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥,所以我们将用python设计一个更简单的bitaddress。

bitaddress.org提供了WIF格式的私钥

了解Bitaddress原理
Bitaddress做了三件事情。首先,初始化字节数组,然后尝试从使用者的电脑获得尽可能多的熵,根据使用者的输入填满数组,最后生成私钥。
Bitaddress使用256字节的数组来存储熵。这个数组是被循环复写的,所以当数组第一次填满时,索引变为零,然后复写过程再次开始。
程式从window.crypto生成一个256字节的数组。然后写入一个时间戳来获得4个位元组的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外6个字节。
初始化后,使用者持续输入来复写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字元代码。
最后,bitaddress使用累积的熵来生成私钥。bitaddress使用名为ARC4的RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取32次。
初始化我们自己的种子池
我们从加密RNG和时间戳中写入一些字节。__seed_int以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。

def __init_pool(self):
for i in range(self.POOL_SIZE):
random_byte = secrets.randbits(8)
self.__seed_byte(random_byte)
time_int = int(time.time())
self.__seed_int(time_int)
def __seed_int(self, n):
self.__seed_byte(n)
self.__seed_byte(n >> 8)
self.__seed_byte(n >> 16)
self.__seed_byte(n >> 24)
def __seed_byte(self, n):
self.pool[self.pool_pointer] ^= n & 255
self.pool_pointer += 1
if self.pool_pointer >= self.POOL_SIZE:
self.pool_pointer = 0

由输入填充种子池
这里我们先写入一个时间戳,然后写入使用者输入的字串。

def seed_input(self, str_input):
time_int = int(time.time())
self.__seed_int(time_int)
for char in str_input:
char_code = ord(char)
self.__seed_byte(char_code)

生成私钥
首先使用我们的池生成32位的数字,并确保我们的私钥在范围内(1,CURVE_ORDER),然后为了方便,我们转为十六进制并删除0x的部分。

def generate_key(self):
big_int = self.__generate_big_int()
big_int = big_int % (self.CURVE_ORDER — 1) # key < curve order
big_int = big_int + 1 # key > 0
key = hex(big_int)[2:]
return key
def __generate_big_int(self):
if self.prng_state is None:
seed = int.from_bytes(self.pool, byteorder=’big’, signed=False)
random.seed(seed)
self.prng_state = random.getstate()
random.setstate(self.prng_state)
big_int = random.getrandbits(self.KEY_BYTES * 8)
self.prng_state = random.getstate()
return big_int

最后仅需三行就可以生成我们的私钥。

kg = KeyGenerator()
kg.seed_input(‘Truly random string. I rolled a dice and got 4.’)
kg.generate_key()

生成ETH公钥
将我们刚刚的私钥代入椭圆曲线,我们会得到一个64字节的整数,它是两个32字节的整数,代表椭圆曲线上连接在一起的X点和Y点。

private_key_bytes = codecs.decode(private_key, ‘hex’)
# 获得 ECDSA 公钥
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
key_bytes = key.to_string()
key_hex = codecs.encode(key_bytes, ‘hex’)

钱包地址
要从公钥创建地址时,我们只需要将公钥带入Keccak-256(你可能会听到一些人称呼他为“卡咖256”),然后获得回传值的最后20个字节。没有Base58或任何其他转换,唯一需要的是在地址的开头添加0x。

public_key_bytes = codecs.decode(public_key, ‘hex’)
keccak_hash = keccak.new(digest_bits=256)
keccak_hash.update(public_key_bytes)
keccak_digest = keccak_hash.hexdigest()
# Take the last 20 bytes
wallet_len = 40
wallet = ‘0x’ + keccak_digest[-wallet_len:]

校验和(ERC-55)
比特币通过将公钥哈希后并获得回传值的前4个字节来创建校验和,如果不添加校验和则无法获得有效地址。
但以太坊一开始并没有校验和机制来验证公钥的完整性。直到Vitalik Buterin在2016年时引入了校验和机制,也就是EIP-55,并且后来被各家钱包和交易所采用。
将校验和添加到以太坊钱包地址使其区分大小写
首先,获得地址的Keccak-256哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。
其次,依序迭代初始地址的字节。如果哈希值的第i个字节大于或等于8,则将第i个地址的字符转换为大写,否则将其保留为小写。
最后,在回传的字串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。
此校验和有几个好处:
1.向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入
2. 保持长度为40个字元
3.平均每个地址将有15个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如4字节的校验代码好,但比ICAP提高了约50倍

checksum = ‘0x’
# Remove ‘0x’ from the address
address = address[2:]
address_byte_array = address.encode(‘utf-8’)
keccak_hash = keccak.new(digest_bits=256)
keccak_hash.update(address_byte_array)
keccak_digest = keccak_hash.hexdigest()
for i in range(len(address)):
address_char = address[i]
keccak_char = keccak_digest[i]
if int(keccak_char, 16) >= 8:
checksum += address_char.upper()
else:
checksum += str(address_char)

结论
为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面20个字节。

如何用比特币私钥创建以太坊地址