从比特币WIFC私钥到地址

从比特币WIFC私钥到地址

四月 10, 2020

最近学校课程作业遇到了该问题,即如何将WIFC私钥转化为地址,因此就做了一番学习,在这做一下记录,因为也是初次接触相关内容,如果文中有错误的地方敬请斧正

借鉴的网站

1
2
3
4
https://aaron67.cc/2019/01/04/bitcoin-address/
https://steemit.com/cn/@oflyhigh/bitcoin-base58-and-base58check-private-key-public-key-address
https://zhuanlan.zhihu.com/p/82093348
https://zhuanlan.zhihu.com/p/30290735

什么是比特币地址?

我们所称的比特币地址可以通俗的理解为我们的银行卡号,我们可以同时拥有多个地址,与此同时,通过区块链也可以查明每个地址的所有的转账记录

一般来说我们的比特币地址有多种形式,其中最常用的两种一个是以1开头的P2PKH地址,即通过一对私钥和公钥控制的钱包地址

那么另一个就是以3开头的P2SH地址

而如果是测试网络,那么会以2,m,n等作为开头,以此来避免我们将币错误的发到测试网络中

WIF私钥

我们的WIF是 Wallet Import Format 的一个缩写,他有着压缩和不压缩两种形式,即压缩WIF compressed(以下简写为WIFC),不压缩WIF uncompressed(以下简写为WIF)

但无论是否压缩,他们都是我们的私钥通过base58check编码之后得到的

那么他们的区别是什么呢?

我们的WIF,是在我们的私钥前面加上0x80这个前缀之后,对这个新产生的值做两次sha256的操作,并将这个计算结果的前四个4节作为我们的验证位

那么我们的WIF就是 80+私钥+验证位 做一个base58编码的结果,其会以数字5开头

那么我们的WIFC呢?

WIFC的计算与WIF相似,其不同之处在于其刚开始计算时不仅要加0x80的前缀,还需要在结尾增加一个压缩标志后缀,即0x01,剩下的结果就与之前没有区别了,做两次sha256,取前四个字节为验证位

WIFC: 80+私钥+01+验证位 做一次base58编码,他会以我们的K或L开头

从WIFC->Address

我们所需的流程为:WIFC->private_key->public_key->public_key_hash->address

从WIF->privite key

那么既然我们知道了我们的WIF和WIFC是如何计算的,那么我们就可以知道我们的私钥改如何逆推回去了

首先我们拿一个WIFC私钥为例:’Kwa6hgpEfzwcsvcTCetNjfkPtcCgVt9suVo4zjtBp7At17GAQZUi’

看到其以K开头就知道是我们的WIFC私钥,然后我们先对其进行一次base58check的解码

我们就可以得到:

1
2
3
4
5
6
7
8
9
10
11
12
from binascii import hexlify, unhexlify
from pwn import *
import base58

compressedWIF='Kwa6hgpEfzwcsvcTCetNjfkPtcCgVt9suVo4zjtBp7At17GAQZUi'

private_salt=hexlify(base58.b58decode_check(compressedWIF))
log.success('private_salt:'+(private_salt))


运行结果:
[+] private_salt:800a7d0d1b60ebe37292229505ca8f36a9ab78bee9ce03a2e43ebfd8fb905947d601

那么先不向下进行,我们先做一次base58的解码

1
2
3
4
5
6
7
8
9
10
11
from binascii import hexlify, unhexlify
from pwn import *
import base58

compressedWIF='Kwa6hgpEfzwcsvcTCetNjfkPtcCgVt9suVo4zjtBp7At17GAQZUi'

private_salt=hexlify(base58.b58decode(compressedWIF))
log.success('private_salt:'+(private_salt))

运行结果:
[+] private_salt:800a7d0d1b60ebe37292229505ca8f36a9ab78bee9ce03a2e43ebfd8fb905947d601325f50cf

从这里我们就能看出我们base58_check解码和base58解码得到结果的一个区别了

我们的base58_check会自动去除我们的校验位,而base58则不会

那么到了这里,我们只需要去除我们的80前缀和01压缩后缀即可得到我们的私钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from binascii import hexlify, unhexlify
from pwn import *
import base58

compressedWIF='Kwa6hgpEfzwcsvcTCetNjfkPtcCgVt9suVo4zjtBp7At17GAQZUi'

private_salt=hexlify(base58.b58decode(compressedWIF))
log.success('private_salt:'+(private_salt))

private_key=private_salt[2:len(private_salt)-10]
log.success('private_key:'+(private_key))

运行结果:
[+] private_salt:800a7d0d1b60ebe37292229505ca8f36a9ab78bee9ce03a2e43ebfd8fb905947d601325f50cf
[+] private_key:0a7d0d1b60ebe37292229505ca8f36a9ab78bee9ce03a2e43ebfd8fb905947d6

从private key -> public key

那么到此我们就得到了我们的private_key,现在需要转化为我们的公钥了

那么我们如何通过私钥转化为公钥呢?这里就不得不说椭圆曲线了

