Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
377 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。6 W) T3 W# }; X
有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。
- N, K! O* m' y8 D; y# P" g( h6 Y( p2 J/ H% J: \
私钥的规格3 }* n: Q" e2 a" h
私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。1 t" ^; h" ]2 i$ C
换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。* r/ p8 R9 z0 f+ n- Y
  ) M# Q* O# J3 r- H5 q6 x& r
相同的私钥,以不同的格式编写。7 E) e5 _: L7 y( W; z( s

9 Z# {: g2 f/ t8 C8 {0 O: r* s安全的私钥生成
5 T( L4 ]8 n* n( q0 x) a* J. T# K既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。# l* L9 ^+ L( @( q
# r' H# C/ X, h' K; e, e# ?
了解 Bitaddress原理
0 K) T* v+ i! Y2 N0 d2 {- mBitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。/ x% ^$ E( P7 Z' t7 Z8 z
Bitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。  y, Z, W% c8 L: R4 J, g; d
程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。5 @) O: W9 a. E# x7 n8 x
初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。4 I, s, [* N: L' c: e9 [
最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。* x* }* R" W8 ^" G' O
初始化我们自己的种子池" U! K& J( f5 I0 w
我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。; p# W' G1 k  w1 S

0 L3 U( _) C/ S5 u' }  udef __init_pool(self):
6 B; \9 m- g% S; s3 ]1 d: r    for i in range(self.POOL_SIZE):
: C* r) ^: Q8 {! ~        random_byte = secrets.randbits(8)
/ Q5 Z6 E5 ~1 V3 T! ^; k        self.__seed_byte(random_byte)0 E8 m  n/ G& ?; M/ \1 c
    time_int = int(time.time())( v' W( [, M; m6 @& h7 B+ M
    self.__seed_int(time_int)
( j; T% M6 l# d5 g: h) vdef __seed_int(self, n):
1 S: }" v- ?. w% U3 {* w) h5 d4 s) y7 v    self.__seed_byte(n)
( a( t2 i) r: J  i    self.__seed_byte(n >> 8)" c% ~1 L/ e& b) b$ J! o7 O6 ?
    self.__seed_byte(n >> 16)
: Q) }+ x, G) p& A4 N3 G% U    self.__seed_byte(n >> 24)& O6 H# S! a3 ~# ^! v
def __seed_byte(self, n):
% J9 O' w' S" F, w+ p) m- H/ H% E    self.pool[self.pool_pointer] ^= n & 255
+ U7 Z# N) [$ e" j# R" w8 D    self.pool_pointer += 19 x0 I! e2 z3 H! _8 f5 h
    if self.pool_pointer >= self.POOL_SIZE:
2 P( @2 R: }' \5 t+ y1 K9 X0 D0 J8 v        self.pool_pointer = 0( k3 z  ^0 k. H) r
由输入填充种子池- R5 V* n3 M: }! C" o
这里我们先写入一个时间戳,然后写入用户输入的字符串。5 F) I" w6 S  `! x: V

% i* d6 d9 P1 P  ?4 m+ hdef seed_input(self, str_input):' C* f* b& M1 N. G' y2 n" d
    time_int = int(time.time())$ k  c$ w- q" h0 |
    self.__seed_int(time_int)
3 M5 @7 N' D, z1 X: R% F    for char in str_input:
! A, b; t9 S3 x5 A  d        char_code = ord(char)
/ Z# N& |7 f$ ]        self.__seed_byte(char_code)- M! X9 }% h% y- T5 h+ `7 I& r/ f
生成私钥" [! x% T0 ]7 ~( W3 T
首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。# m9 {" F, a. S6 F5 a" z
5 ^. P+ r6 m7 y! E0 Y
def generate_key(self):
9 b! }$ U; D. Q% A    big_int = self.__generate_big_int()
0 _% a6 i5 G# o3 G; n$ n8 v: n    big_int = big_int % (self.CURVE_ORDER — 1) # key / z  I* `, W$ I0 t6 j
    big_int = big_int + 1 # key > 0: h  x' e1 @# d/ L
    key = hex(big_int)[2:]
