Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

人民干脆面
350 0 0
在区块链的钱包中,私钥可以产生出公钥,而反过来要想从公钥推算出私钥则是不可能的。用公钥加密的信息可以用私钥来解密,而用私钥签名的信息则由公钥来验证,验证通过后才能证明该信息确实为私钥持有人所发布。以 BTC 为例的话,在这个过程中最重要的角色的就是" 椭圆曲线加密算法"。- N5 Z! c* r6 _. o
有些人会以为 BTC 跟 ETH 是不同的链所以用的椭圆曲线并不相同,但事实上两个链使用的都是相同的 secp256k1 曲线,所以获得公钥的方式完全一样,差别在从公钥生成地址的过程,接下来我们会先介绍如何安全的生成私钥,然后说明 ETH 如何从地址验证由私钥生成的公钥。* f: w$ [2 e5 J. M  V; L
6 o4 z9 `: g* F2 ]6 b5 \
私钥的规格: p% D* {: j7 k+ w  l. m
私钥必须为正整数且必须小于 secp256k1 曲线的阶 (secp256k1 的阶为FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141),每个点可由一组 256位代表,而 256 位正好是 32 个字节,所以我们需要提供这个曲线算法 32 个字节的数据。  w5 i# }: [' D9 N$ j
换句话说,BTC 及 ETH的私钥都是一组 32 字节的字符串,但它也可以是二进制字符串、Base64字符串、WIF 密钥、助记码( mnemonic phrase )、十六进制字符串。
" O% |$ ?2 p( L$ B! p  2 D  u& @) q1 @* f7 ]
相同的私钥,以不同的格式编写。
8 ~: W6 y, P5 X6 g. @  ?1 B$ m" [; H4 m4 D1 R) [3 C+ x2 m
安全的私钥生成
3 k$ Z3 k6 R' r# x4 B& ~& i* s既然都知道他们使用的是同一条曲线,那我们其实就可以使用 BTC 社群比较信任的 bitaddress.org 来生成我们的私钥,(用 MEW 或 Metamask 也都是不错的选择,至少他可以不是一串裸露在外的私钥),但如果有良好安全意识的话,我们甚至不应该用浏览器来生成我们重要的私钥 (可以看看 Reddit 上的讨论),所以我们将用 python 设计一个更简单的 bitaddress。& S7 k" K4 d' l0 K' X9 a) K+ b, i
, A6 K/ e% q+ \" I* R
了解 Bitaddress原理9 a# q# `8 _& n; T/ c5 O
Bitaddress 做了三件事情。首先,初始化字节数组,然后尝试从用户的计算机获得尽可能多的熵,根据用户的输入填满数组,最后生成私钥。8 \% x, Z$ h  _3 @& P4 o* T
Bitaddress 使用 256 字节的数组来存储熵。这个数组是被循环覆写的,所以当数组第一次填满时,索引变为零,然后覆写过程再次开始。) \+ K8 G- x( G
程序从 window.crypto 生成一个 256 字节的数组。然后写入一个时间戳来获得 4 个字节的熵。在这之后,它获得一些其他的数据包括屏幕大小,时区,浏览器扩充套件,地区等。来获得另外 6 个字节。3 ^" s+ [6 E+ i# M+ D8 H) N
初始化后,使用者持续输入来覆写初始字节。当移动光标时,程序会写入光标的位置。当按下按钮时,程序会写入按下的按钮的字符代码。
/ a3 }8 r5 c) y; i最后,bitaddress 使用累积的熵来生成私钥。bitaddress 使用名为 ARC4 的 RNG算法。用当前时间以及收集的熵初始化ARC4,然后逐个取得字节,总共取 32 次。5 Q# E9 Q, k/ e% b; A5 `; N$ u2 O3 g
初始化我们自己的种子池$ x% o4 g4 [! g! J" A! X5 L
我们从加密 RNG 和时间戳中写入一些字节。__seed_int 以及__seed_byte是将熵插入池的数组中的两个函式,而我们使用secrets生成我们的随机数。
1 ?% y  F! ]* h7 t$ f
( u2 o$ B. i6 g/ V' i, z& q' Pdef __init_pool(self):
9 N6 T" A0 v, W- x0 x    for i in range(self.POOL_SIZE):
# A3 I& j! l1 m, e: e5 V* m        random_byte = secrets.randbits(8)
/ j- s$ b* T4 n        self.__seed_byte(random_byte)6 ]$ _, j  Z- z8 u# J& e+ Y
    time_int = int(time.time())/ o& F, `1 r' e1 h
    self.__seed_int(time_int)
' ]* S  w: O( E) n% bdef __seed_int(self, n):
; d, C, b. Z0 w2 A! V3 [    self.__seed_byte(n)- w2 J) [; f1 a, |5 P# S! o
    self.__seed_byte(n >> 8)! q/ e: i! i6 V  v7 d/ {1 R
    self.__seed_byte(n >> 16)
! n3 r( S9 i4 x' |, ]+ Y# T    self.__seed_byte(n >> 24)& Q: m* t/ d" J8 S: G
def __seed_byte(self, n):! K- b8 z7 I, W/ {" d7 [' ~
    self.pool[self.pool_pointer] ^= n & 255! e: G7 h$ l- {; {) F
    self.pool_pointer += 1
/ V. E  U7 |/ X! r' Q  A: T0 K: e    if self.pool_pointer >= self.POOL_SIZE:
% i6 o* o2 n& `& N6 ]! s( V        self.pool_pointer = 0" A9 d# r, q4 d: K  F' |% a
由输入填充种子池
$ b7 O& V: c, n1 D5 m这里我们先写入一个时间戳,然后写入用户输入的字符串。* k( D7 q6 ^7 y* g

4 P" {! n1 q3 c$ ?' O5 ~9 O# idef seed_input(self, str_input):) P* J* Z; R) ?& n4 ^% ~. o& v5 L
    time_int = int(time.time())
! t& S# G$ A1 @+ f9 @8 m    self.__seed_int(time_int)
& d$ M3 I# b2 E, k. E    for char in str_input:& U3 f- A# v' D9 ~
        char_code = ord(char), f1 S) E+ ^$ e* V' |: d
        self.__seed_byte(char_code)2 N/ a" F1 M  u( ?# o2 K
生成私钥
7 ^2 m. L* e" L9 k4 ]0 d首先使用我们的池生成 32 位的数字,并确保我们的私钥在范围内(1, CURVE_ORDER),然后为了方便,我们转为十六进制并删除 0x 的部分。
% Y; G% Z' |% m1 c1 s- e( {1 s9 V8 z4 @' A3 B
def generate_key(self):( C" h3 }: {9 ^- ~
    big_int = self.__generate_big_int()1 Y& f6 _. n: |  ~: }
    big_int = big_int % (self.CURVE_ORDER — 1) # key
0 z4 Q; b4 z! N  U    big_int = big_int + 1 # key > 0
) f9 \2 d0 U4 q4 d9 B% c. R    key = hex(big_int)[2:]
) J) \6 Z% c9 b# I# ~: T0 \) U( r    return key
4 }' m. M6 w; A4 Jdef __generate_big_int(self):
+ h% G4 q0 W. P+ J; o    if self.prng_state is None:
% w& D+ l8 A4 B& ~      seed = int.from_bytes(self.pool, byteorder='big', signed=False)
- ^3 j2 A/ }) T      random.seed(seed)
1 G  F5 A; k+ j' x7 q      self.prng_state = random.getstate(), _1 B$ b# c5 M9 H/ V
    random.setstate(self.prng_state)
