一文看懂怎樣用 Python 創建比特币交易

2018-04-04 15:20

 

比特币價格的上上下下,始終撩動着每一個人無比關切的小心髒。從去(qù)年初的 800 美元左右,飛漲到去(qù)年底到 19783.21 美元最高點,不到1年,便有将近 25 倍的升值速度。盡管眼下又(yòu)掉回 8000 多美元的價格,但(dàn)價格差不多能搞出去(qù)年同期一個數量級,币圈人士“過去(qù)一年比以往 10 年掙的都多”,已經是(shì)不争的事實。

而對區塊鏈開發者來說,據說也已經有拿到年新 500 萬的天價。所以“跑步進入區塊鏈”,已經成爲不少程序員(yuán)的共識。但(dàn)是(shì)看過很多遠離(lí),我們如何才能迅速上手呢?國外網友(yǒu) Ken Shirriff 在博客中分享了他在手動茶古劍比特币交易時的代碼與對比特币協議(yì)的心得,區塊鏈大本營編譯如下。

 

近期,媒體行業對比特币表現(xiàn)出極大的熱情,這鼓舞着我從網絡底層的數據流開始,認真學習比特币的工作原理。通常人們會使用錢包軟件來進行比特币交易,錢包軟件在方便用戶的同時,向用戶隐藏了比特币的交易流程,而我想親自動手來體驗比特币交易,我的目标是(shì)用Python手動創建一筆比特币交易,以十六進制數據的形式将交易廣播到比特币網絡中,然後觀察這筆交易是(shì)怎麽被加入到區塊鏈中的。事實證明,這個過程很有趣,希望你也對它感興趣。

 

在本篇文章中,首先我會對比特币進行一個簡單的概述,之後,我會從以下幾個方面帶領你們學習比特币:創建一個比特币地址(比特币中的賬戶),進行一筆比特币交易,簽署交易,将交易廣播到比特币網絡中,最後等待交易的确認。

 

比特币簡述:

 

首先,我會介紹一下比特币系統是(shì)怎麽運轉的,然後再深入探讨整個細節。比特币是(shì)一個基于點對點網絡的電子貨币,你可以用現(xiàn)金在網上購買比特币,用比特币向他人轉賬,在有些商家,你可以像使用支付寶一樣使用比特币付款,當然,你也可以賣出所持有的比特币換回現(xiàn)金。

 

簡而言之,在比特币網絡中,分布式賬本(區塊鏈)記錄并随時更新着每個比特币的所有權。與銀行不同的是(shì),比特币并沒有與個人或個人的賬戶綁定,相(xiàng)反的,比特币隻屬于一個個比特币地址,比如:1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa。這裏你可能已經繞暈了,難道這段字符中藏着比特币?當然不是(shì),比特币地址是(shì)比特币網絡中的一個身份,也可以通俗地說是(shì)你在比特币中開的一個“銀行賬戶”,我們用這個“賬戶”來進行交易。在網站:blockchain.info中,你可以查到所有的交易信息:

 

比特币賬戶信息

 

但(dàn)是(shì)怎麽證明這個賬戶是(shì)我的呢,不急,先往下看,你的疑問我會爲你一一解答。

 

比特币交易

 

如何像使用現(xiàn)金一樣使用比特币呢?答案是(shì)創建一筆交易。在一筆交易中,比特币的所有者(上文提到過比特币的所有者是(shì)比特币地址)将所有權轉移到一個新的比特币地址。比特币的一個颠覆性創新就是(shì)通過鼓勵節點記賬(也叫礦工挖礦),将交易記錄放(fàng)在一個分布式的數據庫中。交易被集合在區塊中,大概每十分鍾比特币網絡中産生一個新的區塊,成爲交易記錄的一部分,稱爲區塊鏈。加入到區塊鏈中的交易可以被認爲是(shì)一筆成功的交易。現(xiàn)在問題來了,誰來給你記賬呢?是(shì)礦工,礦工的挖礦過程就是(shì)在往區塊鏈中記賬,礦工要核實每筆交易是(shì)否正确,核實完後,礦工們就開始算一道很難的數學題(密碼學中的哈希函數),最早算出答案的人就能生成一個區塊,也叫挖出了一個新的區塊,這個區塊将成爲區塊鏈的新一部分。

 

