一文看懂怎樣用 Python 建立比特幣交易

鏈客,專爲開發者而生,有問必答!python

此文章來自區塊鏈技術社區,未經容許拒絕轉載。
圖片描述git

比特幣價格的上上下下,始終撩動着每個人無比關切的當心臟。從去年初的 800 美圓左右,飛漲到去年末到 19783.21 美圓最高點,不到1年,便有將近 25 倍的升值速度。儘管眼下又掉回 8000 多美圓的價格,但價格差很少能搞出去年同期一個數量級,幣圈人士「過去一年比以往 10 年掙的都多」,已是不爭的事實。程序員

而對區塊鏈開發者來講,聽說也已經有拿到年新 500 萬的天價。因此「跑步進入區塊鏈」,已經成爲很多程序員的共識。可是看過不少遠離,咱們如何才能迅速上手呢?國外網友 Ken Shirriff 在博客中分享了他在手動茶古劍比特幣交易時的代碼與對比特幣協議的心得,區塊鏈大本營編譯以下。github

近期,媒體行業對比特幣表現出極大的熱情,這鼓舞着我從網絡底層的數據流開始,認真學習比特幣的工做原理。一般人們會使用錢包軟件來進行比特幣交易,錢包軟件在方便用戶的同時,向用戶隱藏了比特幣的交易流程,而我想親自動手來體驗比特幣交易,個人目標是用Python手動建立一筆比特幣交易,以十六進制數據的形式將交易廣播到比特幣網絡中,而後觀察這筆交易是怎麼被加入到區塊鏈中的。事實證實,這個過程頗有趣,但願你也對它感興趣。算法

在本篇文章中,首先我會對比特幣進行一個簡單的概述,以後,我會從如下幾個方面帶領大家學習比特幣:建立一個比特幣地址(比特幣中的帳戶),進行一筆比特幣交易,簽署交易,將交易廣播到比特幣網絡中,最後等待交易的確認。數據庫

比特幣簡述:小程序

首先,我會介紹一下比特幣系統是怎麼運轉的,而後再深刻探討整個細節。比特幣是一個基於點對點網絡的電子貨幣,你能夠用現金在網上購買比特幣,用比特幣向他人轉帳,在有些商家,你能夠像使用支付寶同樣使用比特幣付款,固然,你也能夠賣出所持有的比特幣換回現金。安全

簡而言之,在比特幣網絡中,分佈式帳本(區塊鏈)記錄並隨時更新着每一個比特幣的全部權。與銀行不一樣的是,比特幣並無與我的或我的的帳戶綁定,相反的,比特幣只屬於一個個比特幣地址,好比:1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa。這裏你可能已經繞暈了,難道這段字符中藏着比特幣?固然不是,比特幣地址是比特幣網絡中的一個身份,也能夠通俗地說是你在比特幣中開的一個「銀行帳戶」,咱們用這個「帳戶」來進行交易。在網站:blockchain.info中,你能夠查到全部的交易信息:服務器

比特幣帳戶信息網絡

可是怎麼證實這個帳戶是個人呢,不急,先往下看,你的疑問我會爲你一一解答。

比特幣交易

如何像使用現金同樣使用比特幣呢?答案是建立一筆交易。在一筆交易中,比特幣的全部者(上文提到過比特幣的全部者是比特幣地址)將全部權轉移到一個新的比特幣地址。比特幣的一個顛覆性創新就是經過鼓勵節點記帳(也叫礦工挖礦),將交易記錄放在一個分佈式的數據庫中。交易被集合在區塊中,大概每十分鐘比特幣網絡中產生一個新的區塊,成爲交易記錄的一部分,稱爲區塊鏈。加入到區塊鏈中的交易能夠被認爲是一筆成功的交易。如今問題來了,誰來給你記帳呢?是礦工,礦工的挖礦過程就是在往區塊鏈中記帳,礦工要覈實每筆交易是否正確,覈實完後,礦工們就開始算一道很難的數學題(密碼學中的哈希函數),最先算出答案的人就能生成一個區塊,也叫挖出了一個新的區塊,這個區塊將成爲區塊鏈的新一部分。

也許你會問了,明明是記帳,幹着會計的活,爲何要叫挖礦呢?和傳統的在地下挖礦石同樣,比特幣挖礦也是會有收穫的。挖礦是一種新發行比特幣的過程,當前,每挖到一個礦,礦工會獲得系統獎勵的12.5個比特幣,按目前一個比特幣接近一萬美圓的市價,這就是一筆12.5萬美圓的鉅款。此外,礦工還能夠得到本區塊中全部的交易費,舉例來講,在高度爲512587的區塊中,幸運的礦工總共收穫了12.829個比特幣。正因如此,礦工之間的競爭十分激烈,採礦的難度與礦工間激烈的競爭是比特幣安全的重要保證,由於這樣能夠保證沒有壞人能操縱系統。

