Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
467 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。2 }$ Y8 U6 L2 B: O, q: D" g% j
有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。0 R( s# V, ^/ \6 F0 g. Q% B

& V: F, T9 `. Y. p3 |私钥的规格' M3 E! k% u% W( v, i: k. U* I0 Q
私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。
9 H/ f/ F6 g6 X2 t* c换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。" P9 P8 u$ j$ ~
  
! C. c1 K( x* \相同的私钥,以不同的格式编写。
0 @% M8 X" j# n" Q) V; r0 Y) A. ~; ]. g
安全的私钥生成
; G% w4 D) v% t既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。
' Z, X/ [( n" C8 c& }4 X
% h3 ]. P! A0 V* b) K; E1 C了解 Bitaddress原理  G: ^/ u# ~* u. h3 g6 Z# v% S! {  z* K- q
Bitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。& Z2 @8 y8 T4 H$ O
Bitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。) V+ T- l, k- G' s7 \' U
程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。1 P3 j, K. S4 ~/ U% i
初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。% y+ p+ N6 q; L+ |" C" @
最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。! [' \6 D5 ?; l. e! @3 W
初始化我们自己的种子池* y# X! G( y1 @% X! m% v; l$ ^8 v
我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。
6 j0 I- h7 M  O" u/ Z
, `( {+ K8 n" E* v3 r' i* kdef __init_pool(self):
; w+ u, a1 W/ M9 \    for i in range(self.POOL_SIZE):
9 ~# p8 q" ~/ [1 J        random_byte = secrets.randbits(8)# b6 \% Z& b! `6 C/ t/ c! m
        self.__seed_byte(random_byte)+ L6 G9 V* y9 A
    time_int = int(time.time())! w, D: K- ]+ D) v+ H
    self.__seed_int(time_int)
" H* ]! T# m2 L8 X) Gdef __seed_int(self, n):
9 h5 x) ?# Q1 Q. O8 s    self.__seed_byte(n)
$ C+ N9 K$ d- x2 g% Q/ t    self.__seed_byte(n >> 8)8 R" f1 L$ r5 ?5 [" Q
    self.__seed_byte(n >> 16)
. X  G3 |' w+ ?6 D) @    self.__seed_byte(n >> 24), r4 g( M8 z- A0 B+ H2 g, {
def __seed_byte(self, n):8 ]; u7 F9 L* k- D' S+ c4 Q8 R
    self.pool[self.pool_pointer] ^= n & 255: C, h7 f/ u& Z* Z6 u% d+ p
    self.pool_pointer += 1
+ q1 ^% X/ k0 i5 c    if self.pool_pointer >= self.POOL_SIZE:# |2 Z+ z" Y2 I3 w5 p  n+ L1 p
        self.pool_pointer = 0
  @5 [3 R, K' p9 s由输入填充种子池
: C  O, k3 I% @- n0 t这里我们先写入一个时间戳,然后写入用户输入的字符串。1 h* |& z1 [0 E0 o/ U8 E, R
: u# ^% ~5 L' ]: t' h3 ^2 A9 e1 \- I3 o
def seed_input(self, str_input):
% K) N4 D& u+ K8 F* N; t    time_int = int(time.time())
( Z+ l6 b, m4 R) I) s, q. V0 n    self.__seed_int(time_int)" p' Y# q  ]. V; l
    for char in str_input:
5 B7 g) B2 m* a. Y        char_code = ord(char)% b% c' a( H; `# [9 j0 t9 M3 B9 k' ?
        self.__seed_byte(char_code)