/ X. J7 r$ q% E) f) A& e    big_int = random.getrandbits(self.KEY_BYTES * 8)
4 P; P# u) F1 p! r: T- e% O    self.prng_state = random.getstate()
9 F# j/ S2 Y$ h0 P8 h) N    return big_int, s0 G" K* D( v% w
最后仅需三行就可以生成我们的私钥。
: S% h( w/ d4 U5 q( x; S0 o% Q. k2 L! N2 S4 x+ m8 u7 B
kg = KeyGenerator()
! E- T" t" H& O. J  Nkg.seed_input(‘Truly random string. I rolled a dice and got 4.’)5 H. ?5 r+ n: |+ P$ D5 m
kg.generate_key()& }& N& T/ m" ~6 v' R/ u$ e2 i7 n
生成ETH公钥
9 N9 ~) \9 M" v: {0 k6 i将我们刚刚的私钥代入椭圆曲线,我们会得到一个 64 字节的整数,它是两个 32 字节的整数,代表椭圆曲线上连接在一起的 X 点和 Y 点。* R) F, y7 B, K

1 s# C& ]4 |" [. |5 t: C& R8 hprivate_key_bytes = codecs.decode(private_key, 'hex'), h8 z+ B! U7 w! ^
# 獲得 ECDSA 公鑰
! k/ j% v  I: J# Nkey = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key2 v/ N! M( M; Q2 _
key_bytes = key.to_string(), @$ |5 Y1 O/ U/ x
key_hex = codecs.encode(key_bytes, 'hex')
( F: m' [3 r( _0 N% j1 x8 @. }钱包地址) i# t/ D5 M9 t. k) V' _
要从公钥创建地址时,我们只需要将公钥带入 Keccak-256 (你可能会听到一些人称呼他为"卡咖256"),然后获得回传值的最后 20 个字节。没有 Base58 或任何其他转换,唯一需要的是在地址的开头添加 0x。1 A  }* J5 f0 V% \4 e