也許你會問了,明明是(shì)記賬,幹着會計的活,爲什麽要叫挖礦呢?和傳統的在地下挖礦石一樣,比特币挖礦也是(shì)會有收獲的。挖礦是(shì)一種新發行比特币的過程,當前,每挖到一個礦,礦工會得到系統獎勵的12.5個比特币,按目前一個比特币接近一萬美元的市價,這就是(shì)一筆12.5萬美元的巨款。此外,礦工還可以獲得本區塊中所有的交易費(fèi),舉例來說,在高度爲512587的區塊中,幸運的礦工總共收獲了12.829個比特币。正因如此,礦工之間的競争十分激烈,采礦的難度與礦工間激烈的競争是(shì)比特币安全的重要保證,因爲這樣可以保證沒有壞人能操縱系統。

 

點對點網絡

 

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

 

加密技術

 

現(xiàn)在回到證明比特币賬戶是(shì)誰的這個問題。比特币使用數字簽名技術以确保隻有比特币賬戶的所有者才能使用賬戶中的比特币。比特币地址的所有者擁有與該地址相(xiàng)匹配的私鑰,當花費(fèi)比特币時,你用要這個私鑰在交易上簽名,證明自己是(shì)這個賬戶的所有者。這有點像現(xiàn)實生活中的蓋章,蓋章就意味着授權。怎麽驗證呢,公鑰與比特币賬戶相(xiàng)關聯,用公鑰就可以驗證簽名是(shì)否正确。這樣就解決了比特币賬戶是(shì)誰的這個問題。

 

怎麽來區分不同的交易呢?交易和區塊都使用密碼學上的哈希值進行索引,是(shì)不是(shì)有點耳熟,對,在比特币協議(yì)中,多處使用到了哈希函數,礦工們剛才算的數學題就是(shì)在算哈希函數。

 

比特币協議(yì)探究

 

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

 

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

 

我遇到的第二個難題就是(shì)加密,嘗試一下手動加密,你就會發現(xiàn)密碼學對人們多不友(yǒu)好,甚至可以說是(shì)無情。即使你隻輸錯了一個字節,交易就會因出錯被拒絕,而且它不會告訴你哪裏出錯了,你隻能重來。

 

最後,手動簽署交易的過程也比想象中難得多,簽署交易時每個環節都必須零失誤,要麽又(yòu)要退回重來。

 

比特币地址和密鑰

 

第一步,我創建了一個比特币地址。通常情況下,人們都是(shì)使用比特币客戶端軟件來創建比特币地址和與之相(xiàng)關的密鑰。本着學習的态度,我寫了一些Python代碼來生成比特币地址,從而揭示地址創建的機理。

 

比特币使用了一系列的密鑰和地址,下圖解釋了它們的關系。首先你要創建一個随機的256位的私鑰,這個私鑰用于在花費(fèi)比特币時簽署交易。因此,私鑰必須保密,否則你的比特币可能會被盜用。

 

橢圓曲線(xiàn)數字簽名算法(Elliptic Curve Digital Signature Algorithm,ECDSA,美國政府的标準,接下來我們會讨論它)會從私鑰中生成一個512位的公鑰,這個公鑰用于驗證交易的簽名。但(dàn)不方便的是(shì),比特币協議(yì)中需要在這個公鑰上添加了前綴04,這個公鑰在交易簽署之前不會被洩露,不像其它系統中公鑰就是(shì)爲了公之于衆的。

 

比特币地址與公鑰的關系

 

