Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
469 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。
2 K. B2 G( i) }; `3 _8 ?+ m有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。
+ u  e4 J  Q0 f8 S
2 z( p! }8 I. D& x$ {; L) B0 u% \0 H私钥的规格4 M+ d9 ]* Q# c6 z  a' a4 a/ M3 f
私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。" S) A3 _0 {8 {6 Z
换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。
) K- u& G0 b1 U' z  
8 o# e* s" T: [1 z2 U6 b. W相同的私钥,以不同的格式编写。
3 \1 }/ ~/ O3 s. K4 z
) X" F; s1 S1 N! d  ?0 g8 K1 Q, Z安全的私钥生成
) W2 Y  J5 q  S/ H  M7 [既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。3 F/ o7 Z/ M9 _" \: a: H' j9 m, e8 k

; R1 U$ _0 x. L$ o+ B了解 Bitaddress原理
* U1 {0 k3 l* zBitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。! v3 a: d* Y) |" A
Bitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。
# N+ P, Z& U' T程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。) s- H4 A6 `+ }7 H4 e% p( a
初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。
% Z$ B1 ~& V" ?  {( o6 ^* _8 k最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。" r% J7 ~( |. F
初始化我们自己的种子池
: j: |- o1 d" F5 c8 Y) }' k1 L9 U我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。  W5 b( U0 q: z0 Q& b0 z- ]) X0 l

3 h, f2 U+ d& ^* m' Qdef __init_pool(self):( g* H( V% Q. q6 G8 W8 t
    for i in range(self.POOL_SIZE):6 }3 g, Z! Y( z' F- x% A
        random_byte = secrets.randbits(8)
3 t# W: S1 R2 w0 O8 @        self.__seed_byte(random_byte)
. f* s' S/ [* ^; `4 C& ]7 R2 R    time_int = int(time.time()); Z  D- F* u: x' N' J3 E6 k
    self.__seed_int(time_int)
# l, T: D  T3 f6 C5 H; ~  u1 Udef __seed_int(self, n):
- B8 H% H$ Y* p    self.__seed_byte(n); p+ F$ |5 E" p% [3 F" R
    self.__seed_byte(n >> 8)
$ H* R& m/ ]! E! p: G' _    self.__seed_byte(n >> 16)
5 X/ _, K, o! S9 V, L    self.__seed_byte(n >> 24)
3 G9 d. l8 P2 R* E3 X5 jdef __seed_byte(self, n):
+ e5 U0 k' Y: `* H    self.pool[self.pool_pointer] ^= n & 255
4 i/ ^% r3 v1 S9 I7 b( L. V) {    self.pool_pointer += 1' \4 D2 W8 a5 z% b1 h  @
    if self.pool_pointer >= self.POOL_SIZE:
  Z# F% F5 [2 E+ K8 s! R        self.pool_pointer = 0
+ \! A5 \' @" A% j由输入填充种子池, h) N! B: A& n# ]
这里我们先写入一个时间戳,然后写入用户输入的字符串。
" ?8 ]4 B# y$ f& ?, t' \6 }9 ]  X
1 s1 m( F8 @: n3 I2 V) vdef seed_input(self, str_input):
1 |2 i5 h* Z1 C' w% P    time_int = int(time.time())
- I- x  j. A& {. ^) @    self.__seed_int(time_int)1 Z( l$ T- c, A: ?: j
    for char in str_input:
; N2 U) `4 |* A7 y7 e0 M8 |        char_code = ord(char)4 T% e7 x$ K7 _0 C9 k0 I  b) U+ Z
        self.__seed_byte(char_code)
* s, c7 ^  H* ?( T0 d, y, p生成私钥
& N7 r4 z- @- R) V6 n3 y1 V首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。) v$ `: y; y- l8 A  J. a# ]
! }) M3 u9 D& X! `
def generate_key(self):
8 c* y; h% D/ t' g. R" W    big_int = self.__generate_big_int()9 @! K4 `8 T6 N$ _4 {8 x7 A: D
    big_int = big_int % (self.CURVE_ORDER — 1) # key ( y5 ~: c9 ]2 D9 V4 n% P
    big_int = big_int + 1 # key > 03 r# ^' G  l+ B* {
    key = hex(big_int)[2:]% y, j4 o9 t. J+ h, D
    return key& C+ R! O+ C* X7 i+ D
def __generate_big_int(self):
3 p/ n: A. N7 g4 w    if self.prng_state is None:
+ X5 D8 `6 ^  i      seed = int.from_bytes(self.pool, byteorder='big', signed=False). ^# p+ q' [- n6 V" V) Z
      random.seed(seed)
, T5 [5 z- d% }5 q! ^$ z% y      self.prng_state = random.getstate()$ q" I& @2 O. p2 f8 N
    random.setstate(self.prng_state); L) ^- r. G4 E
    big_int = random.getrandbits(self.KEY_BYTES * 8)
% |, o+ W1 N. L! Q7 x; ~3 [    self.prng_state = random.getstate()
# m0 [# W3 ]+ M! K/ w2 y5 B    return big_int$ @5 m) Q$ Z& j+ c: G% Q- i
最后仅需三行就可以生成我们的私钥。
3 d6 R5 [8 c! v/ |* H% o! n9 ]- }3 S9 Q1 h
kg = KeyGenerator()
* I7 n! f* g; v0 E7 Ckg.seed_input(‘Truly random string. I rolled a dice and got 4.’)
5 A. J3 s0 U1 [! u7 S/ }$ ekg.generate_key()
6 o+ a5 k6 j7 S2 }4 \" w5 b生成ETH公钥7 k2 o. @6 o% G" P
将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。/ @' q  |' k8 [  A" g, T5 M4 L$ G
- z# p* m9 t: ?
private_key_bytes = codecs.decode(private_key, 'hex'): Z, @! [( n' C0 Q! C/ P4 v( A1 [  t
# 獲得 ECDSA 公鑰* K5 [# [- t" v4 C5 g% M
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
. @+ T# A) U3 y8 G' Fkey_bytes = key.to_string()1 m" h; O0 n2 M* K' y4 j
key_hex = codecs.encode(key_bytes, 'hex')& D& R/ D; U) t& W. O7 {$ Q1 C
钱包地址% G- O( e) e* F" x
要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。
" ~$ _( w2 _' n0 ]! u6 a3 p
4 ^& H, E! k5 ypublic_key_bytes = codecs.decode(public_key, 'hex')
0 g# Y1 R$ T( Q4 P3 |9 A; j' ckeccak_hash = keccak.new(digest_bits=256)1 N5 j$ w; V' `: ], o, W; I
keccak_hash.update(public_key_bytes). e, r- x# u( f$ ]. y* E' K- D
keccak_digest = keccak_hash.hexdigest()
* ^8 q8 Z9 T) P6 X% G# Take the last 20 bytes
' H3 \6 Y0 k2 W; C. i7 Dwallet_len = 403 e! b3 `7 J+ ~- S. e2 @
wallet = '0x' + keccak_digest[-wallet_len:]
5 A) M2 P, K) a校验和 (ERC-55)2 ], R0 }$ j" i% p9 c$ i% }& W
比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。% a; K$ a. S6 X* `
但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。
: B# S# x" I: G2 _9 [" F将校验和添加到以太坊钱包地址使其区分大小写) c9 q0 `) T' l0 Z8 }+ j! H5 Y
首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。3 m1 {  b0 q3 q% U; M
其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。/ \) ]; ?1 Q3 |" W; O! k
最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。+ p# B" X+ c' a, j
此校验和有几个好处:
0 O3 X5 ~+ W" z' R% @/ o1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;; S% ]3 S7 `( `/ n+ z& ^( @# o
2.   保持长度为 40 个字符;
# d: ^. B5 J* `$ I' ^3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;! a& v5 A3 l3 x  V# Q  X6 E

