Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
424 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。! u7 d& M/ E8 ^. j% \, k1 }
有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。( j& D4 t0 ]$ m
! r# [5 C" W8 ^0 O/ F/ O
私钥的规格
, k8 H9 w6 i5 b私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。  f% C! C4 n7 R& |& }
换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。3 P& Q) z: s6 y! h4 L8 s* N" C, h$ b
  ' Q+ V1 i3 t7 {* K2 K; h
相同的私钥,以不同的格式编写。6 e! n) ]) `6 c2 j

! E9 c0 X( T& W' @& e4 B$ D安全的私钥生成  N8 i: \6 @" P; [: o' ?. |6 y
既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。
7 l& u/ [) v) f/ w. d) m2 W5 P# Y. u8 a+ _" a7 e$ s
了解 Bitaddress原理8 b: d. U: ?8 \+ k- D- H
Bitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。: q" G2 Q& _# g
Bitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。
0 ]5 x( O# O8 H% z8 A程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。
* Z5 a0 K' I# O5 P初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。
) B: C' x) }( n% z最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。+ F7 Z! |# O! }: T8 e* F9 O" _
初始化我们自己的种子池
  W3 W# S& h8 L$ Y! T9 y我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。
2 ~, S, M! t) I' X$ Y( e0 t. X6 X& x% ~) R' ?! u  A
def __init_pool(self):( k0 a) ]5 ?& K
    for i in range(self.POOL_SIZE):# l& B' U2 g8 Q* @! I
        random_byte = secrets.randbits(8)
8 o$ p( K. C  `2 L% g1 `        self.__seed_byte(random_byte)
. @" h/ s; N: J3 T7 s' b- q; k3 @    time_int = int(time.time()), p' |7 C5 a0 i  \- ?1 W8 f# @
    self.__seed_int(time_int)' @% k0 o5 R  J. q" A
def __seed_int(self, n):
8 O9 V; m, S2 c* G0 S    self.__seed_byte(n)* t- T4 B) R7 v& h; j- H* x$ M1 O# C
    self.__seed_byte(n >> 8): G- E  C/ V$ y5 x/ f
    self.__seed_byte(n >> 16)/ ?) z; `1 I9 S2 ?, k$ P
    self.__seed_byte(n >> 24)) n/ ?4 p/ h! f* h( I5 G
def __seed_byte(self, n):' {4 n; ^/ F" @6 D/ w* T
    self.pool[self.pool_pointer] ^= n & 2554 t9 s  }: X3 {: k. K
    self.pool_pointer += 1' J; m. u$ i+ `) @! w* t
    if self.pool_pointer >= self.POOL_SIZE:
' L/ @$ y" \- C$ F' m        self.pool_pointer = 01 ^3 |3 w, G; L/ u" C! H) J
由输入填充种子池+ r: E5 F1 n' L+ f7 h
这里我们先写入一个时间戳,然后写入用户输入的字符串。
, O& f- q: O$ Y" c; k+ p8 Q; i% Y( J) X; v/ Q
def seed_input(self, str_input):5 k3 y% B8 q  a5 B7 ]
    time_int = int(time.time())  }! I# ^8 \3 u
    self.__seed_int(time_int)
9 @1 X4 f9 k* J9 S  n- y    for char in str_input:
8 `2 E' v+ N0 W        char_code = ord(char); y& M- [3 b$ U
        self.__seed_byte(char_code)+ M: C, _/ B4 ?9 e
生成私钥
' Q  F) o) u4 O7 `+ `* P首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。6 Y  Y6 B! u1 }4 ?
2 n" M: m  s1 M4 n/ P3 x4 ]
def generate_key(self):' Q; {7 E: i+ _: ~, E5 r
    big_int = self.__generate_big_int()
. D* L; C1 ]0 G: b# p. |$ b    big_int = big_int % (self.CURVE_ORDER — 1) # key
4 e2 g( ~, C- k7 p: k6 H! o: h    big_int = big_int + 1 # key > 0' x, c  ^! y9 m' |$ A4 \$ u
    key = hex(big_int)[2:]+ T& G) g) v+ j( o0 u$ N7 V# a( r
    return key( ?% s+ W1 V: m2 }
def __generate_big_int(self):8 a7 d; l. v& d/ P; ?& Z
    if self.prng_state is None:
$ o1 ~( N8 p8 K5 Q  q      seed = int.from_bytes(self.pool, byteorder='big', signed=False)
: m7 p; G2 u: i; t3 u& |! J      random.seed(seed)4 m$ E% H( A3 W+ c6 p. D7 R+ N2 e
      self.prng_state = random.getstate()
3 H' K7 Q5 {9 M2 g3 k' u2 }    random.setstate(self.prng_state)1 Z& ?5 k8 a# S, u
    big_int = random.getrandbits(self.KEY_BYTES * 8)  B$ y& @6 Y# B% U% d
    self.prng_state = random.getstate()* U" F* Z/ ]+ x1 y/ i# f: ]
    return big_int
5 a; f% r$ m6 b( `7 c最后仅需三行就可以生成我们的私钥。8 u  H' m0 C% h2 @+ J% d

