Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
363 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。
5 Y7 Q6 G( R3 ?5 ]* d: X3 _& H( g有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。
% g" ~* O1 Q' d. y3 o3 F. g- m" r; Z2 u1 I# _9 m; {
私钥的规格
6 W8 Y5 e' E* M. I* T1 q' U私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。% F3 W$ E' ]" g! l- ]
换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。" r; z, N! K. L( J% x
  
" \- J+ l$ u, D) y8 b3 X相同的私钥,以不同的格式编写。
/ ~$ o* e! I8 L/ u
! R* P, X  v! _6 ?安全的私钥生成& F9 I& s  B5 b7 Q7 f. E$ {
既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。
, ?# q0 F  a1 E5 ]2 [' V' d5 T- a% I
了解 Bitaddress原理
7 O2 n& U$ N9 u* g) D2 G+ QBitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。
' r% v8 M! S' t4 DBitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。$ q" D# r' Q6 |9 x  z! W3 N2 Z1 I
程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。, ~1 l! {7 `  N6 `' ?, a2 N
初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。
* r. P+ M' q- c7 `0 L8 K最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。
0 p' I9 A' I# H5 ]2 q- t/ p: j初始化我们自己的种子池! S2 f# U; R/ z& P% ~
我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。. J' X, |' y& j7 X

3 P0 Y5 ?; K' z& P+ [5 |def __init_pool(self):* V$ w4 }( p+ L+ @" ]5 b- F
    for i in range(self.POOL_SIZE):% o& r# Z& ]; [1 N
        random_byte = secrets.randbits(8)% d0 u3 {2 O7 |% u: O8 F0 Q
        self.__seed_byte(random_byte)7 ?( Q9 x& [: X1 {
    time_int = int(time.time())" ?! h! ^" y2 q4 j
    self.__seed_int(time_int)- O) R- y/ y# V  T! G: Q
def __seed_int(self, n):3 ]5 i9 U5 J! l
    self.__seed_byte(n)
( n6 Q: U- M* j( ^1 ?! R    self.__seed_byte(n >> 8)7 R* T6 s: B5 b4 q) Z( m
    self.__seed_byte(n >> 16)
; @% n% R5 n* ?! u    self.__seed_byte(n >> 24)1 p$ T/ T5 D6 L9 y
def __seed_byte(self, n):
3 G. r. ?. f* m+ |) e) c3 I+ Q    self.pool[self.pool_pointer] ^= n & 2552 k4 l$ Z' l1 u0 B) n: ]' a
    self.pool_pointer += 1
, u- i' m4 E2 p9 c. Q. h# l    if self.pool_pointer >= self.POOL_SIZE:
( m" n7 H" v4 b: M5 s4 y7 C6 Q        self.pool_pointer = 0
/ y) g+ d0 y9 o1 r& N+ V% P由输入填充种子池
. u/ e  x0 I6 o2 C) J1 K! H) t这里我们先写入一个时间戳,然后写入用户输入的字符串。
% u  k2 K1 \. Z4 X- _' t+ r9 g0 w8 o, o" b3 o
def seed_input(self, str_input):! w. ]$ \4 c$ M1 L
    time_int = int(time.time())
+ U# l5 r) v- j* |% L2 O- h    self.__seed_int(time_int)
6 d- a. [$ ^0 U1 w' i* m1 G    for char in str_input:
! e# h9 `, T# C& s        char_code = ord(char)
) H! d% b; `9 `! `% J) H) Y. Y4 A        self.__seed_byte(char_code)- `7 c8 t4 T. j2 D2 \& X
生成私钥
; E* i. `1 q! t, p5 P首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。
2 ~0 b) V" v  j8 a$ r8 U+ P
5 n! Z+ y7 \' T! O2 J) sdef generate_key(self):' i* C. ^" L9 _$ n
    big_int = self.__generate_big_int()
. B8 {9 a/ y8 m    big_int = big_int % (self.CURVE_ORDER — 1) # key
+ h, `- S, R" U( |# c& m4 G0 v    big_int = big_int + 1 # key > 0/ ^% `% U4 B5 Z/ a& C
    key = hex(big_int)[2:]" M, q. d/ h) E( S4 f* n
    return key
, A' W+ T( h* m  r1 W% R$ idef __generate_big_int(self):2 o! Z. k1 z$ r
    if self.prng_state is None:
/ @8 v) ]: ]' ~      seed = int.from_bytes(self.pool, byteorder='big', signed=False)8 u  \/ I' ~0 X: M; q, L5 V! L
      random.seed(seed)  |4 ~0 p3 i4 L6 ]
      self.prng_state = random.getstate()5 S6 s+ q$ |' b9 W, h+ H
    random.setstate(self.prng_state): t0 ], I% p( h1 B
    big_int = random.getrandbits(self.KEY_BYTES * 8)/ y* C, C0 a6 I9 Z* X
    self.prng_state = random.getstate()) J9 d6 ?' c# M2 l2 T. _- Z4 b! J$ J
    return big_int+ n( y+ q) _7 V5 V5 n
