Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
480 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。3 o. R. B# w: T4 N6 j4 i
有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。. ~- t' }1 C% D5 d% |
5 e1 Z5 m' d) `3 y/ r8 X
私钥的规格4 R7 H9 P; Q$ A0 I
私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。
& }* ]( b  e0 ], U$ Q3 z换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。# F5 S4 P# k1 `0 c! a: ~
  2 t7 r4 x/ E/ p. O
相同的私钥,以不同的格式编写。$ p' M. ], t- s$ p

% V/ c; c6 r5 k" r) w# e安全的私钥生成8 T" s( F. d1 @3 {% {
既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。) w! [8 S8 Q7 e& N3 i9 S' t* K

" U9 h. m- _! \" R+ f6 R了解 Bitaddress原理
$ U+ P, M3 |! ~; a& s- l: t) uBitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。
5 a- H$ Z. b: H6 q- {' @Bitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。
# a# V3 a) \7 V# I/ i程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。; M) a/ P  X6 {, ^8 z
初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。' Y! [% j0 @( i2 P
最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。
9 P2 j( O: p$ X: m3 l* w初始化我们自己的种子池
+ R6 ^2 {9 V$ G! E8 E我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。5 Q( P( \5 `. k' ?* u
2 a+ ^4 G. q( K1 [7 k4 t
def __init_pool(self):
/ W  w1 G/ X6 y: L6 M0 b4 N7 Y    for i in range(self.POOL_SIZE):
4 A6 C3 @8 Z7 Z; K4 h        random_byte = secrets.randbits(8)
" P; m" U: {8 {1 P        self.__seed_byte(random_byte)$ _+ N; b4 V- Q) E, k
    time_int = int(time.time())
* r4 B/ H6 i: M2 n! t% m/ |    self.__seed_int(time_int)' [7 p& j, G0 V; D: d
def __seed_int(self, n):
3 |7 `0 {+ E& R* M0 s/ ]. Y    self.__seed_byte(n)
( _: c+ F3 D) r! R/ j    self.__seed_byte(n >> 8)
2 v, U; ], H& a+ f0 z- ?. F    self.__seed_byte(n >> 16)
  ?! s" X/ w( ?. ?; k: ~2 [6 r    self.__seed_byte(n >> 24)
$ d8 @9 _* n/ }  a; l! kdef __seed_byte(self, n):
5 H' r/ m1 r1 M1 s' b    self.pool[self.pool_pointer] ^= n & 2555 r: U. a% b/ M6 ^& f! s" M. [$ T
    self.pool_pointer += 13 H, a) E9 _/ M0 d) j
    if self.pool_pointer >= self.POOL_SIZE:4 B9 e# I( G+ M8 F# q; q
        self.pool_pointer = 0
7 Q. c6 E9 T* r由输入填充种子池
' Z  T% A' F3 t8 B4 S5 \- d这里我们先写入一个时间戳,然后写入用户输入的字符串。, V+ s% t( @5 J9 c$ n
" T1 Y. P, h6 O+ Q/ G: C
def seed_input(self, str_input):
, w9 ~; h0 T0 T1 {8 R    time_int = int(time.time()): W3 f+ l: n$ J$ o4 Y" L3 R2 \% Q
    self.__seed_int(time_int)! A0 B  o2 a2 q- p
    for char in str_input:6 G$ r2 R, G. o; ]( S: V4 v
        char_code = ord(char)3 k- o9 b8 S! }# @) H' `' J; z: A- ~
        self.__seed_byte(char_code)
) y) Y% e# a9 V  V$ ~. c4 t* w  `& T生成私钥; Z  {5 F$ C* o7 s6 M
首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。" X( ^6 u6 W% B
7 g& W: G  C: @7 ?; S
def generate_key(self):
$ u- @1 {& U7 C" y; c    big_int = self.__generate_big_int(). u1 c. o- r" k, n6 X2 Z
    big_int = big_int % (self.CURVE_ORDER — 1) # key ' P8 _5 r7 B" B0 w8 r
    big_int = big_int + 1 # key > 0' x4 \  K9 f& E, r5 r1 E3 Y& c
    key = hex(big_int)[2:]8 O5 o4 r. v# L0 u% ^% x9 q6 E1 j9 B) `
    return key) A8 L$ a% v- U7 |/ b7 x
def __generate_big_int(self):4 G5 L1 V' a) P* }) j6 n! a
    if self.prng_state is None:
7 h3 N2 ?+ T5 c% j  a. I/ d      seed = int.from_bytes(self.pool, byteorder='big', signed=False)
. m/ r' G7 i* M5 k8 x      random.seed(seed)
0 X4 D1 y0 u; Z      self.prng_state = random.getstate()+ }% V1 E/ V( a* Z1 C( }  T
    random.setstate(self.prng_state)
  @9 B1 i& ^6 G6 p" I: |    big_int = random.getrandbits(self.KEY_BYTES * 8)
* a: N9 W) `8 S% h( c. z0 V    self.prng_state = random.getstate()
# C! x+ U( J& b1 I8 w1 ^    return big_int' ]' Z' `9 m* n; L' H) ]6 M
最后仅需三行就可以生成我们的私钥。, D/ ^, G8 L  M+ u" Y- `5 I% Y

6 x; S* t1 C8 Bkg = KeyGenerator()
* C( j  R- H: T3 M3 W( a+ {  C1 Mkg.seed_input(‘Truly random string. I rolled a dice and got 4.’), q, t8 j; n  w( ^4 _8 x. {& H
kg.generate_key()
3 R, U2 m9 y! U8 U& W" M) o: P生成ETH公钥
0 y0 W, P" p% o* B将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。
$ e1 q9 o7 J' A+ X  O( N' C, l1 x' K; t
1 }# z6 F7 ?7 z$ h' I! p2 f1 _5 tprivate_key_bytes = codecs.decode(private_key, 'hex')
" h1 @/ p0 i; N0 d" X1 N# 獲得 ECDSA 公鑰' D( b3 W) W- ~8 B
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
  v, ?& b7 j4 S+ Q+ K4 d( Nkey_bytes = key.to_string()/ B0 [% P- g' Y( T+ e( W
key_hex = codecs.encode(key_bytes, 'hex')( f4 [& N* ^! w3 D. y2 k. c- F- I
钱包地址0 d! I. C* h7 ~1 |5 Q& V
要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。
5 g3 ?% f4 T0 Z) R3 e& m- v8 D% K3 K( ^* `1 l
public_key_bytes = codecs.decode(public_key, 'hex')
# A. |! j9 P- Q# Gkeccak_hash = keccak.new(digest_bits=256)8 [. Q1 k0 y1 J  N/ _. a% b- d
keccak_hash.update(public_key_bytes)" V; j. Y, U" `, q+ v  w
keccak_digest = keccak_hash.hexdigest()- e0 m+ D2 b. c7 ~  c, l
# Take the last 20 bytes, C4 @& {) T9 T3 @
wallet_len = 40
8 N' p" o4 ~$ r- ]0 B( y, iwallet = '0x' + keccak_digest[-wallet_len:]4 @# D: D( i, ]9 s
校验和 (ERC-55)1 Z! l, ]$ e4 g! F, I1 t( Y. @
比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。
4 w/ W9 \3 M7 ^: K; g. F) e但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。* a$ Q4 K( h4 x0 ~; N0 \
将校验和添加到以太坊钱包地址使其区分大小写
" g; u) w1 U$ _3 L首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。
6 v8 U, Q0 v4 X: [) \# H其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。
/ ?) n1 \5 [, \/ B8 G- K# [9 T最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。
1 A' S  Q+ B4 g1 K- J8 S此校验和有几个好处:
/ t! R, w1 T: O0 v" `1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;) y) [1 k3 U" F6 Z+ m, [
2.   保持长度为 40 个字符;
, ]/ |" M# |& H9 \3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;
" l  y1 L$ e. S' y  M5 {* u* }; r. @# n5 ^- Q- a
checksum = '0x'
3 x1 P5 q/ o0 I9 h. |" W# Remove '0x' from the address/ O9 q% n, I" F; ^' A
address = address[2:]: Q2 t$ `" q. t3 r8 b* |& `2 [: Z: p
address_byte_array = address.encode('utf-8')
% V- K5 _2 r! y" v% q& }  Hkeccak_hash = keccak.new(digest_bits=256)
6 ~: _" \6 u. }: T; }7 wkeccak_hash.update(address_byte_array)& ?- z5 s1 O3 d! h. l
keccak_digest = keccak_hash.hexdigest()7 _6 t# y4 q2 P2 P$ N
for i in range(len(address)):$ T1 ~& w+ C( ?6 i+ J6 n) }
    address_char = address keccak_char = keccak_digest( a7 c! a1 F8 y  o4 L" V$ A4 T* i
    if int(keccak_char, 16) >= 8:, q" ?3 v8 g: B- b' R
        checksum += address_char.upper()( {. [% x0 m# b* R# z* m
    else:
# d9 Z5 l! k, y# L2 |        checksum += str(address_char)) ]" C3 a- M' q7 L1 F/ W
总结
0 {% e$ j8 m. S% w* P为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9