點對點網絡

比特幣並無一箇中央服務器,相反,比特幣在一個點對點網絡中運行。若是你運行一個比特幣節點,那你就成了網絡的一部分。比特幣網絡中的節點彼此交換本身存儲的交易,區塊,以及IP地址信息(用於節點間創建鏈接互相通訊)。當你第一次鏈接到比特幣網絡,你的節點會從隨機挑選的節點中下載區塊鏈的信息。反過來,你的節點也會向後加入者提供信息。當你要建立一筆比特幣交易時,你要把這筆交易發送給一些節點,這些節點會在比特幣網絡中廣播這筆交易,直到全網都收到這筆交易。礦工們會收集你的交易信息,生成一個含有你這筆交易的區塊,向全網廣播,這時,你的節點也會收到這個區塊信息,經過驗證,這筆交易被加入到了區塊鏈中,你就交易成功了。

加密技術

如今回到證實比特幣帳戶是誰的這個問題。比特幣使用數字簽名技術以確保只有比特幣帳戶的全部者才能使用帳戶中的比特幣。比特幣地址的全部者擁有與該地址相匹配的私鑰,當花費比特幣時,你用要這個私鑰在交易上簽名,證實本身是這個帳戶的全部者。這有點像現實生活中的蓋章,蓋章就意味着受權。怎麼驗證呢,公鑰與比特幣帳戶相關聯,用公鑰就能夠驗證簽名是否正確。這樣就解決了比特幣帳戶是誰的這個問題。

怎麼來區分不一樣的交易呢?交易和區塊都使用密碼學上的哈希值進行索引,是否是有點耳熟,對,在比特幣協議中,多處使用到了哈希函數,礦工們剛纔算的數學題就是在算哈希函數。

其實比特幣並不長這個樣

比特幣協議探究

在接下來的文章裏,我將逐步介紹我是怎樣手動進行一次比特幣交易的。首先,我生成了一個比特幣帳戶以及對應的公鑰,私鑰。接下來我發起了一筆比特幣交易,我向這個新生成的帳戶轉了一小筆比特幣。期間手動簽署這筆交易很困難,它花費了我不少的時間。最後,我將這筆交易發送到比特幣網絡,等待它被加入區塊鏈。本文的其他部分會詳細地介紹這些步驟。

事實證實,手動進行比特幣交易比我想象中的更加困難。正如你所看到的,比特幣的協議有些許混亂:它使用了大端格式數字(高位編址,將高序字節存儲在起始地址),小端格式數字(低位編址,將低序字節存儲在起始位置),固定長度數字,可變長度數字,自定義編碼格式,DER編碼格式以及各類加密算法。所以,僅僅是將數據轉換爲正確的格式就浪費了不少時間。

我遇到的第二個難題就是加密,嘗試一下手動加密,你就會發現密碼學對人們多不友好,甚至能夠說是無情。即便你只輸錯了一個字節,交易就會因出錯被拒絕,並且它不會告訴你哪裏出錯了,你只能重來。

最後,手動簽署交易的過程也比想象中可貴多,簽署交易時每一個環節都必須零失誤,要麼又要退回重來。

比特幣地址和密鑰

第一步,我建立了一個比特幣地址。一般狀況下,人們都是使用比特幣客戶端軟件來建立比特幣地址和與之相關的密鑰。本着學習的態度,我寫了一些Python代碼來生成比特幣地址,從而揭示地址建立的機理。

比特幣使用了一系列的密鑰和地址,下圖解釋了它們的關係。首先你要建立一個隨機的256位的私鑰,這個私鑰用於在花費比特幣時簽署交易。所以,私鑰必須保密,不然你的比特幣可能會被盜用。

橢圓曲線數字簽名算法(Elliptic Curve Digital Signature Algorithm,ECDSA,美國政府的標準,接下來咱們會討論它)會從私鑰中生成一個512位的公鑰,這個公鑰用於驗證交易的簽名。但不方便的是,比特幣協議中須要在這個公鑰上添加了前綴04,這個公鑰在交易簽署以前不會被泄露,不像其它系統中公鑰就是爲了公之於衆的。

比特幣地址與公鑰的關係

下一步就是生成與他人交易時使用的比特幣地址了。512位的公鑰太長不方便使用,所以使用SHA-256和RIPEMD哈希算法將其縮小爲160位。而後使用比特幣定義的Base58Check 編碼將密鑰編碼爲ASCII(American Standard Code for Information Interchange,美國信息交換標準代碼)格式。獲得的地址(例如上文中的:1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa)就是你接收別人比特幣時要發佈的地址。須要注意的是,你沒法從比特幣地址中復原出公鑰或私鑰。若是你丟失了你的私鑰(好比說你把私鑰存在你的硬盤上,但硬盤丟失),你的比特幣將永遠丟失。