, r3 ?# x% @! W% p2 G9 L5 tpublic_key_bytes = codecs.decode(public_key, 'hex')
7 y/ j$ v5 z4 j) I/ {' Dkeccak_hash = keccak.new(digest_bits=256)3 ^& g& i% E. S8 i
keccak_hash.update(public_key_bytes)
3 x& I# x1 P5 A# q0 g* a5 vkeccak_digest = keccak_hash.hexdigest()$ G0 D- m  H2 z, N% A. N& o
# Take the last 20 bytes
" d8 N9 ^* @3 R  gwallet_len = 40
# M7 `* ~8 p( @5 C5 \8 Uwallet = '0x' + keccak_digest[-wallet_len:]3 G& G( v2 N- H8 ?6 T
校验和 (ERC-55)  ^3 T( A. F5 W$ v5 R, w" ^
比特币通过将公钥哈希后并获得回传值的前 4 个字节来创建校验和,如果不添加校验和则无法获得有效地址。3 Y  R1 y1 S/ I1 s
但以太坊一开始并没有校验和机制来验证公钥的完整性。直到 Vitalik Buterin 在 2016 年时引入了校验和机制,也就是 EIP-55,并且后来被各家钱包和交易所采用。, _/ ?" Q4 Y8 s" e. j3 J4 x
将校验和添加到以太坊钱包地址使其区分大小写
' m8 z4 {1 F' W) `& ?首先,获得地址的 Keccak-256 哈希值。需要注意的是,将此地址传递至哈希函数时不能有0x的部分。
9 f/ ]/ y, ~' J" T4 X2 Z3 ~其次,依序迭代初始地址的字节。如果哈希值的第 i 个字节大于或等于 8,则将第 i 个地址的字符转换为大写,否则将其保留为小写。
* A: d' I0 T# I  J8 w最后,在回传的字符串开头加回0x。如果忽略大小写,校验和地址会与初始地址相同。但使用大写字母的地址让任何人都能检验地址是否有效。% n; j( c# T6 ^* C2 G9 e( m
此校验和有几个好处:
" U7 u- M  L! {! o. d% ?1.     向后兼容许多接受混合大小写的十六进制解析器,将来也能轻松引入;
; X- P. |) K+ X$ I4 b  i2.   保持长度为 40 个字符;4 y2 s2 R2 p; i& y$ ~: C
3.   平均每个地址将有 15 个校验位,如果输入错误,随机生成的地址意外通过检查的净概率将为0.0247%,虽然不如 4 字节的校验代码好,但比 ICAP 提高了约 50 倍;% j3 }$ J! W  H$ I! B) G

9 ^- U7 ?8 ?6 t' K5 zchecksum = '0x'
1 a9 g! M) |( K# Remove '0x' from the address
5 J+ A+ c  l' T  V" @7 caddress = address[2:]
! {+ i9 g$ _0 K: eaddress_byte_array = address.encode('utf-8')
' \0 N7 y8 X, ikeccak_hash = keccak.new(digest_bits=256)
$ }& O8 J; x' |/ C0 rkeccak_hash.update(address_byte_array)
4 |! M( k) `$ X  V* M5 R% okeccak_digest = keccak_hash.hexdigest()3 G2 _' k$ {0 I4 y1 |
for i in range(len(address)):
% ]. w% |& n/ n    address_char = address keccak_char = keccak_digest. u9 \/ F8 m8 Z/ B$ b! B
    if int(keccak_char, 16) >= 8:6 X, I6 u3 h- }, q
        checksum += address_char.upper()
5 \3 _# x0 \$ {0 X    else:
! o3 c# y/ f) n+ c4 ^        checksum += str(address_char)
  Z# K- N# y8 Q- |& F5 W/ }: `总结
) P# h& e1 b, j4 o$ u- W  j* T为以太坊创建钱包地址相较于比特币简单得多。我们需要做的就只是将私钥丢到椭圆曲线,然后再把得到的公钥丢到Keccak-256,最后撷取该哈希值的后面 20 个字节。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

人民干脆面 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    9