就像我們在銀行的賬戶密碼一樣,決定比特幣一個賬戶所有權的是與賬戶相關的三個核心概念:私鑰、公鑰和比特幣地址。比特幣錢包和交易與它們息息相關。
(一) 私鑰、公鑰和錢包地址(比特幣地址)之間的關係
每個比特幣錢包中包含一系列的密鑰對,每個密鑰對包括一個私鑰和一個公鑰。私鑰(k)是一個數字,通常是隨機選出的。有了私鑰,我們就可以使用橢圓曲線乘法這個單向加密函數產生一個公鑰(K)。有了公鑰(K),我們就可以使用一個單向加密哈希函數生成比特幣地址(A)。
事實上,比特幣地址可以看作一種公鑰。
他們之間的關係如上圖所示,私鑰可以通過橢圓加密算法生成公鑰,公鑰可以通過哈希函數生成比特幣地址。反之,無法從比特幣地址推斷生成公鑰,也無法從公鑰推斷生成私鑰,這是由後面要介紹的非對稱加密算法所實現的。
值得一提的是,掌握私鑰就能生成相應的公鑰和比特幣地址(錢包地址),相當於掌握了整個賬戶,所以我們一定要保管好自己的私鑰。
(二)私鑰、公鑰和錢包地址(比特幣地址)生成流程圖:
1. 首先使用隨機數發生器生成一個『私鑰』。一般來說這是一個256bits的數,擁有了這串數字就可以對相應『錢包地址』中的比特幣進行操作,所以必須被安全地保存起來。
2. 『私鑰』經過SECP256K1算法處理生成了『公鑰』。SECP256K1是一種橢圓曲線算法,通過一個已知『私鑰』時可以算得『公鑰』,而『公鑰』已知時卻無法反向計算出『私鑰』。這是保障比特幣安全的算法基礎。
3. 同SHA256一樣,RIPEMD160也是一種Hash算法,由『公鑰』可以計算得到『公鑰哈希』,而反過來是行不通的。
4. 將一個字節的地址版本號連接到『公鑰哈希』頭部(對於比特幣網絡的pubkey地址,這一字節為“0”),然後對其進行兩次SHA256運算,將結果的前4字節作為『公鑰哈希』的校驗值,連接在其尾部。
5. 將上一步結果使用BASE58進行編碼(比特幣定製版本),就得到了『錢包地址』。
比如, 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
(三)加密算法在比特幣錢包和交易中的應用場景。
『私鑰』用來生成『公鑰』和『錢包地址』,也用來對交易進行簽名。擁有了『私鑰』就是擁有了對這個錢包餘額的一切操作權力。
(1)使用『私鑰』對交易進行簽名
比特幣錢包間的轉賬是通過交易(Transaction)實現的。交易數據是由轉出錢包『私鑰』的所有者生成,也就是說有了『私鑰』就可以花費該錢包的比特幣餘額。生成交易的過程如下:
1. 交易的原始數據包括“轉賬數額”和“轉入錢包地址”,但是僅有這些是不夠的,因為無法證明交易的生成者對“轉出錢包地址”餘額有動用的權利。所以需要用『私鑰』對原始數據進行簽名。
2. 生成“轉出錢包公鑰”,這一過程與生成『錢包地址』的第2步是一樣的。
3. 將“轉出簽名”和“轉出公鑰”添加到原始交易數據中,生成了正式的交易數據,這樣它就可以被廣播到比特幣網絡進行轉賬了。
(2)使用『公鑰』對簽名進行驗證
交易數據被廣播到比特幣網絡後,節點會對這個交易數據進行檢驗,其中就包括對簽名的校驗。如果校驗正確,那麼這筆餘額就成功地從“轉出錢包”轉移到“轉入錢包”了。
(四)生成私鑰、公鑰、比特幣地址的示例代碼。
橢圓加密算法使用的是 python 的 ecdsa 庫,哈希算法使用的是 hashlib 。 另外沒講到的部分是 base58Checkencode , 這是在表示比特幣地址時所用的方法,可以把地址壓縮得更短 ,使得表示更為清晰。
(1)首先需要通過終端安裝 ecdsa 包(橢圓加密算法庫)。
Linux系統:sudo pip install ecdsa
Windows系統:直接下載源碼安裝。
(2)Python代碼:(windows,python2.7 idle)
# -*- coding: UTF-8 -*-
import ecdsa
import ecdsa.der
import ecdsa.util
import hashlib
import os
import re
import struct
b58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def base58encode(n):
result = ''
while n > 0:
result = b58[n%58] + result
n /= 58
return result
def base256decode(s):
result = 0
for c in s:
result = result * 256 + ord(c)
return result
def countLeadingChars(s, ch):
count = 0
for c in s:
if c == ch:
count += 1
else:
break
return count
def base58CheckEncode(version, payload):
s = chr(version) + payload
checksum = hashlib.sha256(hashlib.sha256(s).digest()).digest()[0:4]
result = s + checksum
leadingZeros = countLeadingChars(result, '\0')
return '1' * leadingZeros + base58encode(base256decode(result))
def privateKeyToWif(key_hex):
return base58CheckEncode(0x80, key_hex.decode('hex'))
def privateKeyToPublicKey(s):
sk = ecdsa.SigningKey.from_string(s.decode('hex'), curve=ecdsa.SECP256k1)
vk = sk.verifying_key
return ('\04' + sk.verifying_key.to_string()).encode('hex')
def pubKeyToAddr(s):
ripemd160 = hashlib.new('ripemd160')
ripemd160.update(hashlib.sha256(s.decode('hex')).digest())
return base58CheckEncode(0, ripemd160.digest())
def keyToAddr(s):
return pubKeyToAddr(privateKeyToPublicKey(s))
#Generate a random private key
private_key = os.urandom(32).encode('hex')
print "Secret Exponent (Uncompressed) : %s " % private_key
print "Public Key : %s" % privateKeyToPublicKey(private_key)
print "Private Key : %s " % privateKeyToWif(private_key)
print "Address : %s " % keyToAddr(private_key)
(3)輸出:
閱讀更多 四中非著名語文教師 的文章