椭圆曲线的具体实现流程我就在这里不用大篇幅来讲,他主要依靠于我们解决椭圆曲线离散对数问题十分困难这一点,详情可以从wiki上查看

这里我们使用的是比特币使用的一条椭圆曲线,即

GTMhOs.png

它由我们的secp256k1标准来定义,其中p为一个很大的素数

而我们的公钥,是这条椭圆曲线上的一个点:

K=k*G

其中,我们的k就是我们的私钥,而G为一个固定的点

由于我们椭圆曲线离散对数问题这一数学难题,因此我们无法从公钥逆推回私钥,从而保证了我们私钥的安全性

知道了公钥如何计算,下面我们就可以得到公钥了,这里需要注意的是我们的公钥也是分为压缩公钥和非压缩公钥的

那么他们又有什么区别呢?

首先通过计算,我们可以得到公钥的x轴坐标和y轴坐标,那么如果我们想要得到我们的非压缩公钥,就十分的简单了

只需要在前面加一个表示非压缩的前缀0x04即可,其最终格式为:

04 + K.x + K.y

其中K.x为公钥横坐标,K.y为公钥纵坐标

那么我们如何得到非压缩公钥呢?

由于我们曲线的特殊性,我们只需要知道x轴坐标和y轴坐标的奇偶性就可以推算出y的值,那么也就有了一个定义

如果y轴坐标为奇数,那么x轴的前缀就需要添加0x03,如果为偶数则需要添加0x02

那么我们压缩公钥的格式就为:

前缀(0x03||0x02) + K.x

这里我们就来计算我们的公钥的值吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from secp256k1 import PrivateKey
from binascii import hexlify, unhexlify
from pwn import *
import base58

compressedWIF='Kwa6hgpEfzwcsvcTCetNjfkPtcCgVt9suVo4zjtBp7At17GAQZUi'

private_salt=hexlify(base58.b58decode(compressedWIF))
log.success('private_salt:'+(private_salt))

private_key=private_salt[2:len(private_salt)-10]
log.success('private_key:'+(private_key))

privkey=PrivateKey(unhexlify(private_key))
uncompressed_public_key = hexlify(privkey.pubkey.serialize(compressed=False)).decode('ascii')
log.success('uncompressed public_key:'+(uncompressed_public_key))

compressed_public_key=hexlify(privkey.pubkey.serialize()).decode('ascii')
log.success('compressed public_key:'+(compressed_public_key))


运行结果:
[+] private_salt:800a7d0d1b60ebe37292229505ca8f36a9ab78bee9ce03a2e43ebfd8fb905947d601325f50cf
[+] private_key:0a7d0d1b60ebe37292229505ca8f36a9ab78bee9ce03a2e43ebfd8fb905947d6
[+] uncompressed public_key:041decbd8ff0c1b8e328bab5ea9794ebcffbcba7f8e8c1e604a193bea543ef13160cfafec08b79973f519843ec8f724302f454dba89224389c42e8fb3b4444148b
[+] compressed public_key:031decbd8ff0c1b8e328bab5ea9794ebcffbcba7f8e8c1e604a193bea543ef1316

public_key->public_key_hash

得到了我们的public_key,我们就可以来计算我们的public_key_hash了

计算public_key_hash需要我们的压缩密钥,也就是我们前面计算出来的

031decbd8ff0c1b8e328bab5ea9794ebcffbcba7f8e8c1e604a193bea543ef1316

那么我们首先需要对他做一次hash256,并对结果做一次RIPEMD160的计算,这样得出来的值就是我们的public_key_hash了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from secp256k1 import PrivateKey
from binascii import hexlify, unhexlify
from pwn import *
import hashlib
import base58

compressedWIF='Kwa6hgpEfzwcsvcTCetNjfkPtcCgVt9suVo4zjtBp7At17GAQZUi'

private_salt=hexlify(base58.b58decode(compressedWIF))
log.success('private_salt:'+(private_salt))

private_key=private_salt[2:len(private_salt)-10]
log.success('private_key:'+(private_key))

privkey=PrivateKey(unhexlify(private_key))
uncompressed_public_key = hexlify(privkey.pubkey.serialize(compressed=False)).decode('ascii')
log.success('uncompressed public_key:'+(uncompressed_public_key))

compressed_public_key=hexlify(privkey.pubkey.serialize()).decode('ascii')
log.success('compressed public_key:'+(compressed_public_key))

#first hash256
hash256=hashlib.sha256()
hash256.update(unhexlify(compressed_public_key))
Cpublic_hash256=hash256.hexdigest()
log.success('compressed public_key hash256 :'+(Cpublic_hash256))

#ripemd160(hash256())
ripemd=hashlib.new('ripemd160')
ripemd.update(unhexlify(Cpublic_hash256))
Cpublic_ripemd=ripemd.hexdigest()
log.success('compressed public_key hash & ripmed :'+(Cpublic_ripemd))