下一步就是(shì)生成與他人交易時使用的比特币地址了。512位的公鑰太長不方便使用,因此使用SHA-256和RIPEMD哈希算法将其縮小爲160位。然後使用比特币定義的Base58Check 編碼将密鑰編碼爲ASCII(American Standard Code for Information Interchange,美國信息交換标準代碼)格式。得到的地址(例如上文中的:1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa)就是(shì)你接收别人比特币時要發布的地址。需要注意的是(shì),你無法從比特币地址中複原出公鑰或私鑰。如果你丢失了你的私鑰(比如說你把私鑰存在你的硬盤上,但(dàn)硬盤丢失),你的比特币将永遠丢失。

 

最後,錢包交換格式密鑰(WIF)用于将私鑰添加到你的錢包軟件中,這隻是(shì)将私鑰進行Base58Check編碼轉換爲ASCII格式,這一步是(shì)可逆的,而且很容易經過逆變換恢複出256位的私鑰。(圖中有我的私鑰,我很好奇是(shì)否有人會用我的私鑰去(qù)偷(通過私鑰簽署交易,從而轉走)我那價值80美分的比特币,然而真有人那麽做了,可以在

https://blockchain.info/address/1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa 看到,也算是(shì)爲教學做貢獻了。)

 

總之,共有三種密鑰:私鑰,公鑰,公鑰的哈希值,經過使用Base58Check編碼,它們對外都是(shì)以ASCII格式表示。私鑰是(shì)其中最重要的密鑰,因爲花費(fèi)比特币時需要私鑰簽署交易,而且其他的密鑰都可以從私鑰中産生。公鑰的哈希值就是(shì)你們剛看的的比特币地址。

 

我使用下面的代碼片段來生成WIF格式的私鑰和地址。私鑰隻是(shì)一個随機的256位的數字,使用橢圓曲線(xiàn)數字簽名算法從私鑰中生成公鑰,公鑰使用SHA-256算法,RIPEMD-160算法進行哈希計算,再經Base58編碼并進行校驗後得到比特币地址。最後,私鑰用Base58Check編碼以生成用于将私鑰輸入錢包軟件的WIF編碼。注意,這段Python随機函數代碼在密碼學上安全性并不高,如果你想要嘗試這一步驟,建議(yì)使用更安全的錢包軟件來生成比特币地址和密鑰。

 

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

 

從内部分析一筆交易

 

交易是(shì)比特币系統的基本操作,也許你會認爲交易就是(shì)簡單地把比特币從一個地址轉移到另一個地址,但(dàn)交易其實并不簡單。一筆交易包含一個或多個輸入和輸出,交易中的每個輸入的地址都提供比特币,每個輸出的地址都接受比特币。

 

一筆簡單的比特币交易,交易C花費(fèi)了從交易A和交易B獲得的0.008個比特币,其中0.001個比特币被當作交易費(fèi)付給礦工

 

上圖顯示了一筆簡單的比特币交易“C”,在這筆交易中,有0.005個比特币是(shì)在交易A中獲得的,0.003個比特币是(shì)在交易B中獲得的。(圖中箭頭是(shì)由新交易的輸入指向得到這些比特币的交易的輸出,所以比特币的流向是(shì)逆着箭頭方向的。)對于輸出,有0.003個比特币給了第一個比特币地址,有0.004個比特币給了第二個比特币地址,剩餘的0.001個比特币作爲交易費(fèi)付給礦工。請注意,本次交易并沒有影響到在交易A中另一個輸出爲0.015的比特币。 

 

在一筆交易中,輸入的比特币地址必須花出所有的比特币,假如你在之前的交易收到了100個比特币,但(dàn)你隻想花1個比特币,創建這筆交易你必須花完所有的100個比特币,那剩下的99個比特币怎麽辦呢?解決方案就是(shì)在交易中再增加一個輸出,将剩餘的99個比特币轉給自己。這樣你就可以花費(fèi)任意數額的比特币。 

 

通常交易要支付交易費(fèi),如果一筆交易中輸入的比特币總和大于輸出的比特币的總和,剩餘的費(fèi)用就是(shì)給礦工的交易費(fèi)。這筆費(fèi)用并沒有明确要求,但(dàn)是(shì)對于礦工而言,沒有交易費(fèi)的交易就會被列爲低優先級交易,可能要等上幾天才會被處理甚至被礦工直接丢棄。交易費(fèi)通常并不高,但(dàn)它可能影響着你的交易。

 

手動創建一筆交易

 

如下圖所示,在我的實驗中我發起了一筆隻有一個輸入一個輸出的交易。我在Coinbase上買了一些比特币,并将0.00101234個比特币放(fàng)入地址:

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

81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48,我的目标是(shì)創建一筆交易,将這些比特币轉入我的另一個地址:

1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,扣除0.0001個比特币的交易費(fèi)後,目标地址将獲得0.00091234個比特币。

比特币交易結構實例

 

 Blockchain.info上的交易記錄

https://blockchain.info/address/1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5?filter=4

 

按照協議(yì)标準,創建這筆交易很簡單。如下表所示,這筆交易隻有一個輸入,源自于81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48中的輸出0(第一個輸出)。輸出爲0.00091234個比特币(91234在十六進制中用0x016462表示),它以小端格式存儲在值區域中。加密過程中的scriptSig和scriptPubKey較爲複雜(zá),我們稍後再做讨論。

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

這是(shì)我生成交易使用的代碼,這段代碼隻是(shì)把數據打包成二進制文件。簽署交易較爲困難,我們等一會兒再說。

 


 

# 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

 

比特币交易怎樣簽署

 

下圖爲我們簡單描述了交易是(shì)如何簽署并相(xiàng)互連接的。針對中間這筆從比特币地址B轉賬到比特币地址C的交易。交易的内容(包括前一個交易的哈希值(索引))被進行哈希計算并用B的私鑰簽名。另外,B的公鑰也被包含在了交易中。 

 

通過執行幾個簡單運算,任何人都能驗證B是(shì)否簽署了這筆交易。首先,B的公鑰與之前收到這筆比特币交易的地址做驗證,證明B的公鑰有效。(正如前面所說的,地址很容易從公鑰中計算獲得)。接下來,可以通過B的公鑰驗證B交易簽名的真僞。這些步驟能确保交易的有效性和交易得到B的授權。比特币于衆不同的一點是(shì),B的公鑰在B發起交易之前是(shì)不公開的。 

 

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

 

比特币腳本語言

 

你可能會以爲僅僅通過在交易内容中附上簽名就可以簽署比特币交易,其實不然,這個過程十分複雜(zá)。實際上,每一筆交易中都包含一個“小程序”,用于确認交易是(shì)否有效。這個“小程序”用腳本語言寫成,通過這種基于堆棧的比特币腳本語言,我們可以應對許多複雜(zá)的比特币支付場景。例如,托管系統可以設定隻要經過三分之二的用戶授權,就可執行交易的規則,也可以設置其他的合約。 

 

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

 

前一個交易中的腳本稱爲scriptPubKey,當前交易中的腳本稱爲scriptSig。要驗證交易時,先執行scriptSig,然後再執行scriptPubKey。如果兩個腳本都成功執行,交易就被認定爲有效,交易中的比特币就可以成功花出。否則,交易無效。要注意的是(shì)前一個交易中的scriptPubKey規定了花費(fèi)比特币的條件,當前交易的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操作驗證驗證前兩個堆棧中的值是(shì)否相(xiàng)等(驗證這筆交易中你使用的比特币是(shì)否屬于你自己)-如果公鑰的哈希等于之前交易中的輸出地址,這就證明公鑰是(shì)有效的(證明這個比特币是(shì)你的)。最後,OP_CHECKSIG操作将檢查交易的簽名是(shì)否與堆棧裏的公鑰和簽名匹配,匹配就證明簽名是(shì)有效的(證明交易的到了你的授權)

 

簽署交易

 

我發現(xiàn)簽署這筆交易是(shì)手動使用比特币時最難的地方,這一過程出奇地困難且容易出錯。簽名的基本思想很簡單,使用橢圓曲線(xiàn)簽名算法和私鑰來生成交易的數字簽名,但(dàn)細節非常棘手。簽署交易的過程可以通過這19個步驟描述。

 

 

簽署交易的19個步驟

 

對交易的簽名讓我面臨巨大的挑戰,這涉及到一個如何在交易内容中還沒有加入簽名時簽署這筆交易的問題。爲了避免這個問題,在計算生成簽名之前,我把scriptPubKey這個腳本從上一筆交易複制到當前交易中(當前這筆交易正在被簽署),然後将簽名轉換爲腳本語言的代碼,創建嵌入在當前交易中的scriptSig腳本。對于具有多個輸入的交易,簽署交易環節更加複雜(zá),因爲每個輸入都需要單獨的簽名,這裏我就不做詳細讨論了。

 

哈希值這一步驟難倒了我。在簽名之前,交易中有一個臨時附加的哈希值常量。對于常規的交易,這個值是(shì)SIGHASH_ALL(0x00000001)。簽名後,這個哈希值将從交易内容的最後删除,附加到scriptSig腳本中。

 

在比特币中另一件令人讨厭(yàn)的事情是(shì)雖然簽名和公鑰都是(shì)512位的橢圓曲線(xiàn)值,但(dàn)它們的表示方式完全不同:簽名用DER編碼方式編碼,而公鑰用純字節表示。另外,兩個值都有一個額外的字節,但(dàn)位置并不一緻:SIGHASH_ALL這個附加的哈希值常量放(fàng)在簽名後面,而04這個值放(fàng)在公鑰前面。

 

由于ECDSA算法需要使用随機數,所以調試簽名十分困難。每次計算出的簽名都會有所不同,因此無法與已知(zhī)正确的簽名進行比較。

 

正是(shì)由于上述的複雜(zá)性,我花了很長時間才得到了一個簽名。不過,最終我找出了簽名代碼中所有的錯誤,并成功用它簽署了一筆交易。這是(shì)我使用的簽名代碼:

 

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)。 這證明這筆交易有效,我可以花費(fèi)這些比特币。

 

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腳本包含成功花費(fèi)比特币時必須執行的腳本。需要注意的是(shì),這個腳本将在未來花費(fèi)這些比特币的時候執行。它包含以十六進制表示而不是(shì)以Base58Check表示的目标地址1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,腳本的效果是(shì)隻有這個目标地址的私鑰所有者才能使用比特币,因此目标地址實際上是(shì)這些比特币的所有者

 

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

 