$ W3 Y0 v9 }( [& Y& @' t' @: }1 Hchecksum = '0x'4 c8 }; e- |$ P0 E5 J8 h
# Remove '0x' from the address
8 [% p+ a( `! iaddress = address[2:]( W. z( l2 n3 g: C% J3 b
address_byte_array = address.encode('utf-8'), ]5 p% D6 t, r0 ]7 }
keccak_hash = keccak.new(digest_bits=256)& R0 c0 D# Z8 Z# @3 K7 J
keccak_hash.update(address_byte_array): f1 h/ z; \7 ]# Z( c( \, a$ h1 d
keccak_digest = keccak_hash.hexdigest()& k1 D: m9 n; ~( B6 K/ d- \
for i in range(len(address)):
9 o6 ?# H3 s# L+ Q% b    address_char = address keccak_char = keccak_digest; k; j" G0 f/ u6 b2 i
    if int(keccak_char, 16) >= 8:% L, R0 `/ p* L) D6 ^
        checksum += address_char.upper(); ?8 h3 e* f# u! m* w7 \6 C
    else:9 [8 c7 k+ K/ V- }* N6 K% ?
        checksum += str(address_char)$ @5 D4 d1 \/ C8 O1 [) f* \
总结
! P0 L/ }* F3 S为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9