一文看懂怎樣用 Python 建立比特幣交易
比特幣價格的上上下下,始終撩動著每一個人無比關切的小心臟。從去年初的 800 美元左右,飛漲到去年底到 19783.21 美元最高點,不到1年,便有將近 25 倍的升值速度。儘管眼下又掉回 8000 多美元的價格,但價格差不多能搞出去年同期一個數量級,幣圈人士“過去一年比以往 10 年掙的都多”,已經是不爭的事實。
而對區塊鏈開發者來說,據說也已經有拿到年新 500 萬的天價。所以“跑步進入區塊鏈”,已經成為不少程式設計師的共識。但是看過很多遠離,我們如何才能迅速上手呢?國外網友 Ken Shirriff 在部落格中分享了他在手動茶古劍比特幣交易時的程式碼與對比特幣協議的心得,區塊鏈大本營編譯如下。
近期,媒體行業對比特幣表現出極大的熱情,這鼓舞著我從網路底層的資料流開始,認真學習比特幣的工作原理。通常人們會使用錢包軟體來進行比特幣交易,錢包軟體在方便使用者的同時,向用戶隱藏了比特幣的交易流程,而我想親自動手來體驗比特幣交易,我的目標是用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/address/1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa 看到,也算是為教學做貢獻了。)
總之,共有三種金鑰:私鑰,公鑰,公鑰的雜湊值,經過使用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/address/1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5?filter=4
按照協議標準,建立這筆交易很簡單。如下表所示,這筆交易只有一個輸入,源自於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。
為什麼橢圓曲線在密碼學上很有用?因為橢圓曲線做整數乘法運算速度很快,但做除法時需要蠻力。例如,你可以快速地計算一個乘法12345678*A = Q,但是如果你只知道A和Q,求解n*A=Q中的n十分困難。因此在橢圓曲線演算法中,這裡的12345678將是私鑰,曲線上的點Q將是公鑰。
在密碼學中,點的座標並不是它在曲線上的實值點,而是對整數的模數。橢圓曲線的一個好用的特性就是對實數或模數進行運算的數學運算幾乎相同。正因為如此,比特幣的橢圓曲線並不像上面的圖片,而是一團雜亂無章的256位點集(想想在一個空間中充滿了大量雜亂無章的點)。
橢圓曲線數字簽名演算法(ECDSA)接收交易的雜湊值,使用該交易資料,私鑰,以及一個隨機數從橢圓曲線上生成一個新的點,從而實現對交易的簽名。任何擁有公鑰,交易資料,和簽名的人都可以通過做一個簡單的橢圓曲線運算來驗證簽名的有效性。讀到這裡,你應該明白了為什麼只有擁有私鑰的人才能簽署訊息,但擁有公鑰的任何人都可以驗證該訊息。
把交易傳送到比特幣網路
回到交易中來,別忘了此時我們的交易還沒有被加入到區塊鏈中,還不是一筆有效交易。剛剛我建立並簽署了一筆交易。下一步就是將這筆交易傳送到比特幣網路中,網路中的礦工會收集交易並把它打包進區塊中。
如何找到比特幣網路的節點
首先我要在比特幣的點對點網路中找到一個節點。節點的列表會隨節點的進出動態更新,當一個比特幣節點連線到另一個節點時,它們就會不斷交換彼此新發現的比特幣節點資訊,因此,新節點加入的訊息會快速地傳遍整個網路。
然而,新的比特幣節點如何第一次找到比特幣節點?這是一個先有雞還是先有蛋的問題。比特幣節點通過以下幾種方法來解決這個問題。有幾個可信的比特幣節點會以bitsee