最後,錢包交換格式密鑰(WIF)用於將私鑰添加到你的錢包軟件中,這只是將私鑰進行Base58Check編碼轉換爲ASCII格式,這一步是可逆的,並且很容易通過逆變換恢復出256位的私鑰。(圖中有個人私鑰,我很好奇是否有人會用個人私鑰去偷(經過私鑰簽署交易,從而轉走)我那價值80美分的比特幣,然而真有人那麼作了,能夠在

https://blockchain.info/addre... 看到,也算是爲教學作貢獻了。)

總之,共有三種密鑰:私鑰,公鑰,公鑰的哈希值,通過使用Base58Check編碼,它們對外都是以ASCII格式表示。私鑰是其中最重要的密鑰,由於花費比特幣時須要私鑰簽署交易,並且其餘的密鑰均可以從私鑰中產生。公鑰的哈希值就是大家剛看的的比特幣地址。

我使用下面的代碼片斷來生成WIF格式的私鑰和地址。私鑰只是一個隨機的256位的數字,使用橢圓曲線數字簽名算法從私鑰中生成公鑰,公鑰使用SHA-256算法,RIPEMD-160算法進行哈希計算,再經Base58編碼並進行校驗後獲得比特幣地址。最後,私鑰用Base58Check編碼以生成用於將私鑰輸入錢包軟件的WIF編碼。注意,這段Python隨機函數代碼在密碼學上安全性並不高,若是你想要嘗試這一步驟,建議使用更安全的錢包軟件來生成比特幣地址和密鑰。

def privateKeyToWif(key_hex):

return utils.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 utils.base58CheckEncode(0, ripemd160.digest())

def keyToAddr(s):

return pubKeyToAddr(privateKeyToPublicKey(s))

# Warning: this random function is not cryptographically strong and is just for example
private_key = ''.join(['%x' % random.randrange(16) for x in range(0, 64)])
print keyUtils.privateKeyToWif(private_key)
print keyUtils.keyToAddr(private_key)
keyUtils.py

從內部分析一筆交易

交易是比特幣系統的基本操做,也許你會認爲交易就是簡單地把比特幣從一個地址轉移到另外一個地址,但交易其實並不簡單。一筆交易包含一個或多個輸入和輸出,交易中的每一個輸入的地址都提供比特幣,每一個輸出的地址都接受比特幣。

一筆簡單的比特幣交易,交易C花費了從交易A和交易B得到的0.008個比特幣,其中0.001個比特幣被看成交易費付給礦工

上圖顯示了一筆簡單的比特幣交易「C」,在這筆交易中,有0.005個比特幣是在交易A中得到的,0.003個比特幣是在交易B中得到的。(圖中箭頭是由新交易的輸入指向獲得這些比特幣的交易的輸出,因此比特幣的流向是逆着箭頭方向的。)對於輸出,有0.003個比特幣給了第一個比特幣地址,有0.004個比特幣給了第二個比特幣地址,剩餘的0.001個比特幣做爲交易費付給礦工。請注意,本次交易並無影響到在交易A中另外一個輸出爲0.015的比特幣。

在一筆交易中,輸入的比特幣地址必須花出全部的比特幣,假如你在以前的交易收到了100個比特幣,但你只想花1個比特幣,建立這筆交易你必須花完全部的100個比特幣,那剩下的99個比特幣怎麼辦呢?解決方案就是在交易中再增長一個輸出,將剩餘的99個比特幣轉給本身。這樣你就能夠花費任意數額的比特幣。

一般交易要支付交易費,若是一筆交易中輸入的比特幣總和大於輸出的比特幣的總和,剩餘的費用就是給礦工的交易費。這筆費用並無明確要求,可是對於礦工而言,沒有交易費的交易就會被列爲低優先級交易,可能要等上幾天纔會被處理甚至被礦工直接丟棄。交易費一般並不高,但它可能影響着你的交易。

手動建立一筆交易

以下圖所示,在個人實驗中我發起了一筆只有一個輸入一個輸出的交易。我在Coinbase上買了一些比特幣,並將0.00101234個比特幣放入地址:

1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5中,這筆交易哈希爲:

81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48,個人目標是建立一筆交易,將這些比特幣轉入個人另外一個地址:

1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,扣除0.0001個比特幣的交易費後,目標地址將得到0.00091234個比特幣。

比特幣交易結構實例

Blockchain.info上的交易記錄

https://blockchain.info/addre...

按照協議標準,建立這筆交易很簡單。以下表所示,這筆交易只有一個輸入,源自於81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48中的輸出0(第一個輸出)。輸出爲0.00091234個比特幣(91234在十六進制中用0x016462表示),它以小端格式存儲在值區域中。加密過程當中的scriptSig和scriptPubKey較爲複雜,咱們稍後再作討論。

version

01 00 00 00

input count

01

input

previous output hash(reversed)

48 4d 40 d4 5b 9e a0 d6 52 fc a8 25 8a b7 ca a4 25 41 eb 52 97 58 57 f9 6f b5 0c d7 32 c8 b4 81

previous output index

00 00 00 00

script length

scriptSig

script containing signature

sequence

ff ff ff ff

output count

01

output

value

62 64 01 00 00 00 00 00

script length

scriptPubKey

script containing destination address

block lock time

00 00 00 00

這是我生成交易使用的代碼,這段代碼只是把數據打包成二進制文件。簽署交易較爲困難,咱們等一下子再說。

Makes a transaction from the inputs# outputs is a list of [redemptionSatoshis, outputScript]

def makeRawTransaction(outputTransactionHash, sourceIndex, scriptSig, outputs):

def makeOutput(data):
           redemptionSatoshis, outputScript = data
           return (struct.pack("<Q", redemptionSatoshis).encode('hex') +
           '%02x' % len(outputScript.decode('hex')) + outputScript)
       formattedOutputs = ''.join(map(makeOutput, outputs))
       return (        
           "01000000" + # 4 bytes version
           "01" + # varint for number of inputs
           outputTransactionHash.decode('hex')[::-1].encode('hex') + # reverse outputTransactionHash
           struct.pack('<L', sourceIndex).encode('hex') +        
           '%02x' % len(scriptSig.decode('hex')) + scriptSig +        
           "ffffffff" + # sequence
           "%02x" % len(outputs) + # number of outputs
           formattedOutputs +        
           "00000000" # lockTime
           )

txnUtils.py

比特幣交易怎樣簽署

下圖爲咱們簡單描述了交易是如何簽署並相互鏈接的。針對中間這筆從比特幣地址B轉帳到比特幣地址C的交易。交易的內容(包括前一個交易的哈希值(索引))被進行哈希計算並用B的私鑰簽名。另外,B的公鑰也被包含在了交易中。

經過執行幾個簡單運算,任何人都能驗證B是否簽署了這筆交易。首先,B的公鑰與以前收到這筆比特幣交易的地址作驗證,證實B的公鑰有效。(正如前面所說的,地址很容易從公鑰中計算得到)。接下來,能夠經過B的公鑰驗證B交易簽名的真僞。這些步驟能確保交易的有效性和交易獲得B的受權。比特幣於衆不一樣的一點是,B的公鑰在B發起交易以前是不公開的。

在比特幣系統中,比特幣經過區塊鏈上的一筆筆交易在不一樣的地址間傳遞。區塊鏈上的每一筆交易都能被驗證以確保比特幣交易的有效性。

比特幣交易的相互鏈接

比特幣腳本語言

你可能會覺得僅僅經過在交易內容中附上簽名就能夠簽署比特幣交易,其實否則,這個過程十分複雜。實際上,每一筆交易中都包含一個「小程序」,用於確認交易是否有效。這個「小程序」用腳本語言寫成,經過這種基於堆棧的比特幣腳本語言,咱們能夠應對許多複雜的比特幣支付場景。例如,託管系統能夠設定只要通過三分之二的用戶受權,就可執行交易的規則,也能夠設置其餘的合約。

腳本語言十分複雜,大約有80種操做碼,包括算數計算,按位操做,字符串處理,條件語句和堆棧操做。腳本語言也包含一些必要的密碼學操做(SHA-256,RIPEMD等等)做爲原語(原語是執行過程當中不可被打斷的基本操做,你能夠理解爲一段代碼)。爲了確保腳本語言能夠運行完畢自動退出,該語言不支持任何循環操做,所以它不是圖靈完備的。然而,實際上,它只支持少數類型的交易。

前一個交易中的腳本稱爲scriptPubKey,當前交易中的腳本稱爲scriptSig。要驗證交易時,先執行scriptSig,而後再執行scriptPubKey。若是兩個腳本都成功執行,交易就被認定爲有效,交易中的比特幣就能夠成功花出。不然,交易無效。要注意的是前一個交易中的scriptPubKey規定了花費比特幣的條件,當前交易的scriptSig必須知足這個條件。

在一個標準的交易中,scriptSig腳本將從私鑰中生成的簽名並壓入堆棧中,再壓入公鑰。接下來scriptPubKey腳本會執行運算先驗證公鑰的有效性,再驗證簽名的有效性。

正如腳本中所表示,scriptSig:

PUSHDATA
signature data and SIGHASH_ALL
PUSHDATA
public key data
scriptPubKey:

OP_DUP
OP_HASH160
PUSHDATA
Bitcoin address (public key hash)
OP_EQUALVERIFY
OP_CHECKSIG
當這段代碼執行時,PUSHDATA操做首先會把簽名壓入堆棧,接着把公鑰壓入堆棧。OPHASH-160操做計算公鑰的160位哈希值,PUSHDATA操做再把交易中的輸入地址(輸入帳號)壓入堆棧,而後,OP-EQUALVERIFY操做驗證驗證前兩個堆棧中的值是否相等(驗證這筆交易中你使用的比特幣是否屬於你本身)-若是公鑰的哈希等於以前交易中的輸出地址,這就證實公鑰是有效的(證實這個比特幣是你的)。最後,OP_CHECKSIG操做將檢查交易的簽名是否與堆棧裏的公鑰和簽名匹配,匹配就證實簽名是有效的(證實交易的到了你的受權)

簽署交易

我發現簽署這筆交易是手動使用比特幣時最難的地方,這一過程出奇地困難且容易出錯。簽名的基本思想很簡單,使用橢圓曲線簽名算法和私鑰來生成交易的數字簽名,但細節很是棘手。簽署交易的過程能夠經過這19個步驟描述。

簽署交易的19個步驟

對交易的簽名讓我面臨巨大的挑戰,這涉及到一個如何在交易內容中尚未加入簽名時簽署這筆交易的問題。爲了不這個問題,在計算生成簽名以前,我把scriptPubKey這個腳本從上一筆交易複製到當前交易中(當前這筆交易正在被簽署),而後將簽名轉換爲腳本語言的代碼,建立嵌入在當前交易中的scriptSig腳本。對於具備多個輸入的交易,簽署交易環節更加複雜,由於每一個輸入都須要單獨的簽名,這裏我就不作詳細討論了。

哈希值這一步驟難倒了我。在簽名以前,交易中有一個臨時附加的哈希值常量。對於常規的交易,這個值是SIGHASH_ALL(0x00000001)。簽名後,這個哈希值將從交易內容的最後刪除,附加到scriptSig腳本中。

在比特幣中另外一件使人討厭的事情是雖然簽名和公鑰都是512位的橢圓曲線值,但它們的表示方式徹底不一樣:簽名用DER編碼方式編碼,而公鑰用純字節表示。另外,兩個值都有一個額外的字節,但位置並不一致:SIGHASH_ALL這個附加的哈希值常量放在簽名後面,而04這個值放在公鑰前面。

因爲ECDSA算法須要使用隨機數,因此調試簽名十分困難。每次計算出的簽名都會有所不一樣,所以沒法與已知正確的簽名進行比較。

正是因爲上述的複雜性,我花了很長時間才獲得了一個簽名。不過,最終我找出了簽名代碼中全部的錯誤,併成功用它簽署了一筆交易。這是我使用的簽名代碼:

def makeSignedTransaction(privateKey, outputTransactionHash, sourceIndex, scriptPubKey, outputs):

myTxn_forSig = (makeRawTransaction(outputTransactionHash, sourceIndex, scriptPubKey, outputs)
     + "01000000") # hash code

s256 = hashlib.sha256(hashlib.sha256(myTxn_forSig.decode('hex')).digest()).digest()
sk = ecdsa.SigningKey.from_string(privateKey.decode('hex'), curve=ecdsa.SECP256k1)
sig = sk.sign_digest(s256, sigencode=ecdsa.util.sigencode_der) + '\01' # 01 is hashtype
pubKey = keyUtils.privateKeyToPublicKey(privateKey)
scriptSig = utils.varstr(sig).encode('hex') + utils.varstr(pubKey.decode('hex')).encode('hex')
signed_txn = makeRawTransaction(outputTransactionHash, sourceIndex, scriptSig, outputs)
verifyTxnSignature(signed_txn)    
return signed2_txn

txnUtils.py

最終的scriptSig腳本中包含簽名以及比特幣源地址的公鑰(1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5)。 這證實這筆交易有效,我能夠花費這些比特幣。

PUSHDATA 47

47

signature(DER)

sequence

30

length

44

integer

02

length

20

X

2c b2 65 bf 10 70 7b f4 93 46 c3 51 5d d3 d1 6f c4 54 61 8c 58 ec 0a 0f f4 48 a6 76 c5 4f f7 13

integer

02

length

20

Y

6c 66 24 d7 62 a1 fc ef 46 18 28 4e ad 8f 08 67 8a c0 5b 13 c8 42 35 f1 65 4e 6a d1 68 23 3e 82

SIGHASH_ALL

01

PUSHDATA 41

41

public key

type

04

X

14 e3 01 b2 32 8f 17 44 2c 0b 83 10 d7 87 bf 3d 8a 40 4c fb d0 70 4f 13 5b 6a d4 b2 d3 ee 75 13

Y

10 f9 81 92 6e 53 a6 e8 c3 9b d7 d3 fe fd 57 6c 54 3c ce 49 3c ba c0 63 88 f2 65 1d 1a ac bf cd

最終的scriptPubKey腳本包含成功花費比特幣時必須執行的腳本。須要注意的是,這個腳本將在將來花費這些比特幣的時候執行。它包含以十六進制表示而不是以Base58Check表示的目標地址1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,腳本的效果是隻有這個目標地址的私鑰全部者才能使用比特幣,所以目標地址其實是這些比特幣的全部者

OP_DUP

76

OP_HASH160

a9

PUSHDATA 14

14

public key hash

c8 e9 09 96 c7 c6 08 0e e0 62 84 60 0c 68 4e d9 04 d1 4c 5c

OP_EQUALVERIFY

88

OP_CHECKSIG

ac

最終的交易

通過上述的一系列操做,咱們完成了最終的交易。可是,別忘了,此時的交易還沒加入區塊鏈中,接收方尚未收到你的比特幣。

privateKey = keyUtils.wifToPrivateKey("5HusYj2b2x4nroApgfvaSfKYZhRbKFH41bVyPooymbC6KfgSXdD") #1MMMM

signed_txn = txnUtils.makeSignedTransaction(privateKey,

"81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48", # output (prev) transaction hash

0, # sourceIndex

keyUtils.addrHashToScriptPubKey("1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5"),

[[91234, #satoshis

keyUtils.addrHashToScriptPubKey("1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa")]]

)

txnUtils.verifyTxnSignature(signed_txn)
print'SIGNED TXN', signed_txn
makeTransaction.py

最終的交易信息以下所示:

version

01 00 00 00

input count

01

input

previous output hash(reversed)

48 4d 40 d4 5b 9e a0 d6 52 fc a8 25 8a b7 ca a4 25 41 eb 52 97 58 57 f9 6f b5 0c d7 32 c8 b4 81

previous output index

00 00 00 00

script length

8a

scriptSig

47 30 44 02 20 2c b2 65 bf 10 70 7b f4 93 46 c3 51 5d d3 d1 6f c4 54 61 8c 58 ec 0a 0f f4 48 a6 76 c5 4f f7 13 02 20 6c 66 24 d7 62 a1 fc ef 46 18 28 4e ad 8f 08 67 8a c0 5b 13 c8 42 35 f1 65 4e 6a d1 68 23 3e 82 01 41 04 14 e3 01 b2 32 8f 17 44 2c 0b 83 10 d7 87 bf 3d 8a 40 4c fb d0 70 4f 13 5b 6a d4 b2 d3 ee 75 13 10 f9 81 92 6e 53 a6 e8 c3 9b d7 d3 fe fd 57 6c 54 3c ce 49 3c ba c0 63 88 f2 65 1d 1a ac bf cd

sequence

ff ff ff ff

output count

01

output

value

62 64 01 00 00 00 00 00

script length

19

scriptPubKey

76 a9 14 c8 e9 09 96 c7 c6 08 0e e0 62 84 60 0c 68 4e d9 04 d1 4c 5c 88 ac

block lock time

00 00 00 00

小插曲:橢圓曲線簽名

比特幣的簽名算法使用到了橢圓曲線簽名算法,這麼實用的功能,你可能會好奇它是怎麼作到的?在當年英國數學家安德魯·懷爾斯攻克費馬大定理時,我第一次接觸到了橢圓曲線的算法。橢圓曲線的數學思想頗有意思,因此在這裏我給你們作一個快速的概述。

橢圓曲線這個叫法使人迷惑,由於橢圓曲線並非橢圓,並且看起來也不像橢圓,甚至橢圓曲線與橢圓相關性都不多。通俗地講,橢圓曲線就是知足一個簡單方程y ^ 2 = x ^ 3 + ax + b的曲線。比特幣中使用的稱爲secp256k1的橢圓曲線,它知足的方程爲y ^ 2 = x ^ 3 + 7。

secp256k1橢圓曲線

橢圓曲線的一個重要特性就是你能夠用一個簡單的規則來定義橢圓曲線上點的相加:若是在曲線上繪製一條直線,這條直線與曲線交與A,B,C三個點,那麼這個加法定義爲A+B+C=0。由這個加法的定義,咱們能夠定義整數乘法:例如4A = A + A + A + A。

爲何橢圓曲線在密碼學上頗有用?由於橢圓曲線作整數乘法運算速度很快,但作除法時須要蠻力。例如,你能夠快速地計算一個乘法12345678A = Q,可是若是你只知道A和Q,求解nA=Q中的n十分困難。所以在橢圓曲線算法中,這裏的12345678將是私鑰,曲線上的點Q將是公鑰。

在密碼學中,點的座標並非它在曲線上的實值點,而是對整數的模數。橢圓曲線的一個好用的特性就是對實數或模數進行運算的數學運算幾乎相同。正由於如此,比特幣的橢圓曲線並不像上面的圖片,而是一團雜亂無章的256位點集(想一想在一個空間中充滿了大量雜亂無章的點)。

橢圓曲線數字簽名算法(ECDSA)接收交易的哈希值,使用該交易數據,私鑰,以及一個隨機數從橢圓曲線上生成一個新的點,從而實現對交易的簽名。任何擁有公鑰,交易數據,和簽名的人均可以經過作一個簡單的橢圓曲線運算來驗證簽名的有效性。讀到這裏,你應該明白了爲何只有擁有私鑰的人才能簽署消息,但擁有公鑰的任何人均可以驗證該消息。

把交易發送到比特幣網絡

回到交易中來,別忘了此時咱們的交易尚未被加入到區塊鏈中,還不是一筆有效交易。剛剛我建立並簽署了一筆交易。下一步就是將這筆交易發送到比特幣網絡中,網絡中的礦工會收集交易並把它打包進區塊中。

如何找到比特幣網絡的節點

首先我要在比特幣的點對點網絡中找到一個節點。節點的列表會隨節點的進出動態更新,當一個比特幣節點鏈接到另外一個節點時,它們就會不斷交換彼此新發現的比特幣節點信息,所以,新節點加入的消息會快速地傳遍整個網絡。

然而,新的比特幣節點如何第一次找到比特幣節點?這是一個先有雞仍是先有蛋的問題。比特幣節點經過如下幾種方法來解決這個問題。有幾個可信的比特幣節點會以bitseed.xf2.org的域名在DNS系統(Domain Name System,域名系統,萬維網上做爲域名和IP地址相互映射的一個分佈式數據庫)上註冊,經過執行nslookup命令,你就能夠獲得這些節點的IP地址,只要有一個在工做便可。若是很不幸它們都沒有工做的話,你能夠試着鏈接那幾個已經在你的客戶端中硬編碼記錄下來的地址。

Nslookup命令能夠用來尋找比特幣節點

當用戶啓動或中止比特幣客戶端時,節點就會加入或離開比特幣網絡。因此鏈接節點有很大的不肯定性,在我實驗時,就遇到了鏈接的節點已經離開比特幣網絡的狀況,若是你想重複個人實驗,最好多找幾個節點,可能須要屢次嘗試才能找到一個運行着的節點。

與比特幣節點通訊

一旦得到了一個正在工做的比特幣節點的IP地址,當務之急就經過這個節點是把個人交易發送到比特幣的點對點網絡中。使用點對點的網絡協議十分簡單,我在端口8333上打開了一個到任意對等端的TCP鏈接,發送消息,而後接受反饋消息。比特幣的點對點協議對用戶很友好,即便個人請求數據出錯時,仍是繼續與我保持通訊。

重要提示:正如一些人指出的那樣,若是你想重複個人實驗,切記要使用比特幣的測試網絡,在測試網絡上,你可使用「虛擬」的比特幣來進行交易。由於在真實網絡上,萬一你不當心,有可能會失去全部的比特幣。還記得上面提到的那個100個比特幣轉帳1個的交易麼,若是你忘了將剩餘的比特幣轉給本身,那麼剩餘的99個比特幣就會做爲交易費支付給礦工。可是本着科學的態度,我並不在乎在真實的比特幣網絡中損失我這些價值1美圓的比特幣。

協議中包含24種不一樣的信息種類。每一條信息都是一個簡單的二進制大對象(binary large object ,BLOB,是一個能夠存儲二進制文件的容器),其中包含一個ASCII命令和一個適用該命令的二進制有效參數。該協議能夠在比特幣的維基上查詢。

鏈接到比特幣網絡的第一步就是經過交換客戶端版本信息來創建鏈接。首先,我發送了一條客戶端版本信息,其中包含個人協議版本號,IP地址和其餘內容。比特幣節點也向我回復了它的版本信息。在此以後,我應該回復一個verack信息(version acknowledgement,版本確認)來確認它的版本信息。正如我所說,比特幣點對點網絡協議對用戶十分友好,即便我跳過了verack信息,以後的操做也是一切正常。

交換版本信息這一步並不簡單,由於信息具備標準的格式,不過不用懼怕,能夠用幾行代碼來建立這些信息。下面代碼段中的makeMessage函數能夠由隨機數,命令名以及命令的參數來生成一條消息。getVersionMessage函數經過將各個字段打包在一塊兒來爲版本消息建立參數。

magic = 0xd9b4bef9

def makeMessage(magic, command, payload):

checksum =

hashlib.sha256(hashlib.sha256(payload).digest()).digest()[0:4]

return struct.pack('L12sL4s', magic, command, len(payload), checksum) + payload

def getVersionMsg():

version = 60002
services = 1
timestamp = int(time.time())
addr_me = utils.netaddr(socket.inet_aton("127.0.0.1"), 8333)
addr_you = utils.netaddr(socket.inet_aton("127.0.0.1"), 8333)
nonce = random.getrandbits(64)
sub_version_num = utils.varstr('')
start_height = 0

payload = struct.pack('<LQQ26s26sQsL', version, services, timestamp, addr_me,
    addr_you, nonce, sub_version_num, start_height)    
return makeMessage(magic, 'version', payload)

msgUtils.py

發送交易tx

我使用下面精簡的Python代碼把個人交易發送到比特幣網絡中,這個代碼發送一條客戶端版本信息,接受(也能夠忽略)比特幣節點的版本信息和verack信息。最後將個人交易以tx信息發送。代碼中這個16進制的字符串是我以前建立的交易。

def getTxMsg(payload): return makeMessage(magic, 'tx', payload)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("97.88.151.164", 8333))

