Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
163 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。5 G( L5 W" i, F( Y, O
有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。
* D3 I8 |9 W. l8 f
( e$ T) @9 R+ S/ w% D; u  s私钥的规格' g: U* l  ~8 t/ A% q# N9 w, k9 I
私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。7 W( G( q: x* f9 c# {& O
换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。
5 g6 T9 Y% S, w5 ?: B; u  * m5 Q9 l) u3 ~$ i6 f9 A9 L' J3 Y
相同的私钥,以不同的格式编写。
! D/ H! U, _2 b6 [6 U8 N" _* t' c, t* d/ L+ m( n9 U0 ]
安全的私钥生成# o- ?; p$ @5 w2 _8 A9 e7 s
既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。
% n) _' N' E! H8 O6 e  D+ T+ `( C) h2 q4 p, J* A+ O- V1 z. H
了解 Bitaddress原理$ N0 f3 ?0 R" G# m) v. E
Bitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。1 J1 y6 q1 |& a% q; C$ u0 A
Bitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。6 `9 b; [! k: k3 t* b
程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。) X: l, z4 b1 g6 x- Y5 e: q6 M
初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。1 p+ k* @1 O. o5 V
最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。/ A6 N9 C- _( W1 ]
初始化我们自己的种子池
4 \5 k/ V, D# I3 V0 Q, y( f2 {我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。
2 [% `1 j2 [; ~6 B7 [& g! a
: k% ^3 f* \- @- Jdef __init_pool(self):
* O2 P$ w2 ?: @    for i in range(self.POOL_SIZE):
- p( |) M7 M5 P        random_byte = secrets.randbits(8)
0 Q& R; p( V- u$ [1 L. |6 T# q        self.__seed_byte(random_byte)
+ j' z5 ~, ~- v! @, S& e( c    time_int = int(time.time())
* e7 F- B, Q. U2 K    self.__seed_int(time_int)
+ @  l4 e" e  e& cdef __seed_int(self, n):
- p/ V- r, Q# W  ?% S2 C/ W3 w% v    self.__seed_byte(n)0 r1 D' x5 G; Q8 L( L
    self.__seed_byte(n >> 8)
2 |6 B3 T0 s, {9 P; N    self.__seed_byte(n >> 16): P- D+ e; n7 ^) z
    self.__seed_byte(n >> 24)
) v; p+ x0 K, P' }$ Y3 Mdef __seed_byte(self, n):
- {" H& u2 R  c" u. ^    self.pool[self.pool_pointer] ^= n & 255% l% Z# ~; B0 O
    self.pool_pointer += 1% k" i; a6 i3 [1 i, `  u/ J
    if self.pool_pointer >= self.POOL_SIZE:
% H& L/ }9 n* J% J6 U4 m        self.pool_pointer = 0
9 i1 }+ t2 C- e, c由输入填充种子池: P5 U9 l% \7 c8 L/ W
这里我们先写入一个时间戳,然后写入用户输入的字符串。; F5 ?! r' i; w: [
3 q. A4 N1 A  T9 e
def seed_input(self, str_input):
0 c% n4 G( \3 R9 x    time_int = int(time.time())! w/ G& ?- O. e, j% T' Y
    self.__seed_int(time_int)
& U; Z  |+ H+ Y( c$ A% o  i1 v    for char in str_input:6 k/ ^& J3 F4 b7 P4 t6 u, N4 j
        char_code = ord(char)% @/ r- I+ z# W- O  H! c! W0 W$ O
        self.__seed_byte(char_code)7 ]/ O  }; q% m% K/ l8 O
生成私钥+ ]6 o1 K0 x- j. d( R0 m
首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。
( m% C2 h! w3 B) J+ C; F5 ]9 l( I! m( ~% k" ~3 v
def generate_key(self):% e! b* s, L4 u/ |
    big_int = self.__generate_big_int(): E. a5 g" B" y" a0 k" @
    big_int = big_int % (self.CURVE_ORDER — 1) # key
7 d: C( J6 x. y+ S+ I- ^    big_int = big_int + 1 # key > 05 Q' [; ?1 C- V/ g5 Q! S
    key = hex(big_int)[2:]3 u! M. ?$ z# q* V% L
    return key+ o& j, S. @& `
def __generate_big_int(self):
% K; B: n' O. I  ~4 V0 e& R: q    if self.prng_state is None:1 q% [7 \+ ?( p! ]9 P+ c
      seed = int.from_bytes(self.pool, byteorder='big', signed=False)8 Q7 l% L( i) U  v
      random.seed(seed)* [9 Q& v& `, i0 D8 W
      self.prng_state = random.getstate()3 m7 L( i: Q  E9 I! W. U( e5 |
    random.setstate(self.prng_state)& ?* G1 ~% y$ \. p+ v0 B, C
    big_int = random.getrandbits(self.KEY_BYTES * 8)( c# t9 ^5 L7 Y  h" z& M, {
    self.prng_state = random.getstate()3 B" o, E2 _/ }8 O% J
    return big_int$ ]$ d% U4 Q! n: \0 d
最后仅需三行就可以生成我们的私钥。
; v% U! Q, H3 w  _# o- a% b( a' [4 p& b; N( v
kg = KeyGenerator()6 X8 G+ f* k0 Z
kg.seed_input(‘Truly random string. I rolled a dice and got 4.’)
- X$ i7 _! a  vkg.generate_key(); Q/ O- P; q' D2 o8 P% i
生成ETH公钥
/ I. S0 U: t" U" N) K将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。
( L" p/ c! p: Y+ C0 s8 k- f1 e: {' K! l1 x9 m) K: Q/ a
private_key_bytes = codecs.decode(private_key, 'hex')
* Z1 `! ^0 c8 G- a4 g$ j  @# 獲得 ECDSA 公鑰% Y1 @9 G1 E4 b" u0 b" \; y
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
& d% E/ m8 l1 Ukey_bytes = key.to_string()
9 Z$ W4 }  T, J) @# kkey_hex = codecs.encode(key_bytes, 'hex')
8 j5 s$ q9 A* z: ^钱包地址
* P+ x8 `2 X& d4 L' V) e6 E要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。9 ^) C9 J, J) h. v8 O
' D; _! B9 a9 }/ @
public_key_bytes = codecs.decode(public_key, 'hex')
% b2 y0 N% C! J& Tkeccak_hash = keccak.new(digest_bits=256)
  G" M& @& Y' Z% Ckeccak_hash.update(public_key_bytes)
, ^( F6 }8 h0 y3 T( p, Vkeccak_digest = keccak_hash.hexdigest()
* I- F- c  _7 u# Take the last 20 bytes& ~# U, e7 @% t' c0 j# Q+ L7 o
wallet_len = 40. G8 `8 [* v9 h/ N+ y* L. F3 ~
wallet = '0x' + keccak_digest[-wallet_len:]
  h$ x  M0 X" {3 R) E5 U校验和 (ERC-55)4 j" v& i' O; r  M% K. P
比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。+ _. s3 e$ C7 U0 O: h' b
但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。1 s7 n+ O; g6 q; n7 i% l
将校验和添加到以太坊钱包地址使其区分大小写4 l' S( O0 }5 j7 ~0 }, a4 [
首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。3 d1 e# `0 Q' ]5 w; [/ S* B! `
其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。$ N' P) Y* a) f4 [! e: c+ ?5 D7 r
最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。9 T6 \% d$ E; N
此校验和有几个好处:
3 P: ~5 B" j* ?- _; d3 D1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;
$ i+ t; A4 ?! T" p3 B* O2 @9 }* W  F2.   保持长度为 40 个字符;
1 ~8 \1 g5 h+ T  ]0 t3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;
" m/ c" F  Q8 p4 |3 z: ^9 X3 ^, \/ r
checksum = '0x'( ~) Z0 r1 U2 G9 w4 H+ v, w
# Remove '0x' from the address
1 e: o/ C$ a. U( f! n2 yaddress = address[2:]" Y/ {1 p6 ], F* o8 H
address_byte_array = address.encode('utf-8'): p* ^* m2 G* V
keccak_hash = keccak.new(digest_bits=256)5 [, \2 C1 ]8 M' o$ Q( B( [4 q
keccak_hash.update(address_byte_array)' f$ L9 h% k; |8 \
keccak_digest = keccak_hash.hexdigest()
. K9 h; t* S% l0 [% h- _4 ffor i in range(len(address)):' O) p/ D  U2 u# J
    address_char = address keccak_char = keccak_digest
# e/ v  E9 A) K3 H' A    if int(keccak_char, 16) >= 8:
  P. j9 Y4 ?. q0 u! C( l5 b        checksum += address_char.upper()
% N9 y% M1 g0 o8 @& D) V- U3 B5 V    else:. `* J! A' T6 O% k
        checksum += str(address_char)
: P; S2 M! D, @8 I- z总结
2 f8 J# s: a$ @% [8 o为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9