Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
369 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。, R+ G" i3 Z# J5 u9 ^$ v/ K
有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。
* y5 D& s) J2 [1 k3 f& Q; A: E/ G1 E& @7 n1 M6 S' k! n
私钥的规格
$ n# Q8 u( j8 K私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。
; n/ H: D4 A! a0 A换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。
9 P& b' j6 G( H! c% h( z* W% |" W  
7 ?! ?0 x/ e3 S# V  W% W7 X相同的私钥,以不同的格式编写。
  j) S/ v; N$ Q- |  B
7 f+ B1 W9 }/ O& t7 \安全的私钥生成; A/ w% [0 }( ^7 ^' v8 o1 _. I5 q
既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。+ U$ A9 e5 _3 ^7 y/ i
2 t* d0 x3 }: L; j& }
了解 Bitaddress原理
& O" b, y# C  BBitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。- r! j+ f9 o# |* H9 ?
Bitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。
! {* e4 y/ Y4 x* [& F程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。
( x9 b: H! F# j9 i4 a初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。9 i- X8 [; ~  x; U& \* }  p9 K" _
最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。5 y; ?( O- m: F/ e
初始化我们自己的种子池
1 w$ u, R- X: L# Y( Z+ w我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。
8 d. X, R* f2 ~+ W3 @! j5 G2 R1 }. Q3 G
def __init_pool(self):
; R1 F+ N8 I# F' P4 X. B: W    for i in range(self.POOL_SIZE):
2 C! _, H! y9 D  Y5 I% t        random_byte = secrets.randbits(8)
* \6 P, W7 F" k8 z+ o# g        self.__seed_byte(random_byte)/ i8 X& g5 k; y5 V
    time_int = int(time.time())5 C+ D: P4 L- ~4 J5 n
    self.__seed_int(time_int)& D, i# t5 v* O" [: u' q" `% Z" l
def __seed_int(self, n):& r' j: Y* T5 \
    self.__seed_byte(n)
9 Z2 x! V+ @" \* q* j% S, A, r, q    self.__seed_byte(n >> 8)
4 W+ G' ]7 p2 J- H* ]1 ?. _* `    self.__seed_byte(n >> 16)
4 z# h' S) C# [' j  b1 o4 l1 l    self.__seed_byte(n >> 24)
4 F- s, b) X0 \  S% i- O& c; ldef __seed_byte(self, n):
' a  _0 z2 b6 d8 g% p0 C/ G+ B2 p  o    self.pool[self.pool_pointer] ^= n & 255% G& J( f4 [; I2 ]) v' e
    self.pool_pointer += 1  |4 P1 C; C- B% |  J6 p3 e. @
    if self.pool_pointer >= self.POOL_SIZE:
5 r) f% A) T: A0 G        self.pool_pointer = 01 a8 X* R0 r4 j4 ]* W& A
由输入填充种子池
4 q! e* z# n& i9 V这里我们先写入一个时间戳,然后写入用户输入的字符串。! b; y3 J) @; h; K8 Q

( ]# q8 \- L, V4 M: u% {def seed_input(self, str_input):
: `2 n) G1 d: r    time_int = int(time.time()); v" c+ o2 [" K, g% w( `
    self.__seed_int(time_int)
7 ^  U3 d4 Z5 \- m! m    for char in str_input:6 D) `1 s' x5 D% M/ b
        char_code = ord(char)
& z+ J) O5 U% W7 d" p        self.__seed_byte(char_code)& p  g! J( H; Z$ t. W: z2 }* _
生成私钥/ p9 ^. k$ M6 T& g; V
首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。
& R# M# W. u- M, P$ |, r. U$ v$ d7 d2 [- t" l& c$ O
def generate_key(self):
/ }8 M8 ^3 h; [! G: @  c- j4 Z, ^    big_int = self.__generate_big_int()
% W; _; S% H2 P, K! e$ c4 T    big_int = big_int % (self.CURVE_ORDER — 1) # key
+ P- y5 _6 z0 L* |; B9 i    big_int = big_int + 1 # key > 0
* E1 N2 `1 c: J+ \5 a+ }$ W    key = hex(big_int)[2:]  e: P% e" S8 f, a! W6 J
    return key