$ t; L; }% F# X$ h" T. qkg = KeyGenerator()% Z* H8 w  U' O/ Y/ h
kg.seed_input(‘Truly random string. I rolled a dice and got 4.’)
/ V( D) y4 {! i! T% i+ e- W5 Ckg.generate_key()
# P3 b  A6 R3 C5 O生成ETH公钥
/ r6 L2 V+ B: O5 ]+ |  R将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。
, _) B. ]. V7 d( d2 [: `3 Q: T! |: V, _2 q9 X
private_key_bytes = codecs.decode(private_key, 'hex')
+ K2 b+ t( m7 c: [4 T# 獲得 ECDSA 公鑰
9 J' m% D% k( h( B6 @2 B! gkey = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key6 r! C+ p% q6 `- y8 L6 _/ D" L
key_bytes = key.to_string()* y; o9 Z5 h/ K- B
key_hex = codecs.encode(key_bytes, 'hex')
2 Q, [$ `  J) T! P8 P钱包地址
# V( x6 f! ]; o; G& s要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。7 i' v3 Z* }/ f& c0 N9 p1 _* Q

) {& Q2 Y6 o* W0 upublic_key_bytes = codecs.decode(public_key, 'hex')3 A! Q% ]9 G2 b3 N: O5 x
keccak_hash = keccak.new(digest_bits=256)
6 Q7 {+ w5 {' i! x5 I2 nkeccak_hash.update(public_key_bytes)
- d9 {" i8 o& _9 K# nkeccak_digest = keccak_hash.hexdigest()7 a+ p- g- ~$ Y1 E. l
# Take the last 20 bytes. Y$ C7 _7 q3 |. r& `9 g8 D( s
wallet_len = 408 o) z3 q% ]0 W7 ?" N4 R
wallet = '0x' + keccak_digest[-wallet_len:]0 T2 ~9 h& f! C9 d; ~6 h+ k# o+ V2 ?2 ?
校验和 (ERC-55)4 `4 ?. Q) p1 I. p9 A) _3 @
比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。3 `' R& Z4 j1 o" u# g
但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。. r2 c! G- T2 ]5 N) Y0 P
将校验和添加到以太坊钱包地址使其区分大小写
" N3 @( q5 {  Y3 |首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。
! a1 Y1 c' j( I1 ?( W( e其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。
' o. {. O, @* B最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。
4 S) ]! \( I, x9 O4 N  b' t6 h此校验和有几个好处:( k. @4 `  w9 A1 e3 ~1 ?: r/ ~
1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;
) N  O, v2 ?( n. u$ C% A2.   保持长度为 40 个字符;( h8 X! ^7 _& ^
3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;
3 W/ {5 l# [& Y! y7 T! l0 }
# B- a. Z0 Q, u& W. h2 Xchecksum = '0x'
. P7 |1 z: w$ _  f* ~; v8 R1 m# Remove '0x' from the address" ?  I1 D2 b- E/ w9 J8 X  w
address = address[2:]# e; R; Q& k9 B$ y3 w) E8 N+ u
address_byte_array = address.encode('utf-8'): _% S; o; E4 P$ J
keccak_hash = keccak.new(digest_bits=256)5 p& h! d. ?" o7 A- f
keccak_hash.update(address_byte_array)
- Y! r) ~. ?% V9 q& G' jkeccak_digest = keccak_hash.hexdigest()& P! H: g7 H9 o- Y
for i in range(len(address)):# }+ I( Y2 N, Q
    address_char = address keccak_char = keccak_digest
) z7 |, W6 ^, t/ X) t" C! l    if int(keccak_char, 16) >= 8:
" c7 X: j9 D' f+ |& M/ ?$ A        checksum += address_char.upper()3 B& Z9 F* q1 q! j
    else:& i4 c/ `1 ?8 A' L" t% H
        checksum += str(address_char)
7 I# v3 b( y: Q4 l( W+ K/ t4 l! l( S) E总结
) j( }# f& ?: E" T0 J* w9 V: G( R为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9