sock.send(msgUtils.getVersionMsg())
sock.recv(1000) # receive version
sock.recv(1000) # receive verack
sock.send(msgUtils.getTxMsg("0100000001484d40d45b9ea0d652fca8258ab7caa42541eb52975857f96fb50cd732c8b481000000008a47304402202cb265bf10707bf49346c3515dd3d16fc454618c58ec0a0ff448a676c54ff71302206c6624d762a1fcef4618284ead8f08678ac05b13c84235f1654e6ad168233e8201410414e301b2328f17442c0b8310d787bf3d8a404cfbd0704f135b6ad4b2d3ee751310f981926e53a6e8c39bd7d3fefd576c543cce493cbac06388f2651d1aacbfcdffffffff0162640100000000001976a914c8e90996c7c6080ee06284600c684ed904d14c5c88ac00000000".decode('hex')))
minimalSendTxn.py

如下Wireshark(一個抓取,分析網絡封包的軟件)軟件的截圖顯示出我是如何將交易發送到比特幣網絡中的。我用Python編寫了腳原本分析網絡數據,爲了簡單起見,在這裏我使用Wireshark。從圖中能夠看到個人這筆tx交易。

Wireshark中抓取的這筆正在上傳至比特幣網絡的交易tx

爲了實時監控我這筆交易的進度,我在比特幣網絡中新運行了一個節點,在把我交易發到比特幣網絡5秒鐘以後,另外一個節點給我發送了這個tx消息,其中包含我剛剛發送的這筆交易的哈希,因而可知,在僅僅這幾秒中,個人交易已經傳遍了比特幣網絡,至少也是比特幣網絡的一部分。

