Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
366 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。9 I0 S5 X. N  N8 G! u- Q8 Y; p1 @7 d
有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。) U3 \1 n" i3 N3 V  |/ N
8 f" f. s' ?0 `1 c; E2 `! D
私钥的规格
6 {" }  _2 b; x! g  t8 ~7 ?私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。+ V) f5 \3 i/ n/ T' f) m  t
换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。# x/ Q1 k/ {. |9 z( M# _- i
  
8 E3 p6 a0 e( D/ K" ^+ r' l4 @相同的私钥,以不同的格式编写。
# [& T2 X: \+ t9 {: l
, @9 E: f! s$ Y! x; Z7 f安全的私钥生成9 r2 H$ H* c8 q6 J, i/ w
既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。: b8 u9 V/ j. w; r% |, Y

, e8 W: ^; P* l了解 Bitaddress原理
' J" G6 N7 |' q) m0 W. DBitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。
2 ~9 ~/ I# j& K. C3 i9 |# e" `Bitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。; w4 F/ {( V" K0 k: ^
程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。+ k" G3 W9 R+ {* U1 ~/ ?% o' z
初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。
& y: d5 y+ v6 l$ u' @最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。8 K; i' x6 J* \' }
初始化我们自己的种子池
2 y; D7 U, l/ \& f+ {我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。: e& m9 ]8 @7 W4 h- x

* {+ d8 j# E! D7 G/ Udef __init_pool(self):( q" v  c, [5 m+ P* c; t/ {7 v! r
    for i in range(self.POOL_SIZE):' d: F- e- H: T* P/ g8 I% t* _
        random_byte = secrets.randbits(8)! n( u& p: N' B/ R4 {2 A
        self.__seed_byte(random_byte)
6 Y8 J9 [" ~: N' B3 f    time_int = int(time.time())
( z) l' V8 `% }2 G, K8 H8 y8 |    self.__seed_int(time_int)
: r& K5 F8 @0 h' fdef __seed_int(self, n):  i. b7 W" q$ Z: b- O
    self.__seed_byte(n)5 y+ f/ l* o* D: o" `5 N
    self.__seed_byte(n >> 8)
$ @. y: R8 `* }5 h+ o( `1 l2 e" m; j    self.__seed_byte(n >> 16): F+ K4 U9 Z7 M- b/ j3 A% {, S
    self.__seed_byte(n >> 24)
$ {0 P. ]* [3 C) Z6 e: P; edef __seed_byte(self, n):5 Y; P& B! l/ V/ O8 u3 d
    self.pool[self.pool_pointer] ^= n & 255+ B0 x' B$ g: J. a5 V
    self.pool_pointer += 1
# ~6 b! L, \( t& z9 t    if self.pool_pointer >= self.POOL_SIZE:
/ z' j( }2 [) T4 X        self.pool_pointer = 0
" O2 M8 \7 s* J, \: N. D& V, n' [由输入填充种子池* C. ~  [( W/ w$ N  J! c8 f# j
这里我们先写入一个时间戳,然后写入用户输入的字符串。
% U- w3 i3 b: c
2 Z0 U9 l( Z- q5 tdef seed_input(self, str_input):
& C! z3 \9 F2 f! J    time_int = int(time.time())
& G- [; v# [0 v3 N. r    self.__seed_int(time_int)
/ l" |7 G/ I6 x8 ]/ T5 y) S! z8 l    for char in str_input:
1 \; _( d3 r' C1 q0 A        char_code = ord(char)/ l/ D/ g& s, k
        self.__seed_byte(char_code)
: a5 }$ U2 F/ E0 D生成私钥1 y& h" {- g) C  D4 C
首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。
/ x3 |- L0 W" P; q
) f% E$ D9 p+ z0 a( S; \def generate_key(self):- i5 A! V) t" J1 P! v& x
    big_int = self.__generate_big_int()
0 j( D5 K( h0 ]$ m    big_int = big_int % (self.CURVE_ORDER — 1) # key $ t" }# q9 K7 Y* z9 F: B
    big_int = big_int + 1 # key > 0+ T+ ?  H, I9 ^" G- k6 }9 l
    key = hex(big_int)[2:]
! S9 N: C( ^  s4 v# [  \    return key
0 a$ Q# F  _6 D  }! rdef __generate_big_int(self):
, v% y7 l5 @* Z3 P( t4 B5 Y    if self.prng_state is None:
0 ~+ M/ S2 a% p4 l( _1 f2 g) I8 }. w      seed = int.from_bytes(self.pool, byteorder='big', signed=False)8 n/ k) t$ j# x1 t0 j6 Z
      random.seed(seed)
/ a/ e, I, M* Z+ e" _9 s5 k% j' _      self.prng_state = random.getstate()& u) h# I5 B1 t
    random.setstate(self.prng_state)! D+ _  z; l! A3 z) u) I; y$ t" X
    big_int = random.getrandbits(self.KEY_BYTES * 8)( V: _$ T- y  x! H0 j* d
    self.prng_state = random.getstate(), \) `- |* r+ A2 z( n. f
    return big_int
8 |. X* F/ [- @/ P. E) o1 L1 i最后仅需三行就可以生成我们的私钥。
3 X' a+ }: g7 x# Y( w5 Y% M/ l+ x5 F+ C# T* u; I# n/ ?
kg = KeyGenerator(): ^: y! l2 V! b# n% v
kg.seed_input(‘Truly random string. I rolled a dice and got 4.’)
; g9 A2 A# d4 g0 ^( J4 U" s0 s, }kg.generate_key()
: V2 [/ Y% n9 \3 v生成ETH公钥
3 }/ Q  ^/ E; Y1 Z将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。
# b2 N1 G) q  F8 z9 R1 g& b2 P! Q. z& s
private_key_bytes = codecs.decode(private_key, 'hex')
4 ?  i" X% c+ k# 獲得 ECDSA 公鑰" i5 e2 x4 i: u7 B& K
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key6 C" [3 C1 o, ^% U0 p, a- w
key_bytes = key.to_string()
3 V; a" C" }8 G" P% Xkey_hex = codecs.encode(key_bytes, 'hex')
1 Y7 A3 c& w: X# \: e8 T钱包地址
7 I# N2 D1 K  w0 b要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。" h1 X8 K& e; J* f. g
1 B* s3 T# V/ X
public_key_bytes = codecs.decode(public_key, 'hex')
$ v  q8 y5 h* I7 _( kkeccak_hash = keccak.new(digest_bits=256)
9 h) s/ N, V9 \: u8 a; g3 d- ukeccak_hash.update(public_key_bytes)
* A* n" ^$ h; u  G+ H* Ekeccak_digest = keccak_hash.hexdigest()
. G( t* W  V* t+ i* M. Q' z# Take the last 20 bytes* ]' N& A5 @' h' ]( [. F
wallet_len = 403 @( _! \: K- A" `
wallet = '0x' + keccak_digest[-wallet_len:]9 c" {* \2 n0 }$ W- ?2 T7 I
校验和 (ERC-55)
" a/ q& [: B7 Q3 Y& ?比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。
+ n; d5 g, q) h5 o! A' j但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。
; W. w1 {8 w( V6 X$ p9 v将校验和添加到以太坊钱包地址使其区分大小写% _) U9 B( [6 U9 [- |
首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。1 H# Q- A  [/ h9 M) M% W. p
其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。. d5 S: {6 j8 x, [
最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。$ P( d/ ?; n7 A/ T% [  W& R: [
此校验和有几个好处:2 Y7 s" N( _% R6 A* A
1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;) \* J2 r0 B- i# ?, m/ K
2.   保持长度为 40 个字符;4 a* W2 y9 s6 P! L. I# u8 i
3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;. Y/ z; x9 C, R6 `! p1 k' p$ ~
5 J" M, M) Y, n5 b# H8 K8 F" [
checksum = '0x'1 J" D5 i( j* ~" e5 R  }: p
# Remove '0x' from the address
5 q+ O) }. |( |& ~# _; maddress = address[2:]
; X! E: N4 }2 O, t' k. maddress_byte_array = address.encode('utf-8')+ O* i3 ^6 q- j
keccak_hash = keccak.new(digest_bits=256)! \" G% c4 d5 d5 s. s- ~
keccak_hash.update(address_byte_array)
" K5 `6 G3 I- @" ^3 U+ w4 [keccak_digest = keccak_hash.hexdigest()) ]3 i3 ~  Y" c
for i in range(len(address)):2 p0 V! l! }& A' k5 I2 G2 H/ l& E
    address_char = address keccak_char = keccak_digest
# Q$ m' Q  a8 n# C4 q5 h    if int(keccak_char, 16) >= 8:
: c0 M: Q& g. B) Z        checksum += address_char.upper()
1 v9 [" o# ~% x    else:1 ?: x. T  m: \& O6 \: w9 o
        checksum += str(address_char)) R0 o) O% e4 p' `' A' u' P3 c+ B
总结
! F/ W( P. T# @$ c  H为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9