What is Nostr?
Balenciaga
npub1edq…90e9
2023-02-16 17:46:44

Balenciaga on Nostr: 《比特币地址生成算法详解》 1 生成过程 ...

《比特币地址生成算法详解》

1 生成过程

第一步,随机选取一个32字节的数,大小介于1~0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141之间,作为私钥

18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725

第二步,使用椭圆曲线加密算法(ECDSA-SECP256k1)计算私钥所对应的非压缩公钥(共65字节,1字节0x04,32字节为x坐标,32字节为y坐标)。

0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6

第三步,计算公钥的SHA-256哈希值

600FFE422B4E00731A59557A5CCA46CC183944191006324A447BDB2D98D4B408

第四步,计算上一步哈希值的RIPEMD-160哈希值

010966776006953D5567439E5E39F86A0D273BEE

第五步,在上一步结果之间加入地址版本号(如比特币主网版本号"0x00")

00010966776006953D5567439E5E39F86A0D273BEE

第六步,计算上一步结果的SHA-256哈希值

445C7A8007A93D8733188288BB320A8FE2DEBD2AE1B47F0F50BC10BAE845C094

第七步,再次计算上一步结果的SHA-256哈希值

D61967F63C7DD183914A4AE452C9F6AD5D462CE3D277798075B107615C1A8A30

第八步,取上一步结果的前4个字节(8位十六进制数)D61967F6,把这4个字节加在第五步结果的后面,作为校验(这就是比特币地址的16进制形态)

00010966776006953D5567439E5E39F86A0D273BEED61967F6

第九步,用base58表示法变换一下地址(这就是最常见的比特币地址形态)

16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM

下面给出比特币地址生成的python源码

1 import hashlib
2 from ecdsa import SECP256k1, SigningKey
3 import sys
4 import binascii
5
6 # 58 character alphabet used
7 BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
8
9 def from_bytes (data, big_endian = False):
10 if isinstance(data, str):
11 data = bytearray(data)
12 if big_endian:
13 data = reversed(data)
14 num = 0
15 for offset, byte in enumerate(data):
16 num += byte << (offset * 8)
17 return num
18
19 def base58_encode(version, public_address):
20 """
21 Gets a Base58Check string
22 See https://en.bitcoin.it/wiki/Base58Check_encoding
23 """
24 if sys.version_info.major > 2:
25 version = bytes.fromhex(version)
26 else:
27 version = bytearray.fromhex(version)
28 firstSHA256 = hashlib.sha256(version + public_address)
29 print("first sha256: %s"%firstSHA256.hexdigest().upper())
30 secondSHA256 = hashlib.sha256(firstSHA256.digest())
31 print("second sha256: %s"%secondSHA256.hexdigest().upper())
32 checksum = secondSHA256.digest()[:4]
33 payload = version + public_address + checksum
34 print("Hex address: %s"%binascii.hexlify(payload).decode().upper())
35 if sys.version_info.major > 2:
36 result = int.from_bytes(payload, byteorder="big")
37 else:
38 result = from_bytes(payload, True)
39 # count the leading 0s
40 padding = len(payload) - len(payload.lstrip(b'\0'))
41 encoded = []
42
43 while result != 0:
44 result, remainder = divmod(result, 58)
45 encoded.append(BASE58_ALPHABET[remainder])
46
47 return padding*"1" + "".join(encoded)[::-1]
48
49 def get_private_key(hex_string):
50 if sys.version_info.major > 2:
51 return bytes.fromhex(hex_string.zfill(64))
52 else:
53 return bytearray.fromhex(hex_string.zfill(64))
54
55 def get_public_key(private_key):
56 # this returns the concatenated x and y coordinates for the supplied private address
57 # the prepended 04 is used to signify that it's uncompressed
58 if sys.version_info.major > 2:
59 return (bytes.fromhex("04") + SigningKey.from_string(private_key, curve=SECP256k1).verifying_key.to_string())
60 else:
61 return (bytearray.fromhex("04") + SigningKey.from_string(private_key, curve=SECP256k1).verifying_key.to_string())
62
63 def get_public_address(public_key):
64 address = hashlib.sha256(public_key).digest()
65 print("public key hash256: %s"%hashlib.sha256(public_key).hexdigest().upper())
66 h = hashlib.new('ripemd160')
67 h.update(address)
68 address = h.digest()
69 print("RIPEMD-160: %s"%h.hexdigest().upper())
70 return address
71
72 if __name__ == "__main__":
73 #private_key = get_private_key("FEEDB0BDEADBEEF")
74 private_key = get_private_key("18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725")
75 print("private key: %s"%binascii.hexlify(private_key).decode().upper())
76 public_key = get_public_key(private_key)
77 print("public_key: %s"%binascii.hexlify(public_key).decode().upper())
78 public_address = get_public_address(public_key)
79 bitcoin_address = base58_encode("00", public_address)
80 print("Final address %s"%bitcoin_address)