交易成功:個人交易被加入區塊鏈

在將個人交易發送比特幣網絡以後,我須要等待它被礦工開採出來加入到區塊鏈中,而後才能宣稱個人實驗圓滿成功。10分鐘後,個人比特幣節點收到一條含有新區塊信息的inv消息(參見下圖Wireshark抓到的網絡封包),檢查這個區塊後發現個人交易被包含在了區塊中,證實個人交易是有效的,個人實驗成功了。經過個人比特幣錢包軟件和在線查詢,再一次確認了我已經交易成功。能夠說,通過不斷的努力,我成功手動建立了一筆交易,並讓比特幣系統接受了它。(固然了,我也通過了幾回失敗的嘗試,這些錯誤的交易都消失在了網絡之中,永遠都不會被檢索到。

Wireshark中抓取的新區塊產生的封包信息

個人交易是被當時哈希算力(挖礦速度)最大的礦池(多個礦工一塊兒挖礦)GHash.IO挖出,區塊高度爲279068,區塊哈希爲0000000000000001a27b1d6eb8c405410398ece796e742da3b3e35363c2219ee,在上圖Wireshark數據包中inv消息的哈希值是經先後反轉獲得的ee192……。你應該會發現區塊的哈希值以大量的0開頭,在一個16進制的哈希值中發現一個以這麼多0開頭的數,這就是爲何挖礦如此困難的緣由。這個區塊中由462筆交易,個人交易是其中之一。

高度爲279068的區塊以及我發起的這筆交易

https://blockchain.info/block...

挖到這個區塊的礦工們收到了25個比特幣的獎勵,交易費總共是0.104個比特幣,按當時的市價分別爲19000美圓和80美圓。我支付了0.0001個比特幣的交易費,大約是我交易額的10%,按當時的市價爲8美分。

結論

手動進行比特幣交易比我想象中困可貴多,可是在這個過程當中我學到了不少,但願你也是。個人Python代碼僅僅是爲了介紹,若是你想跟我同樣用Python手動進行比特幣交易,也能夠試試這幾個項目。

https://en.bitcoin.it/wiki/Bi...

https://github.com/richardkis...

https://github.com/jgarzik/py...

寫在最後
2017年是區塊鏈的井噴之年,通過一年的積攢,2018年將迎來區塊鏈的落地之年,區塊鏈會逐漸顛覆各行各業。對於我的,區塊鏈的機會會愈來愈多,也許你錯過了比特幣的投資,不妨如今抓住區塊鏈這個風口,投資本身,多學習相關知識,區塊鏈大有可爲,投身區塊鏈的你將大有做爲!

https://huiyi.csdn.net/activi...

你們能夠比較市面上的不一樣類型區塊鏈課程,貨比三家,選適合本身的,趕忙上車是王道。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息