Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
403 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。
& c$ y' ^: h  _有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。5 X, l" q+ `) v; L
. L/ U) g: B1 j" [0 ?8 s
私钥的规格- o+ a2 y* M1 S( D. c% ?; `0 X
私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。1 X/ N, k* Z) A) s6 y' s2 g
换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。
, L$ U9 V7 D! f5 P& ]3 O- h  
, K* P9 ^& y- P: G+ n相同的私钥,以不同的格式编写。5 B' i3 U' ?- ^3 z$ f

# Q1 j% p" Q) T3 [0 C) c7 E安全的私钥生成2 y3 _- H; `( r4 H) Q2 d
既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。
" ?3 c- P' J" p# p3 Q$ c8 M1 l0 F: N+ M6 b$ g% ?( g
了解 Bitaddress原理. _8 l1 h1 {5 H2 C
Bitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。
' B2 L) j6 |% Z2 ~Bitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。4 C  E4 E; U- V" @+ L7 s9 S& B
程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。4 \( X5 ^5 W$ R' K
初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。
$ C1 Z$ ~; D, Q* w+ X' i最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。
: L# h3 j  r! V8 u2 z3 c1 E初始化我们自己的种子池
7 O9 h: P; l# ]  z我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。
7 p. ^' S5 S3 m  Z5 o0 X5 I4 O* ]9 J7 s0 l% g* ]' k
def __init_pool(self):, n; y' d7 Z& Q' V( M! r6 k
    for i in range(self.POOL_SIZE):3 q. i( S, r$ P' j" T' p0 T5 o
        random_byte = secrets.randbits(8)
2 V6 c. {' R) ^. T" E: W        self.__seed_byte(random_byte)' ?5 {# f6 l8 G3 v% V. m1 Y, V
    time_int = int(time.time())$ d8 @+ V& v6 q4 P
    self.__seed_int(time_int)8 h+ K4 B4 c+ q( L" v4 s/ P, Q' k
def __seed_int(self, n):2 T( A4 ^5 X- R7 B& E+ \3 h
    self.__seed_byte(n)1 n# U  }: v9 R" D  i' p; z; f
    self.__seed_byte(n >> 8)6 k$ f" t! S# ?* |/ A# A5 X$ b
    self.__seed_byte(n >> 16)9 f! h: R+ g9 K, _3 |
    self.__seed_byte(n >> 24)$ F6 B) l1 ~# [- f. I3 \! |  z
def __seed_byte(self, n):
. L. ]1 V: N% w2 k& L0 J5 q' o    self.pool[self.pool_pointer] ^= n & 255
4 C  y4 h* Z" ]( k0 n6 O3 M    self.pool_pointer += 1
+ T6 E( ^1 A$ u; M$ w+ Z    if self.pool_pointer >= self.POOL_SIZE:* S9 s, [+ [2 Y/ _( c) J4 E+ ~
        self.pool_pointer = 0
& [$ F) R" b1 F% T6 I由输入填充种子池+ o: T0 o' l3 r) E  V6 V2 E
这里我们先写入一个时间戳,然后写入用户输入的字符串。
: u0 ?- i7 P# f+ E* G2 e9 o  N* d2 x, T8 L: u
def seed_input(self, str_input):7 Y( S! \: a9 y& f( b! M; L
    time_int = int(time.time()). v5 d$ i6 u' J( R  Q% k, {8 w: h
    self.__seed_int(time_int)
6 \0 ]6 }5 N& A. S8 w# V/ p    for char in str_input:
' m9 i2 V* H: }" k# o+ M4 @0 R( j        char_code = ord(char)
5 `# s/ D* d. S. v8 V9 h% ]        self.__seed_byte(char_code)4 E  q: }5 u2 @; G0 ?# i/ H
生成私钥1 k* a9 }* }4 c
首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。
: E) E6 |7 _# p( B- L) y- z6 ^1 O% p" v( G" {- ~
def generate_key(self):
* ~" H: y7 ^7 }. C% K  B  I    big_int = self.__generate_big_int()( G- N; Z0 `3 u' G/ I, e3 \  S7 {
    big_int = big_int % (self.CURVE_ORDER — 1) # key * P0 a! a1 I: [8 s7 a& c1 q
    big_int = big_int + 1 # key > 09 {  n+ `7 k2 C2 R- }+ X
    key = hex(big_int)[2:]
; T2 \/ e4 x$ }* w3 }2 _    return key' {) L% A% Y) A. ~) L  h
def __generate_big_int(self):
5 E* v0 n0 g  E2 f: q    if self.prng_state is None:
3 M/ ]9 ]3 q( [      seed = int.from_bytes(self.pool, byteorder='big', signed=False)/ T/ R0 ~( @1 {5 E3 o: y2 l; P. j
      random.seed(seed)0 ]% v) w$ a1 E' _7 @
      self.prng_state = random.getstate()
! ^. w- T7 s/ P$ r    random.setstate(self.prng_state)  h9 E# p: c% k. c0 h& W4 B1 X- m  a
    big_int = random.getrandbits(self.KEY_BYTES * 8)/ r/ v; Q5 o; w) _5 n7 M
    self.prng_state = random.getstate()$ A6 h" M" y% @
    return big_int
' E+ ?: r& U# ]1 c8 O最后仅需三行就可以生成我们的私钥。+ x8 m1 z2 X" D5 @
/ [" h+ o& ?7 T$ b( e
kg = KeyGenerator()- D. E# M) V# g, V) j9 @4 L6 q9 |
kg.seed_input(‘Truly random string. I rolled a dice and got 4.’)
3 `3 B) H: O. Y. W* N5 d- a2 Okg.generate_key()- m- K  ~2 \. R  q
生成ETH公钥4 S7 h! q% B/ [" }7 B" d  Y
将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。2 r, s  V( m, Y( C% z

! @! ~9 x- M$ a8 B/ h7 ]0 g) Hprivate_key_bytes = codecs.decode(private_key, 'hex')
' _& T: w) k1 X" G. e( E- {) @0 o! Z# 獲得 ECDSA 公鑰6 Y- d% N3 U" b0 g; D
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key6 R- U* F% D2 z5 X$ S0 o/ u- I8 J
key_bytes = key.to_string()
& }9 b& X9 P: m6 A7 ]key_hex = codecs.encode(key_bytes, 'hex')/ G: k* d3 [. K& p6 D# H7 T
钱包地址
- m4 p8 }7 N4 Z* X% Z0 B: D& y要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。
+ J& y( U  t. g* c3 E0 }& x2 v+ B+ K5 m  z& c; B
public_key_bytes = codecs.decode(public_key, 'hex')
( M3 q; B0 V6 x0 h* Bkeccak_hash = keccak.new(digest_bits=256)
6 g2 d2 U5 K  X2 J+ b; ukeccak_hash.update(public_key_bytes)
: k( R' @; d) R/ i5 U0 r, dkeccak_digest = keccak_hash.hexdigest()% B# k% [6 X, q; \: B5 Q
# Take the last 20 bytes
) u$ k5 V/ h' ^% X1 ]% d, Mwallet_len = 40
! f) E, n2 \) C9 Pwallet = '0x' + keccak_digest[-wallet_len:]- {& i2 }( n& l+ a) C& J
校验和 (ERC-55)
4 l5 X7 M* U5 X0 m比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。& X0 ^: _) U/ y2 x& I0 H
但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。
% y" @  `) x4 z- ^" M& Q将校验和添加到以太坊钱包地址使其区分大小写
2 n7 X/ d0 _4 K6 K1 f首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。
9 K7 n  [6 j, I! \) c其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。$ F: p6 U( o( i3 Q- L! H& f0 e
最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。; d7 h4 f# h" v: f) p9 }, Z
此校验和有几个好处:! i0 {$ |1 X: X, ^' Z
1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;
; P" ?8 Y# r! H" x4 n- [2.   保持长度为 40 个字符;
! Y7 L8 ^  p% O, A, y# {( T3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;$ A' A# D* b9 ^* `" |. y* _

$ a, o8 t1 q% [4 Pchecksum = '0x'
3 `  ^, g  [/ E3 {) v8 h& M# Remove '0x' from the address" g# C0 R, a& H+ I
address = address[2:]4 t8 x* p% Y, o% ]. C3 w/ L2 j$ ~
address_byte_array = address.encode('utf-8')
# [1 A  [; `! Z7 Tkeccak_hash = keccak.new(digest_bits=256)
* Q1 `0 w3 c7 ~5 H# I/ g# Akeccak_hash.update(address_byte_array)4 ~. ?5 R3 K3 A! h4 y8 L# I
keccak_digest = keccak_hash.hexdigest()
7 ]$ `, d  J& l) B0 {. dfor i in range(len(address)):5 X0 D$ b( O( p$ `' i
    address_char = address keccak_char = keccak_digest! K4 D0 z- u: Z8 J# v2 X9 M0 ]5 U
    if int(keccak_char, 16) >= 8:% f+ f; m2 K$ S2 J  S- w; X' ?( Q
        checksum += address_char.upper()3 a0 b! G0 f) E2 [0 f) G4 P2 C
    else:( a1 z1 Q7 Y4 y
        checksum += str(address_char)- P) p6 J3 x/ |6 i$ J" p
总结1 f/ j3 N4 X9 N5 g2 N/ W9 z
为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9