7 \* B2 o  ^: w8 |生成私钥
, c) t9 |6 R) Z6 h8 G首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。
2 G5 z6 ~( _; y! U1 T5 W6 e3 B; H; ~0 [
def generate_key(self):
& B$ W7 Q1 k) ~7 y    big_int = self.__generate_big_int()
$ g; P  |' m5 x' b4 U    big_int = big_int % (self.CURVE_ORDER — 1) # key
4 B% b' n" u8 H; |3 f5 {; m    big_int = big_int + 1 # key > 0- S( L4 c- T+ d; }; t+ d
    key = hex(big_int)[2:]+ X8 `" I! {4 U$ v
    return key
0 L- N5 G( {$ v! Y7 V8 |def __generate_big_int(self):4 j4 @, }' f# d8 {; S  l  H
    if self.prng_state is None:
5 {" X2 Q+ z" v! u      seed = int.from_bytes(self.pool, byteorder='big', signed=False)/ y, D& l* s9 j8 L/ [
      random.seed(seed)
2 m  E9 i" x4 ]  ^      self.prng_state = random.getstate()
& q! w$ E- s2 p# q( J    random.setstate(self.prng_state)& \" x# {0 U7 ^3 I
    big_int = random.getrandbits(self.KEY_BYTES * 8)% Z8 ?' x6 v3 x9 P3 k( ]/ _
    self.prng_state = random.getstate()! r  X  F' w3 Z1 y
    return big_int  `2 j, \7 U* T# @% I
最后仅需三行就可以生成我们的私钥。
& ^! n+ X# x) i% d
, N3 n% N2 C8 G) ^! U  z- \' Z) Skg = KeyGenerator()
2 D$ e3 u8 O3 U$ M* `5 H& j$ ~kg.seed_input(‘Truly random string. I rolled a dice and got 4.’)5 k8 Q1 p6 a: R2 h
kg.generate_key()
/ Q) J& G  U9 q. x生成ETH公钥
# y4 j& P2 Y( j将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。
0 n8 h$ G: j: a1 ?6 a2 [  L# o, i/ t; M& q0 v
private_key_bytes = codecs.decode(private_key, 'hex')- K: J0 x7 `# r7 ?. C0 ]
# 獲得 ECDSA 公鑰. P2 L* s) k+ `3 O7 N
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
2 l; w4 m4 |- e2 `1 @, Q8 h* `key_bytes = key.to_string()
0 i5 }1 ^3 [3 x# \key_hex = codecs.encode(key_bytes, 'hex')7 ?* k% l7 A& d# K5 L; |+ @
钱包地址
; o" Y  \1 d4 p% x( t/ O4 E要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。
0 m+ Z9 ?1 ~5 X
+ L% D2 X) o) m- zpublic_key_bytes = codecs.decode(public_key, 'hex')
9 A) _/ [& }& w- u- Y: A" }4 Skeccak_hash = keccak.new(digest_bits=256)
1 g+ H! x6 z$ X5 Y3 \6 y: Akeccak_hash.update(public_key_bytes)
' j3 I' n! u& Skeccak_digest = keccak_hash.hexdigest()3 ?' ?/ @  u" W
# Take the last 20 bytes) y9 s7 x- p. G/ F$ X7 ]
wallet_len = 40- I+ N6 y% q; M
wallet = '0x' + keccak_digest[-wallet_len:]
. V. K& X* v# w4 _; G7 l7 g7 ~校验和 (ERC-55)
$ V0 ~% ?' C- e2 H9 M* X$ ?比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。
3 r# d- p8 j9 ]5 ?8 J' O- t) D' l. G但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。) M; J! s3 E, N
将校验和添加到以太坊钱包地址使其区分大小写2 d# S- a- T; S/ U  C+ {1 L) d
首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。
: y' O0 T) r! {: B其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。, ^" E4 {3 v) ]7 O
最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。" F: A% m; M- T. }  i
此校验和有几个好处:
$ ~9 o" t) ~1 {/ z( p1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;5 j& C% Z  O$ l  ^# X4 n
2.   保持长度为 40 个字符;4 e' ~$ M8 O; `  W
3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;( I3 P, d. ?- w+ B# K
3 B3 Z9 T. q6 V% U- g9 j  Q9 {7 W
checksum = '0x'
8 D5 N% `; N. `, s/ p# Remove '0x' from the address
! P; D' s7 ^* R+ R4 ~. _! K& ?address = address[2:]$ W. D/ U% q5 g2 Y. l8 h  N
address_byte_array = address.encode('utf-8')
7 c7 G- u' m. {" S0 ikeccak_hash = keccak.new(digest_bits=256)( F' H# o5 I' r/ y7 {. S
keccak_hash.update(address_byte_array)7 L% p# }0 T# L' F
keccak_digest = keccak_hash.hexdigest(); N5 X7 D0 B! K4 p
for i in range(len(address)):
4 s; b; A) q! a5 [# |; L/ p    address_char = address keccak_char = keccak_digest
+ s! f7 _7 h) w0 k    if int(keccak_char, 16) >= 8:  R  u5 c7 D7 y' m5 j
        checksum += address_char.upper()
# j  C/ B5 n. k1 D* P3 q    else:8 ]. i/ d5 v7 G/ R! `) w0 Q& `0 b
        checksum += str(address_char)
0 M( f7 I. {6 l总结) i3 ^( s# s  l8 j
为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9