运行结果:
[+] private_salt:800a7d0d1b60ebe37292229505ca8f36a9ab78bee9ce03a2e43ebfd8fb905947d601325f50cf
[+] private_key:0a7d0d1b60ebe37292229505ca8f36a9ab78bee9ce03a2e43ebfd8fb905947d6
[+] uncompressed public_key:041decbd8ff0c1b8e328bab5ea9794ebcffbcba7f8e8c1e604a193bea543ef13160cfafec08b79973f519843ec8f724302f454dba89224389c42e8fb3b4444148b
[+] compressed public_key:031decbd8ff0c1b8e328bab5ea9794ebcffbcba7f8e8c1e604a193bea543ef1316
[+] compressed public_key hash256 :08fdc7cea4b50c4280e33bfe7ba7a0d19d8235cbc94a78e1e977bdd13ffbc5db
[+] compressed public_key hash & ripmed :ff452ce82e7b9e157e0f7abd188d75ab698c74cf

从public_key_hash->Address

有了我们的哈希值,现在就是最后一步了,即计算我们的地址,我们将我们的哈希值记为H

下面就先给我们的H加一个前缀0x00,然后对这个结果做两次sha256的运算

取这个运算结果的前8个字节作为校验码,最后得到的格式即为:

0x00 + H +校验码

此时再对该校验码做一个base58的计算即可得出我们的地址啦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
from secp256k1 import PrivateKey 
from binascii import hexlify, unhexlify
from pwn import *
import hashlib
import base58

compressedWIF='Kwa6hgpEfzwcsvcTCetNjfkPtcCgVt9suVo4zjtBp7At17GAQZUi'

private_salt=hexlify(base58.b58decode(compressedWIF))
log.success('private_salt:'+(private_salt))

private_key=private_salt[2:len(private_salt)-10]
log.success('private_key:'+(private_key))

privkey=PrivateKey(unhexlify(private_key))
uncompressed_public_key = hexlify(privkey.pubkey.serialize(compressed=False)).decode('ascii')
log.success('uncompressed public_key:'+(uncompressed_public_key))

compressed_public_key=hexlify(privkey.pubkey.serialize()).decode('ascii')
log.success('compressed public_key:'+(compressed_public_key))

#first hash256
hash256=hashlib.sha256()
hash256.update(unhexlify(compressed_public_key))
Cpublic_hash256=hash256.hexdigest()
log.success('compressed public_key hash256 :'+(Cpublic_hash256))

#ripemd160(hash256())
ripemd=hashlib.new('ripemd160')
ripemd.update(unhexlify(Cpublic_hash256))
Cpublic_ripemd=ripemd.hexdigest()
log.success('compressed public_key hash & ripmed :'+(Cpublic_ripemd))

#hash256
Cpublic_ripemd=b'\x00'+unhexlify(Cpublic_ripemd)
log.success('add "\x00": '+hexlify(Cpublic_ripemd))
hash256=hashlib.sha256()
hash256.update(Cpublic_ripemd)
has1=hash256.hexdigest()
log.success('hash256 1st :'+(has1))

#hash256
hash256=hashlib.sha256()
hash256.update(unhexlify(has1))
has2=hash256.hexdigest()
log.success('hash256 2nd :'+has2)

Cpublic_ripemd=Cpublic_ripemd+unhexlify(has2[:8])
log.success('modified value : '+hexlify(Cpublic_ripemd))

address=base58.b58encode(Cpublic_ripemd)
log.success('Address : '+(address))


运行结果:
[+] private_salt:800a7d0d1b60ebe37292229505ca8f36a9ab78bee9ce03a2e43ebfd8fb905947d601325f50cf
[+] private_key:0a7d0d1b60ebe37292229505ca8f36a9ab78bee9ce03a2e43ebfd8fb905947d6
[+] uncompressed public_key:041decbd8ff0c1b8e328bab5ea9794ebcffbcba7f8e8c1e604a193bea543ef13160cfafec08b79973f519843ec8f724302f454dba89224389c42e8fb3b4444148b
[+] compressed public_key:031decbd8ff0c1b8e328bab5ea9794ebcffbcba7f8e8c1e604a193bea543ef1316
[+] compressed public_key hash256 :08fdc7cea4b50c4280e33bfe7ba7a0d19d8235cbc94a78e1e977bdd13ffbc5db
[+] compressed public_key hash & ripmed :ff452ce82e7b9e157e0f7abd188d75ab698c74cf
[+] add "\x00": 00ff452ce82e7b9e157e0f7abd188d75ab698c74cf
[+] hash256 1st :ecfc308eb6cb6f444711fe9d16d35713e46a2dda0c2185e550c6fee0a9587e96
[+] hash256 2nd :597599af8a17b0e24787ba83210b8636a7abe91814eb6b24568d09f3c324fa19
[+] modified value : 00ff452ce82e7b9e157e0f7abd188d75ab698c74cf597599af
[+] Address : 1QGkBJve12oY6CQTKfvoTZv26YvUi6ZL4e

至此我们的计算就结束啦,:)