最終的交易

 

經過上述的一系列操作,我們完成了最終的交易。但(dàn)是(shì),别忘了,此時的交易還沒加入區塊鏈中,接收方還沒有收到你的比特币。

 

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

 

小插曲:橢圓曲線(xiàn)簽名

 

比特币的簽名算法使用到了橢圓曲線(xiàn)簽名算法,這麽實用的功能,你可能會好奇它是(shì)怎麽做到的?在當年英國數學家安德魯·懷爾斯攻克費(fèi)馬大定理時,我第一次接觸到了橢圓曲線(xiàn)的算法。橢圓曲線(xiàn)的數學思想很有意思,所以在這裏我給大家做一個快速的概述。

 

橢圓曲線(xiàn)這個叫法令人迷惑,因爲橢圓曲線(xiàn)并不是(shì)橢圓,而且看起來也不像橢圓,甚至橢圓曲線(xiàn)與橢圓相(xiàng)關性都很少。通俗地講,橢圓曲線(xiàn)就是(shì)滿足一個簡單方程y ^ 2 = x ^ 3 + ax + b的曲線(xiàn)。比特币中使用的稱爲secp256k1的橢圓曲線(xiàn),它滿足的方程爲y ^ 2 = x ^ 3 + 7。

 

 

secp256k1橢圓曲線(xiàn)

 