最后仅需三行就可以生成我们的私钥。- A5 e4 t: Y$ @5 r- L% N

: C  ^; B6 v/ c6 F1 x6 ]kg = KeyGenerator()
9 {, {- l) F3 k9 Rkg.seed_input(‘Truly random string. I rolled a dice and got 4.’)
( n# w3 Y- n" O3 h$ E* Fkg.generate_key()  Y) Z5 w& x! a" E1 e
生成ETH公钥6 h# I8 O! f2 @
将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。
' J& C1 B8 {$ l
! B$ C6 i  T7 p+ Cprivate_key_bytes = codecs.decode(private_key, 'hex')
1 v0 e- M# c, q' a5 C- @# 獲得 ECDSA 公鑰. M0 o0 |" [2 Q, c6 P2 w
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
4 B$ g7 u- U( _3 okey_bytes = key.to_string()8 F& Z* n' ^& a! y  t$ _$ O) Q
key_hex = codecs.encode(key_bytes, 'hex')+ c- a3 L% l- ?4 S3 i$ @5 d8 D
钱包地址8 y% V/ y8 H; F1 A% ?
要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。& V6 E$ x/ }  N- Z2 P/ v
7 r. m1 D) W" E; B# \' A1 U* \8 v
public_key_bytes = codecs.decode(public_key, 'hex')1 ~, }. s& t7 _! I; A' B+ r
keccak_hash = keccak.new(digest_bits=256)
6 A" L* t" P8 N* [! l' O2 lkeccak_hash.update(public_key_bytes)3 q* o- v  x0 n$ {( F
keccak_digest = keccak_hash.hexdigest()
- }+ M" W' g  {& J& Y* _# Take the last 20 bytes
" \4 v  _- {4 A- twallet_len = 405 x6 l: [* A; A+ L
wallet = '0x' + keccak_digest[-wallet_len:]
7 H" j" G4 Q! Q  _校验和 (ERC-55)3 O/ P  t* e* v* {# T1 V, Z
比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。
7 T/ K9 F+ P& s但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。
: ]! V3 x. s. t" l) Q; W将校验和添加到以太坊钱包地址使其区分大小写/ e$ g! m1 |8 d% ?3 \6 R1 F
首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。. R" F% R, D( H5 v/ N
其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。$ U# W( k1 f& |! p; ]2 v
最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。( O' s! \; ^2 w+ l
此校验和有几个好处:
) q, F4 E, a* |# T* N1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;
3 Q3 [" x5 p: Q; D. ]. O3 `; _& w2.   保持长度为 40 个字符;2 E9 S  P- H. m) \2 C, |
3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;
' [& A1 ]5 X* C( m; F. p2 E# F! p' k% W
checksum = '0x'  {1 U6 N* q2 Q( B; h3 O
# Remove '0x' from the address
7 R% C) H6 E1 n+ kaddress = address[2:]& P1 k) I! a- Z2 h3 w
address_byte_array = address.encode('utf-8')
1 o( N+ q$ G* x7 w, F. e$ m" j9 {keccak_hash = keccak.new(digest_bits=256)& {) A: U* J& ^& ^. o
keccak_hash.update(address_byte_array)1 i5 p0 ]& v: m" i5 f5 n
keccak_digest = keccak_hash.hexdigest()0 g: h- J- z2 Q) ?: o
for i in range(len(address)):, D) Q, x' T9 y( W" w% W
    address_char = address keccak_char = keccak_digest$ T% |# \, w% h5 Q0 C% h  l
    if int(keccak_char, 16) >= 8:
& I/ y$ W5 r* Y, k; [2 H' c- n        checksum += address_char.upper()/ v  q, G6 T/ f
    else:
7 t- ~" p3 @- e1 P, ]8 @        checksum += str(address_char)
& A2 t2 d  a" y2 t2 X总结3 j. u) _* J7 M0 k, Z
为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9