python區塊鏈地址的簡版實現_第1頁
python區塊鏈地址的簡版實現_第2頁
python區塊鏈地址的簡版實現_第3頁
python區塊鏈地址的簡版實現_第4頁
python區塊鏈地址的簡版實現_第5頁
已閱讀5頁,還剩9頁未讀, 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第python區塊鏈地址的簡版實現本文根據/liuchengxu/blockchain-tutorial的內容,用python實現的,但根據個人的理解進行了一些修改,大量引用了原文的內容。文章末尾有本節完整源碼實現地址。

在上一篇文章中,我們已經初步實現了交易。相信你應該了解了交易中的一些天然屬性,這些屬性沒有絲毫個人色彩的存在:在比特幣中,沒有用戶賬戶,不需要也不會在任何地方存儲個人數據(比如姓名,護照號碼或者SSN)。但是,我們總要有某種途徑識別出你是交易輸出的所有者(也就是說,你擁有在這些輸出上鎖定的幣)。這就是比特幣地址(address)需要完成的使命。在上一篇中,我們把一個由用戶定義的任意字符串當成是地址,現在我們將要實現一個跟比特幣一樣的真實地址。

比特幣地址

這就是一個真實的比特幣地址:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa。這是史上第一個比特幣地址,據說屬于中本聰。比特幣地址是完全公開的,如果你想要給某個人發送幣,只需要知道他的地址就可以了。但是,地址(盡管地址也是獨一無二的)并不是用來證明你是一個錢包所有者的信物。實際上,所謂的地址,只不過是將公鑰表示成人類可讀的形式而已,因為原生的公鑰人類很難閱讀。在比特幣中,你的身份(identity)就是一對(或者多對)保存在你的電腦(或者你能夠獲取到的地方)上的公鑰(publickey)和私鑰(privatekey)。比特幣基于一些加密算法的組合來創建這些密鑰,并且保證了在這個世界上沒有其他人能夠取走你的幣,除非拿到你的密鑰。下面,讓我們來討論一下這些算法到底是什么。

公鑰加密(public-keycryptography)算法使用的是成對的密鑰:公鑰和私鑰。公鑰并不是敏感信息,可以告訴其他人。但是,私鑰絕對不能告訴其他人:只有所有者(owner)才能知道私鑰,能夠識別,鑒定和證明所有者身份的就是私鑰。在加密貨幣的世界中,你的私鑰代表的就是你,私鑰就是一切。

本質上,比特幣錢包也只不過是這樣的密鑰對而已。當你安裝一個錢包應用,或是使用一個比特幣客戶端來生成一個新地址時,它就會為你生成一對密鑰。在比特幣中,誰擁有了私鑰,誰就可以控制所有發送到這個公鑰的幣。

私鑰和公鑰只不過是隨機的字節序列,因此它們無法在屏幕上打印,人類也無法通過肉眼去讀取。這就是為什么比特幣使用了一個轉換算法,將公鑰轉化為一個人類可讀的字符串(也就是我們看到的地址)。

如果你用過比特幣錢包應用,很可能它會為你生成一個助記符。這樣的助記符可以用來替代私鑰,并且可以被用于生成私鑰。BIP-039已經實現了這個機制。

好了,現在我們已經知道了在比特幣中證明用戶身份的是私鑰。那么,比特幣如何檢查交易輸出(和存儲在里面的幣)的所有權呢?

在數學和密碼學中,有一個數字簽名(digitalsignature)的概念,算法可以保證:

當數據從發送方傳送到接收方時,數據不會被修改;數據由某一確定的發送方創建;發送方無法否認發送過數據這一事實。

通過在數據上應用簽名算法(也就是對數據進行簽名),你就可以得到一個簽名,這個簽名晚些時候會被驗證。生成數字簽名需要一個私鑰,而驗證簽名需要一個公鑰。簽名有點類似于印章,比方說我做了一幅畫,完了用印章一蓋,就說明了這幅畫是我的作品。給數據生成簽名,就是給數據蓋了章。

為了對數據進行簽名,我們需要下面兩樣東西:

要簽名的數據私鑰

應用簽名算法可以生成一個簽名,并且這個簽名會被存儲在交易輸入中。為了對一個簽名進行驗證,我們需要以下三樣東西:

被簽名的數據簽名公鑰

簡單來說,驗證過程可以被描述為:檢查簽名是由被簽名數據加上私鑰得來,并且公鑰恰好是由該私鑰生成。