橢圓曲線(xiàn)的一個重要特性就是(shì)你可以用一個簡單的規則來定義橢圓曲線(xiàn)上點的相(xiàng)加:如果在曲線(xiàn)上繪制一條直線(xiàn),這條直線(xiàn)與曲線(xiàn)交與A,B,C三個點,那麽這個加法定義爲A+B+C=0。由這個加法的定義,我們可以定義整數乘法:例如4A = A + A + A + A。

 

爲什麽橢圓曲線(xiàn)在密碼學上很有用?因爲橢圓曲線(xiàn)做整數乘法運算速度很快,但(dàn)做除法時需要蠻力。例如,你可以快速地計算一個乘法12345678*A = Q,但(dàn)是(shì)如果你隻知(zhī)道A和Q,求解n*A=Q中的n十分困難。因此在橢圓曲線(xiàn)算法中,這裏的12345678将是(shì)私鑰,曲線(xiàn)上的點Q将是(shì)公鑰。

 

在密碼學中,點的坐标并不是(shì)它在曲線(xiàn)上的實值點,而是(shì)對整數的模數。橢圓曲線(xiàn)的一個好用的特性就是(shì)對實數或模數進行運算的數學運算幾乎相(xiàng)同。正因爲如此,比特币的橢圓曲線(xiàn)并不像上面的圖片,而是(shì)一團雜(zá)亂無章的256位點集(想想在一個空間中充滿了大量雜(zá)亂無章的點)。

 

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

 