2 关键问题

2.1 base58编码

Base58编码是一种二进制转可视字符串的算法,主要用来转换大整数,将整数字节流转换为58编码流,实际上它就是整数的58进制,和2进制、8进制、16进制是一样的道理,只是用58作为进制的单位了,正好和58个不容易混淆的字符对应,比特币所用的字符表如下:

123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

该表去除了几个看起来会产生歧义的字符,如0(零)和O(大写字母O),I(大写的字母i)和l(小写的字母L)等。另外,比特币在实现base58编码时,开头的0做了特殊处理,所以可以将输入流开头的0直接填充到结果前边。以00000000000000000000000000000000000000000094a00911为例,最后非零整数为0x94a00911(2493516049),2493516049除以58商是42991656余数是1,1对应的base58编码是2,42991656除以58商是741235余数是26,26对应的base58编码是T,741235除以58商是12779余数是53,53对应的base58编码是v,12779除以58商是220余数是19,19对应的base58编码是L,220除以58商是3余数是46,46对应的base58编码是o,3对应的base58编码是4,000000000000000000000000000000000000000000对应着21字节的0,所以最终的base48编码为1111111111111111111114oLvT2。

2.2 比特币地址个数

而从比特币地址的生成过程可知,第四步RIPEMD-160算法的结果是20字节(160位)的数,该步是比特币地址的最小限制,所以理论上来说比特币合法地址总是应该是2^160个。另外比特币私钥是32字节(256位)的随机数,并且有一定大小范围的限制,所以合法的比特币私钥个数介于2^255~2^256之间,从而可以看出私钥个数远远大于比特币地址数,所以理论上应该存在多个私钥对应同一地址的情况。

2.3 比特币地址长度

一般BTC地址的长度是34位,也有33位,其实可以通过推理可知(当地址版本为00时),BTC最短长度是26位,最长长度是34位。由第1节第八步可知比特币地址的16进制形态有25个字节,所以理论上最短的地址应该是16进制地址00000000000000000000000000000000000000000000000000对应的base58编码地址1111111111111111111111111,但是由第八步可知以上所说的16进制地址的最后4个字节对应的是0000000000000000000000000000000000000000的两次哈希的最后4个字节即94a00911,所以对应的地址是00000000000000000000000000000000000000000094a00911对应的base58编码地址1111111111111111111114oLvT2,其长度是27位,该地址也是所谓的“燃烧地址”(burn address),BTC地址的正常推导过程是:私钥==>公钥==>Hash160<==>地址,Hash160是没有规则的字符串,我们可以随便提供个Hash160值,从半截腰上直接推导地址:Hash160<==>地址,这时候要想花费该地址上对应的比特币,必须知道Hash160串对应的私钥,而这近乎是一个无解的难题,没有私钥该地址就只能进不能出,进入的币再也不可用了,就像燃料被烧掉了一样, 非常形象。

这里( http://gobittest.appspot.com/Address )提供了从比特币原始私钥到公钥再到HASH160和BASE58的计算程序,可以参考。
Author Public Key
npub1edqu9zdylp6h6f9t079clyzu30qmcd7vsywymjl22ehj5vv90avshv90e9