如何使用ECDSA創(chuàng)建公鑰
什么是私鑰?
一般我們看到的私鑰是這樣的一段字符串:5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss
支持比特幣協(xié)議的應(yīng)用都可以正確把這段字符串轉(zhuǎn)換成比特幣的私鑰,再轉(zhuǎn)換出公鑰,再得到一個(gè)地址,如果該地址上面有對(duì)應(yīng)的比特幣,就可以使用這個(gè)私鑰花費(fèi)上面的比特幣。
私鑰本質(zhì)上是隨機(jī)數(shù)私鑰本質(zhì)上是一個(gè)隨機(jī)數(shù),由32個(gè)byte組成的數(shù)組,1個(gè)byte等于8位二進(jìn)制,一個(gè)二進(jìn)制只有兩個(gè)值0或者1。所以私鑰的總數(shù)是將近2^(8*32)=2^256個(gè),但是有一些私鑰并不能使用,他真實(shí)的大小是介于:1 ~ 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141之間的數(shù)。這個(gè)數(shù)量已經(jīng)超過(guò)了宇宙中原子的總數(shù),想要遍歷所有的私鑰,耗盡整個(gè)太陽(yáng)的能量也是不可能的。
我們所說(shuō)的比特幣私鑰的是密碼學(xué)上面安全的,并不是說(shuō)不可能出現(xiàn)重復(fù)的私鑰,而是說(shuō)不可能通過(guò)遍歷的方式找到某一個(gè)特定的私鑰,或者通過(guò)其它的方式找,而不通過(guò)私鑰就能花費(fèi)地址上面的比特幣,私鑰的安全性是由數(shù)學(xué)上保證的。
私鑰的總數(shù)量很大,但是私鑰的生成是依賴隨機(jī)數(shù)的,真正的隨機(jī)是很難做到的,大部分私鑰的生成都是依賴于偽隨機(jī)算法(PRNG)。
偽隨機(jī)是用函數(shù)生成隨機(jī)數(shù)。它并不真正是隨機(jī)的。只是一個(gè)比較近似真隨機(jī)的隨機(jī)數(shù)。
私鑰生成的隨機(jī)性就很重要的,密碼學(xué)上安全的隨機(jī)是指:
隨機(jī)是不可預(yù)測(cè)的,隨機(jī)的結(jié)果是不可遍歷的,如果不是安全的隨機(jī)數(shù)生成器,生成的私鑰就有可能被別人碰撞到。不依賴隨機(jī)生成的私鑰就會(huì)大大的降低其生成的概率空間。
什么是公鑰?
公鑰是由數(shù)字和字母組成的另一個(gè)地址,這些數(shù)字和字母是通過(guò)使用數(shù)學(xué)函數(shù)加密后從私鑰派生出來(lái)的。加密過(guò)程是不可逆轉(zhuǎn)的,因此沒(méi)有人能夠找到原始的私鑰。這個(gè)地址可以讓你接收比特幣。
公鑰的哈希值總是1,它看起來(lái)是這樣的:1 bvbmseystwetqtfn5au4m4gfg7xjanvn2
這個(gè)地址您可以公開(kāi)提供,以便接收比特幣。用戶可以生成的公共地址數(shù)量沒(méi)有限制。為了生成這樣的密鑰并隨后生成錢(qián)包地址,必須對(duì)私鑰進(jìn)行多次轉(zhuǎn)換。這些轉(zhuǎn)換稱為哈希函數(shù),是不可逆的轉(zhuǎn)換。
使用ECDSA創(chuàng)建公鑰
你要做的第一件事就是將ECDSA應(yīng)用到你的私鑰上,也就是橢圓曲線數(shù)字簽名算法。定義的一個(gè)橢圓曲線方程為y2= x3+ ax + b, a和b為選定值。比特幣利用的是secp256k1曲線。
對(duì)私鑰應(yīng)用ECDSA將得到一個(gè)64字節(jié)的整數(shù),該整數(shù)由兩個(gè)32字節(jié)的整數(shù)組成,它們表示橢圓曲線上點(diǎn)的X和Y。
下面是用Python語(yǔ)言編寫(xiě)的代碼:
private_key_bytes = codecs.decode(private_key, ‘hex’)
# Get ECDSA public key
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
key_bytes = key.to_string()
key_hex = codecs.encode(key_bytes, ‘hex’)
在上面給出的代碼中,使用編程器對(duì)私鑰進(jìn)行解碼。在Python中,至少有兩個(gè)類可以保存私鑰和公鑰:“str”、字符串?dāng)?shù)組和“bytes”——字節(jié)數(shù)組,事情可能會(huì)變得有點(diǎn)混亂。
這是因?yàn)閄字符串?dāng)?shù)組不等于X字節(jié)數(shù)組,但它等于有兩個(gè)元素的字節(jié)數(shù)組O《。codecs.decode方法將字符串轉(zhuǎn)換成字節(jié)數(shù)組。
在應(yīng)用ECDSA之后,我們必須將字節(jié)0x04(04作為前綴)添加到生成的公鑰中。這將生成一個(gè)完整的比特幣公鑰。
壓縮公鑰
我們可以將公鑰壓縮得更短,而不是使用公鑰的長(zhǎng)版本。
這是通過(guò)從ECDSA公鑰中獲取X并在Y的最后一個(gè)字節(jié)是偶數(shù)時(shí)添加0x02,如果最后一個(gè)字節(jié)是奇數(shù),則添加0x03字節(jié)。
使用SHA-256和RIPEMD-160加密密鑰
現(xiàn)在我們繼續(xù)創(chuàng)建錢(qián)包地址。不管應(yīng)用于公鑰的方法是什么,過(guò)程都是相同的。顯然,您將得到不同的結(jié)果地址。
為此,我們需要應(yīng)用兩個(gè)哈希函數(shù): 首先,我們將SHA-256應(yīng)用于公鑰,然后使用RIPEMD-160加密結(jié)果。非常重要的是,算法應(yīng)用的順序要準(zhǔn)確。
在這個(gè)過(guò)程的最后,您將得到一個(gè)160位整數(shù),它表示加密的公鑰。
下面是在Python中加密公鑰所需的代碼:
public_key_bytes = codecs.decode(public_key, ‘hex’)
# Run SHA-256 for the public key
sha256_bpk = hashlib.sha256(public_key_bytes)
sha256_bpk_digest = sha256_bpk.digest()
# Run RIPEMD-160 for the SHA-256
ripemd160_bpk = hashlib.new(‘ripemd160’)
ripemd160_bpk.update(sha256_bpk_digest)
ripemd160_bpk_digest = ripemd160_bpk.digest()
ripemd160_bpk_hex = codecs.encode(ripemd160_bpk_digest, ‘hex’)
添加網(wǎng)絡(luò)字節(jié)
由于比特幣有兩個(gè)網(wǎng)絡(luò),主網(wǎng)和測(cè)試網(wǎng),我們需要?jiǎng)?chuàng)建一個(gè)地址在主網(wǎng)使用。這意味著我們必須向加密的公鑰中添加0x00字節(jié)。對(duì)于測(cè)試網(wǎng)的使用,您必須添加0x6f字節(jié)。
計(jì)算校驗(yàn)和
下一步是計(jì)算得到的主網(wǎng)密鑰的校驗(yàn)和。校驗(yàn)和確保密鑰在整個(gè)過(guò)程中仍然保持其完整性。如果校驗(yàn)和不匹配,地址將被標(biāo)記為無(wú)效。
為了生成密鑰的校驗(yàn)和,必須應(yīng)用SHA-256哈希函數(shù)兩次,然后從這個(gè)結(jié)果中取前4個(gè)字節(jié)。請(qǐng)記住,4個(gè)字節(jié)代表8個(gè)十六進(jìn)制數(shù)字。
計(jì)算校驗(yàn)和所需的代碼是:
# Double SHA256 to get checksum
sha256_nbpk = hashlib.sha256(network_bitcoin_public_key_bytes)
sha256_nbpk_digest = sha256_nbpk.digest()
sha256_2_nbpk = hashlib.sha256(sha256_nbpk_digest)
sha256_2_nbpk_digest = sha256_2_nbpk.digest()
sha256_2_hex = codecs.encode(sha256_2_nbpk_digest, ‘hex’)
checksum = sha256_2_hex[:8]
創(chuàng)建地址所需的最后一步是合并主網(wǎng)密鑰和校驗(yàn)和。
用Base58編碼密鑰
您將注意到,生成的密鑰看起來(lái)不像其他BTC地址。這是因?yàn)榇蠖鄶?shù)將它們轉(zhuǎn)換為Base58地址。
下面是將十六進(jìn)制地址轉(zhuǎn)換為Base58地址所需的算法:
ef base58(address_hex):
alphabet = ‘123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz’
b58_string = ‘’
# Get the number of leading zeros
leading_zeros = len(address_hex) — len(address_hex.lstrip(‘0’))
# Convert hex to decimal
address_int = int(address_hex, 16)
# Append digits to the start of string
while address_int 》 0:
digit = address_int % 58
digit_char = alphabet[digit]
b58_string = digit_char + b58_string
address_int //= 58
# Add ‘1’ for each 2 leading zeros
ones = leading_zeros // 2
for one in range(ones):
b58_string = ‘1’ + b58_string
return b58_string
結(jié)果字符串將代表壓縮的比特幣錢(qián)包地址。
結(jié)論
如果您密切關(guān)注上述步驟,那么從私鑰生成比特幣錢(qián)包地址的過(guò)程并不困難。如果您的私鑰已滿或已壓縮,即使生成的地址將看起來(lái)不同,但它們都是有效的。