把交易發送到比特币網絡

 

回到交易中來,别忘了此時我們的交易還沒有被加入到區塊鏈中,還不是(shì)一筆有效交易。剛剛我創建并簽署了一筆交易。下一步就是(shì)将這筆交易發送到比特币網絡中,網絡中的礦工會收集交易并把它打包進區塊中。

 

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

 

首先我要在比特币的點對點網絡中找到一個節點。節點的列表會随節點的進出動态更新,當一個比特币節點連接到另一個節點時,它們就會不斷交換彼此新發現(xiàn)的比特币節點信息,因此,新節點加入的消息會快速地傳遍整個網絡。

 

然而,新的比特币節點如何第一次找到比特币節點?這是(shì)一個先有雞還是(shì)先有蛋的問題。比特币節點通過以下幾種方法來解決這個問題。有幾個可信的比特币節點會以bitseed.xf2.org的域名在DNS系統(Domain Name System,域名系統,萬維網上作爲域名和IP地址相(xiàng)互映射的一個分布式數據庫)上注冊,通過執行nslookup命令,你就可以得到這些節點的IP地址,隻要有一個在工作即可。如果很不幸它們都沒有工作的話(huà),你可以試着連接那幾個已經在你的客戶端中硬編碼記錄下來的地址。

 

 

Nslookup命令可以用來尋找比特币節點

 

當用戶啓動或停止比特币客戶端時,節點就會加入或離(lí)開比特币網絡。所以連接節點有很大的不确定性,在我實驗時,就遇到了連接的節點已經離(lí)開比特币網絡的情況,如果你想重複我的實驗,最好多找幾個節點,可能需要多次嘗試才能找到一個運行着的節點。

 

與比特币節點通信

 

一旦獲得了一個正在工作的比特币節點的IP地址,當務之急就通過這個節點是(shì)把我的交易發送到比特币的點對點網絡中。使用點對點的網絡協議(yì)十分簡單,我在端口8333上打開了一個到任意對等端的TCP連接,發送消息,然後接受反饋消息。比特币的點對點協議(yì)對用戶很友(yǒu)好,即使我的請求數據出錯時,還是(shì)繼續與我保持通信。 

 

重要提示:正如一些人指出的那樣,如果你想重複我的實驗,切記要使用比特币的測試網絡,在測試網絡上,你可以使用“虛拟”的比特币來進行交易。因爲在真實網絡上,萬一你不小心,有可能會失去(qù)所有的比特币。還記得上面提到的那個100個比特币轉賬1個的交易麽,如果你忘了将剩餘的比特币轉給自己,那麽剩餘的99個比特币就會作爲交易費(fèi)支付給礦工。但(dàn)是(shì)本着科學的态度,我并不在意在真實的比特币網絡中損失我這些價值1美元的比特币。

 

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

 