' n9 [3 ?' j9 bdef __generate_big_int(self):
2 p) I: B2 e$ q- z% j) \    if self.prng_state is None:  Z- n6 i6 b- K/ k
      seed = int.from_bytes(self.pool, byteorder='big', signed=False)0 [0 A" v- S' x, G4 G
      random.seed(seed)
$ D! o  h9 l- ]* O7 Z/ O5 O. o, M      self.prng_state = random.getstate()
$ r1 z& }) O4 `1 ?4 W6 ]+ o    random.setstate(self.prng_state)
8 E5 O* f# u1 Z+ ^, D- A- z    big_int = random.getrandbits(self.KEY_BYTES * 8)
: O( O7 g) \9 k( T! J    self.prng_state = random.getstate()
% ^9 N+ a+ Q0 D0 `- U    return big_int
* v$ j' p" R# s4 n$ A+ U. R最后仅需三行就可以生成我们的私钥。
$ A+ G6 d4 S) ^' O" r8 L3 Y* o
. ?5 n; X4 R& J( p. ]1 N8 g/ {kg = KeyGenerator()( G. r0 r/ `, u* B' V& T4 U% r
kg.seed_input(‘Truly random string. I rolled a dice and got 4.’)
+ Y. p1 o. p7 Vkg.generate_key()
1 F# c1 j8 c6 t% S! Y  d/ ~+ |生成ETH公钥
1 ?+ D1 `: J* F. }' t9 s" m: O将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。
7 L; B/ z4 Y, R# V! X
7 w' x7 \7 u/ L! h: l8 n- W. ?% lprivate_key_bytes = codecs.decode(private_key, 'hex')7 |8 ~& K7 G6 U$ l0 X! P
# 獲得 ECDSA 公鑰1 R6 {' r& d2 t+ Q2 Y1 k
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
" @: M) @9 \6 O+ c+ Lkey_bytes = key.to_string()9 l7 G5 L. B) F2 i4 T
key_hex = codecs.encode(key_bytes, 'hex')3 v! @9 W, O8 [# [* f4 g
钱包地址
1 N; f. O# B- t要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。
, @) P2 `. |! D4 _
7 j5 |7 }3 c  w, P" o- R2 _public_key_bytes = codecs.decode(public_key, 'hex')
' e! p4 r2 T$ K7 `  r/ jkeccak_hash = keccak.new(digest_bits=256), C$ L0 X: P9 \
keccak_hash.update(public_key_bytes)$ l, c9 n+ T) s) c5 w
keccak_digest = keccak_hash.hexdigest()7 Y/ }3 v% l/ l5 f0 _
# Take the last 20 bytes  H' t3 t' X9 r
wallet_len = 40. H7 K6 V! R# r9 B( X& H
wallet = '0x' + keccak_digest[-wallet_len:]- q6 @, F/ ]0 A7 y
校验和 (ERC-55)
! T$ u/ M, g; C. ~# ^! _: g3 E' |比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。- u: ?9 s' u2 g" P5 T' ~) e
但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。; y9 s/ f4 m  V; b/ x+ G  j
将校验和添加到以太坊钱包地址使其区分大小写
, O% ^; E; l& X' w: q首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。8 u8 z" ?1 T) x; Y- {: [% F
其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。5 d: ]$ n  y2 [$ I
最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。
6 o+ Y" I; d# e/ a- h此校验和有几个好处:/ u4 @5 `1 d/ J
1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;
7 z% S- x9 W! ~9 M+ b- n$ A' ~2.   保持长度为 40 个字符;
7 S# i- ?* @+ z% u3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;  B# j/ J8 B: k5 _5 {- F9 Y

! z, t: d. X7 G+ }2 fchecksum = '0x'/ _1 q5 g4 S' X0 }
# Remove '0x' from the address
% u+ {; ?8 V+ p& g3 j$ H! [address = address[2:]7 ?' g) F- A5 f! v" b) G
address_byte_array = address.encode('utf-8')
7 R% [. ]* X2 M2 ^! O, g. \0 {keccak_hash = keccak.new(digest_bits=256)0 ~5 b! _) e9 J
keccak_hash.update(address_byte_array)
( O+ t- R. `( n& ^5 D1 ekeccak_digest = keccak_hash.hexdigest()
7 B$ ^# g: O8 D7 W4 n* Pfor i in range(len(address)):
- `2 E! m$ J! o    address_char = address keccak_char = keccak_digest
- Z0 c. C9 F0 V' N2 s    if int(keccak_char, 16) >= 8:
3 W  P3 Q. n6 o! g5 j4 s3 f        checksum += address_char.upper()/ [# J# f9 e) H8 g3 o  ^# I/ \+ w; z
    else:
  L7 [, T! {5 j4 W% D2 O% b7 K        checksum += str(address_char)
/ z7 _4 }  g3 Q* U4 X总结0 k2 S& }3 o2 Y, [. C' b8 i
为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9