& Z) _; o5 ?- e( b% W    return key1 T/ [: n& @' |) s% G
def __generate_big_int(self):
6 L& @" r5 x6 Y5 o3 O' e    if self.prng_state is None:. B9 I! k8 d! f& Z! {
      seed = int.from_bytes(self.pool, byteorder='big', signed=False)
* E7 P( @6 a, k7 [' U- y4 b; |% z      random.seed(seed)
6 s% d& t3 v+ x4 z4 N# U& P      self.prng_state = random.getstate()- U7 v5 e0 i2 T6 f# g& e1 U
    random.setstate(self.prng_state)5 `: i7 F! Q- ^+ ~9 l
    big_int = random.getrandbits(self.KEY_BYTES * 8)
+ M* E/ B! i- E- Z  t) `5 I4 E% ]    self.prng_state = random.getstate(), c# W- Z8 \9 q: o$ [0 z7 f
    return big_int
. f7 @* K3 ?/ C2 L3 v5 f7 Q最后仅需三行就可以生成我们的私钥。" R; B, Z  N, j  u; C

  Q. L. i3 X) U) t( bkg = KeyGenerator()
5 a0 M1 U6 m6 pkg.seed_input(‘Truly random string. I rolled a dice and got 4.’)
0 R! ^  z" r' s; c2 ^% u+ Dkg.generate_key()
9 Y( u( B2 B- C生成ETH公钥! [% Q9 z) D3 L& R; I( k  A8 z3 x. f
将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。
& j- r' g, c2 l$ p. q+ H/ ]2 i# g
private_key_bytes = codecs.decode(private_key, 'hex')
+ q) l* _  k: [, k& D2 G! W# 獲得 ECDSA 公鑰" Q, P; W2 ]8 i0 ^- E
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key% \2 m/ v4 z! K7 \+ t& H, J5 L
key_bytes = key.to_string()
* W1 L7 X) n. u# n1 v9 Q% `key_hex = codecs.encode(key_bytes, 'hex')) J  `: [( Z: q# [
钱包地址
( ?1 s5 I4 y5 j) @: I+ ^5 P要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。; S/ d  J' k6 V- I# v8 \

" ~5 ]+ f0 [! {) t" upublic_key_bytes = codecs.decode(public_key, 'hex')6 B* Z/ M2 `5 w6 a9 B
keccak_hash = keccak.new(digest_bits=256)
3 Q, S+ t# y+ E1 G( i( f8 H& |/ [! tkeccak_hash.update(public_key_bytes)
/ i9 L; z. T7 l$ T( Z  v7 ^: nkeccak_digest = keccak_hash.hexdigest(), c1 _4 L  X' N( m
# Take the last 20 bytes
7 \2 l4 e8 V; y1 A0 awallet_len = 40$ s# k! H9 `& Y5 ]% X. C" @
wallet = '0x' + keccak_digest[-wallet_len:]
# p7 j3 n3 G+ u0 ~$ T校验和 (ERC-55)
5 K8 H1 B5 s. q. u9 R比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。
* e: d8 j2 Z. @5 X; N/ U但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。
, W6 h. Q; Z8 o" G将校验和添加到以太坊钱包地址使其区分大小写6 P% G% W5 c* n5 w+ R% Q7 c
首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。
) k& T# Z3 I) t$ E+ X4 ^4 h6 @其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。
  j; Q) C: y: O5 F6 u) d9 }! [最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。
/ @4 ^. {& g# |0 S1 N9 B3 ^. @此校验和有几个好处:
8 o) a5 ?  u+ U# P- N! C+ x1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;0 Y3 o7 J- q3 m$ x- c
2.   保持长度为 40 个字符;6 a; G# ~( P8 ~8 s1 t2 S5 r
3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;' K% o% E  N% D" |' `4 W. ^5 [: b
$ C( t& T) w% E& v1 a
checksum = '0x'
" t. T8 v5 Z$ x8 h' Q3 f# Remove '0x' from the address
/ v7 y5 R% D( O. W" e8 P* Z  uaddress = address[2:]
' ?) Y3 w2 B+ U) C, z" Naddress_byte_array = address.encode('utf-8')
! E3 {/ C' ?1 X4 g: a) v/ y# [6 @: Zkeccak_hash = keccak.new(digest_bits=256)' W% r6 L+ y" R3 ?$ ?
keccak_hash.update(address_byte_array)
8 W- [; b/ Y0 `, Bkeccak_digest = keccak_hash.hexdigest()
. A9 g6 a7 a+ H$ x9 }# z1 ufor i in range(len(address)):
0 n% N6 ?  A7 y0 P2 _& s    address_char = address keccak_char = keccak_digest
% o1 S$ `! y7 x& t& d    if int(keccak_char, 16) >= 8:* {0 ]1 u+ y0 v! K. l0 x6 Q9 @
        checksum += address_char.upper()2 A) i# _4 J7 l
    else:
; _2 v% Y* ^1 j! P0 w# W4 r- K        checksum += str(address_char)
  ]7 m+ U( l0 u% Z! S4 y! G总结
. X' |: I0 p- k" K" ?' A为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9