連接到比特币網絡的第一步就是(shì)通過交換客戶端版本信息來建立連接。首先,我發送了一條客戶端版本信息,其中包含我的協議(yì)版本号,IP地址和其他内容。比特币節點也向我回複了它的版本信息。在此之後,我應該回複一個verack信息(version acknowledgement,版本确認)來确認它的版本信息。正如我所說,比特币點對點網絡協議(yì)對用戶十分友(yǒu)好,即使我跳(tiào)過了verack信息,之後的操作也是(shì)一切正常。 

 

交換版本信息這一步并不簡單,因爲信息具有标準的格式,不過不用害怕,可以用幾行代碼來創建這些信息。下面代碼段中的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進制的字符串是(shì)我之前創建的交易。

 

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(一個抓取,分析網絡封包的軟件)軟件的截圖顯示出我是(shì)如何将交易發送到比特币網絡中的。我用Python編寫了腳本來分析網絡數據,爲了簡單起見(jiàn),在這裏我使用Wireshark。從圖中可以看到我的這筆tx交易。

 

 

 

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

 

爲了實時監控我這筆交易的進度,我在比特币網絡中新運行了一個節點,在把我交易發到比特币網絡5秒鍾之後,另一個節點給我發送了這個tx消息,其中包含我剛剛發送的這筆交易的哈希,由此可見(jiàn),在僅僅這幾秒中,我的交易已經傳遍了比特币網絡,至少也是(shì)比特币網絡的一部分。

 

交易成功:我的交易被加入區塊鏈

 

在将我的交易發送比特币網絡之後,我需要等待它被礦工開采出來加入到區塊鏈中,然後才能宣稱我的實驗圓滿成功。10分鍾後,我的比特币節點收到一條含有新區塊信息的inv消息(參見(jiàn)下圖Wireshark抓到的網絡封包),檢查這個區塊後發現(xiàn)我的交易被包含在了區塊中,證明我的交易是(shì)有效的,我的實驗成功了。通過我的比特币錢包軟件和在線(xiàn)查詢,再一次确認了我已經交易成功。可以說,經過不斷的努力,我成功手動創建了一筆交易,并讓比特币系統接受了它。(當然了,我也經過了幾次失敗的嘗試,這些錯誤的交易都消失在了網絡之中,永遠都不會被檢索到。

 

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

 

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

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

(https://blockchain.info/block-index/341440/0000000000000001a27b1d6eb8c405410398ece796e742da3b3e35363c2219ee)

 

挖到這個區塊的礦工們收到了25個比特币的獎勵,交易費(fèi)總共是(shì)0.104個比特币,按當時的市價分别爲19000美元和80美元。我支付了0.0001個比特币的交易費(fèi),大約是(shì)我交易額的10%,按當時的市價爲8美分。

 

結論

 

手動進行比特币交易比我想象中困難得多,但(dàn)是(shì)在這個過程中我學到了很多,希望你也是(shì)。我的Python代碼僅僅是(shì)爲了介紹,如果你想跟我一樣用Python手動進行比特币交易,也可以試試這幾個項目。

 

https://en.bitcoin.it/wiki/Bitcoin-python

https://github.com/richardkiss/pycoin

https://github.com/jgarzik/python-bitcoinlib

 

 

寫在最後

2017年是(shì)區塊鏈的井噴之年,經過一年的積攢,2018年将迎來區塊鏈的落地之年,區塊鏈會逐漸颠覆各行各業。對于個人,區塊鏈的機會會越來越多,也許你錯過了比特币的投資,不妨現(xiàn)在抓住區塊鏈這個風口,投資自己,多學習相(xiàng)關知(zhī)識,區塊鏈大有可爲,投身區塊鏈的你将大有作爲!

*聲明:推送内容及圖片來源于網絡,部分内容會有所改動,版權歸原作者所有,如來源信息有誤或侵犯權益,請聯系我們删除或授權事宜。