數據簽名并不是加密,你無法從一個簽名重新構造出數據。這有點像哈希:你在數據上運行一個哈希算法,然后得到一個該數據的唯一表示。簽名與哈希的區別在于密鑰對:有了密鑰對,才有簽名驗證。但是密鑰對也可以被用于加密數據:私鑰用于加密,公鑰用于解密數據。不過比特幣并不使用加密算法。

在比特幣中,每一筆交易輸入都會由創建交易的人簽名。在被放入到一個塊之前,必須要對每一筆交易進行驗證。除了一些其他步驟,驗證意味著:

檢查交易輸入有權使用來自之前交易的輸出檢查交易簽名是正確的

如圖,對數據進行簽名和對簽名進行驗證的過程大致如下:

現在來回顧一個交易完整的生命周期:

起初,創世塊里面包含了一個coinbase交易。在coinbase交易中,沒有輸入,所以也就不需要簽名。coinbase交易的輸出包含了一個哈希過的公鑰(使用的是RIPEMD16(SHA256(PubKey))算法)當一個人發送幣時,就會創建一筆交易。這筆交易的輸入會引用之前交易的輸出。每個輸入會存儲一個公鑰(沒有被哈希)和整個交易的一個簽名。比特幣網絡中接收到交易的其他節點會對該交易進行驗證。除了一些其他事情,他們還會檢查:在一個輸入中,公鑰哈希與所引用的輸出哈希相匹配(這保證了發送方只能花費屬于自己的幣);簽名是正確的(這保證了交易是由幣的實際擁有者所創建)。當一個礦工準備挖一個新塊時,他會將交易放到塊中,然后開始挖礦。當新塊被挖出來以后,網絡中的所有其他節點會接收到一條消息,告訴其他人這個塊已經被挖出并被加入到區塊鏈。當一個塊被加入到區塊鏈以后,交易就算完成,它的輸出就可以在新的交易中被引用。

橢圓曲線加密

正如之前提到的,公鑰和私鑰是隨機的字節序列。私鑰能夠用于證明持幣人的身份,需要有一個條件:隨機算法必須生成真正隨機的字節。因為沒有人會想要生成一個私鑰,而這個私鑰意外地也被別人所有。

