最近学校课程作业遇到了该问题,即如何将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上查看
这里我们使用的是比特币使用的一条椭圆曲线,即
它由我们的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))
hash256=hashlib.sha256() hash256.update(unhexlify(compressed_public_key)) Cpublic_hash256=hash256.hexdigest() log.success('compressed public_key hash256 :'+(Cpublic_hash256))
ripemd=hashlib.new('ripemd160') ripemd.update(unhexlify(Cpublic_hash256)) Cpublic_ripemd=ripemd.hexdigest() log.success('compressed public_key hash & ripmed :'+(Cpublic_ripemd))
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=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
|
至此我们的计算就结束啦,:)