比特幣使用橢圓曲線來產生私鑰。橢圓曲線是一個復雜的數學概念,我們并不打算在這里作太多解釋(如果你真的十分好奇,可以查看這篇文章,注意:有很多數學公式?。┪覀冎灰肋@些曲線可以生成非常大的隨機數就夠了。在比特幣中使用的曲線可以隨機選取在0與2^2^56(大概是10^77,而整個可見的宇宙中,原子數在10^78到10^82之間)的一個數。有如此高的一個上限,意味著幾乎不可能發生有兩次生成同一個私鑰的事情。

比特幣使用的是ECDSA(EllipticCurveDigitalSignatureAlgorithm)算法來對交易進行簽名,我們也會使用該算法。

Base58

回到上面提到的比特幣地址:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?,F在,我們已經知道了這是公鑰用人類可讀的形式表示而已。如果我們對它進行解碼,就會看到公鑰的本來面目(16進制表示的字節):

0062E907B15CBF27D5425399EBF6F0FB50EBB88F18C29B7D93

比特幣使用Base58算法將公鑰轉換成人類可讀的形式。這個算法跟著名的Base64很類似,區別在于它使用了更短的字母表:為了避免一些利用字母相似性的攻擊,從字母表中移除了一些字母。也就是,沒有這些符號:0(零),O(大寫的o),I(大寫的i),l(小寫的L),因為這幾個字母看著很像。另外,也沒有+和/符號。

下圖是從一個公鑰獲得一個地址的過程:

因此,上面提到的公鑰解碼后包含三個部分:

VersionPublickeyhashChecksum

0062E907B15CBF27D5425399EBF6F0FB50EBB88F18C29B7D93

由于哈希函數是單向的(也就說無法逆轉回去),所以不可能從一個哈希中提取公鑰。不過通過執行哈希函數并進行哈希比較,我們可以檢查一個公鑰是否被用于哈希的生成。

好了,所有細節都已就緒,來寫代碼吧。很多概念只有當寫代碼的時候,才能理解地更透徹。

我們先從錢包Wallet結構開始:

classWallet(object):

#hexversion

VERSION=b'\0'

def__init__(self,private_key):

self._private_key=private_key

self._public_key=private_key.get_verifying_key()

self._address=''

@classmethod

defgenerate_wallet(cls,curve=SECP256k1):

generateawallet

sign_key=SigningKey.generate(curve=curve)

returncls(sign_key)

一個錢包只有一個密鑰對而已。我們需要Wallets類型來保存多個錢包的組合,將它們保存到文件中,或者從文件中進行加載。Wallet的構造函數會生成一個新的密鑰對。generate_wallet函數非常直觀:ECDSA基于橢圓曲線,所以我們需要一個橢圓曲線。接下來,使用橢圓生成一個私鑰。

@property

defaddress(self):

ifnotself._address:

prv_addr=self.VERSION+self._hash_public_key()

self._address=base58.b58encode_check(prv_addr)

returnself._address

以上是地址的生成,使用base58的庫來實現,通過pipinstallbase58即可安裝,我們也可以直接下載源碼放在工程目錄下使用。使用該庫我們就不用重復造輪子了。

至此,就可以得到一個真實的比特幣地址,你甚至可以在查看它的余額。不過我可以負責任地說,無論生成一個新的地址多少次,檢查它的余額都是0。這就是為什么選擇一個合適的公鑰加密算法是如此重要:考慮到私鑰是隨機數,生成同一個數字的概率必須是盡可能地低。理想情況下,必須是低到永遠不會重復。

另外,注意:你并不需要連接到一個比特幣節點來獲得一個地址。地址生成算法使用的多種開源算法可以通過很多編程語言和庫實現。

注意,現在我們已經不再需要script_pub_key和script_sig字段,因為我們不會實現一個腳本語言。相反,script_sig會被分為signature和pub_key字段,script_pub_key被重命名為PubKeyHash。我們會實現跟比特幣里一樣的輸出鎖定/解鎖和輸入簽名邏輯,不同的是我們會通過方法(method)來實現。

use_key方法檢查輸入使用了指定密鑰來解鎖一個輸出。注意到輸入存儲的是原生的公鑰(也就是沒有被哈希的公鑰),但是這個函數要求的是哈希后的公鑰。is_locked_with_key檢查是否提供的公鑰哈希被用于鎖定輸出。這是一個use_key的輔助函數,并且它們都被用于find_unspent_transactions來形成交易之間的聯系。

lock只是簡單地鎖定了一個輸出。當我們給某個人發送幣時,我們只知道他的地址,因為這個函數使用一個地址作為唯一的參數。然后,地址會被解碼,從中提取出公鑰哈希并保存在pub_key_hash字段。

交易必須被簽名,因為這是比特幣里面保證發送方不會花費屬于其他人的幣的唯一方式。如果一個簽名是無效的,那么這筆交易就會被認為是無效的,因此,這筆交易也就無法被加到區塊鏈中。

我們現在離實現交易簽名還差一件事情:用于簽名的數據。一筆交易的哪些部分需要簽名?又或者說,要對完整的交易進行簽名?選擇簽名的數據相當重要。因為用于簽名的這個數據,必須要包含能夠唯一識別數據的信息。比如,如果僅僅對輸出值進行簽名并沒有什么意義,因為簽名不會考慮發送方和接收方。

考慮到交易解鎖的是之前的輸出,然后重新分配里面的價值,并鎖定新的輸出,那么必須要簽名以下數據:

存儲在已解鎖輸出的公鑰哈希。它識別了一筆交易的發送方。存儲在新的鎖定輸出里面的公鑰哈希。它識別了一筆交易的接收方。新的輸出值。

在比特幣中,鎖定/解鎖邏輯被存儲在腳本中,它們被分別存儲在輸入和輸出的script_sig和script_pub_key字段。由于比特幣允許這樣不同類型的腳本,它對script_pub_key的整個內容進行了簽名。

可以看到,我們不需要對存儲在輸入里面的公鑰簽名。因此,在比特幣里,所簽名的并不是一個交易,而是一個去除部分內容的輸入副本,輸入里面存儲了被引用輸出的script_pub_key。

獲取修剪后的交易副本的詳細過程在這里.雖然它可能已經過時了,但是我并沒有找到另一個更可靠的來源。

看著有點復雜,來開始寫代碼吧。先從sign方法開始:

defsign(self,priv_key,prev_txs):

ifself.is_coinbase():

return

tx_copy=self._trimmed_copy()

forin_id,vininenumerate(tx_copy.vins):

prev_tx=prev_txs.get(vin.txid,None)

ifnotprev_tx:

raiseValueError('Previoustransactioniserror')

tx_copy.vins[in_id].signature=None

tx_copy.vins[in_id].pub_key=prev_tx.vouts[vin.vout].pub_key_hash

tx_copy.set_id()

tx_copy.vins[in_id].pub_key=None

sk=ecdsa.SigningKey.from_string(

binascii.a2b_hex(priv_key),curve=ecdsa.SECP256k1)

sign=sk.sign(tx_copy.txid.encode())

self.vins[in_id].signature=binascii.hexlify(sign).decode()

coinbase沒有輸入所以不需要簽名。

tx_copy=self._trimmed_copy()

將會被簽署的是修剪后的交易副本,而不是一個完整交易:

def_trimmed_copy(self):

inputs=[]

outputs=[]

forvininself.vins:

inputs.append(TXInput(vin.txid,vin.vout,None))

forvoutinself.vouts:

outputs.append(TXOutput(vout.value,vout.pub_key_hash))

tx=Transaction(inputs,outputs)

tx.txid=self.txid

returntx

接下來,我們會迭代副本中每一個輸入:

forin_id,vininenumerate(tx_copy.vins):

prev_tx=prev_txs.get(vin.txid,None)

ifnotprev_tx:

raiseValueError('Previoustransactioniserror')

tx_copy.vins[in_id].signature=None

tx_copy.vins[in_id].pub_key=prev_tx.vouts[vin.vout].pub_key_hash

在每個輸入中,signature被設置為None(僅僅是一個雙重檢驗),pub_key被設置為所引用輸出的pub_key_hash?,F在,除了當前交易,其他所有交易都是空的,也就是說他們的signature和pub_key字段被設置為None。因此,輸入是被分開簽名的,盡管這對于我們的應用并不十分緊要,但是比特幣允許交易包含引用了不同地址的輸入。

tx_copy.set_id()

tx_copy.vins[in_id].pub_key=None

hash方法對交易進行序列化,并使用SHA-256算法進行哈希。哈希后的結果就是我們要簽名的數據。在獲取完哈希,我們應該重置pub_key字段,以便于它不會影響后面的迭代。

現在,關鍵點:

sk=ecdsa.SigningKey.from_string(

binascii.a2b_hex(priv_key),curve=ecdsa.SECP256k1)

sign=sk.sign(tx_copy.txid.encode())

self.vins[in_id].signature=binascii.hexlify(sign).decode()

我們通過priv_key對tx_copy.txid進行簽名。一個ECDSA簽名就是一對數字,我們對這對數字連接起來,并存儲在輸入的signature字段。

驗證方法與簽名是類似的:

defverify(self,prev_txs):

tx_copy=self._trimmed_copy()

forin_id,vininenumerate(self.vins):

prev_tx=prev_txs.get(vin.txid,None)

ifnotprev_tx:

raiseValueError('Previoustransactioniserror')

tx_copy.vins[in_id].signature=None

tx_copy.vins[in_id].pub_key=prev_tx.vouts[vin.vout].pub_key_hash

tx_copy.set_id()

tx_copy.vins[in_id].pub_key=None

sign=binascii.unhexlify(self.vins[in_id].signature)

vk=ecdsa.VerifyingKey.from_string(

binascii.a2b_hex(vin.pub_key),curve=ecdsa.SECP256k1)

ifnotvk.verify(sign,tx_copy.txid.encode()):

returnFalse

returnTrue

和上面類似,先進行副本的拷貝,然后置空signature,并重新計算id,然后再進行校驗

sign=binascii.unhexlify(self.vins[in_id].signature)

vk=ecdsa.VerifyingKey.from_string(

binascii.a2b_hex(vin.pub_key),curve=ecdsa.SECP256k1)

ifnotvk.verify(sign,tx_copy.txid.encode()):

returnFalse

如果校驗失敗就直接返回False。

在new_transaction方法中加入簽名:

defnew_transaction(self,from_addr,to_addr,amount):

tx=Transaction(inputs,outputs)

tx.set_id()

self.sign_transaction(tx,from_wallet.private_key)

returntx

defmine(self,bc):

pow=ProofOfWork(self)

fortxinself._transactions:

ifnotbc.verify_transaction(tx):

raiseTransactionVerifyError('transactionverifyerror')

try:

nonce,_=pow.run()

exceptNonceNotFoundErrorase:

print(e)

self._block_header.nonce=nonce

在挖礦方法mine加入交易參數校驗。

測試:

$python3cli.pycreatewallet

Yournewaddressis11oHh3J3jx4yjKppGjCXijVxxc6QZU8zr

$python3cli.pyprintwallet

Walletare:

LZTnHqJG4sSHXHY5Yx1gDqXv9LKc1586Pj

#首先初始化

$python3mai

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論