比特幣價(jià)格的上上下下,始終撩動(dòng)著每一個(gè)人無(wú)比關(guān)切的小心臟。從去年初的 800 美元左右,飛漲到去年底到 19783.21 美元最高點(diǎn),不到1年,便有將近 25 倍的升值速度。盡管眼下又掉回 8000 多美元的價(jià)格,但價(jià)格差不多能搞出去年同期一個(gè)數(shù)量級(jí),幣圈人士“過(guò)去一年比以往 10 年掙的都多”,已經(jīng)是不爭(zhēng)的事實(shí)。
而對(duì)區(qū)塊鏈開(kāi)發(fā)者來(lái)說(shuō),據(jù)說(shuō)也已經(jīng)有拿到年新 500 萬(wàn)的天價(jià)。所以“跑步進(jìn)入?yún)^(qū)塊鏈”,已經(jīng)成為不少程序員的共識(shí)。但是看過(guò)很多遠(yuǎn)離,我們?nèi)绾尾拍苎杆偕鲜帜兀繃?guó)外網(wǎng)友 Ken Shirriff 在博客中分享了他在手動(dòng)茶古劍比特幣交易時(shí)的代碼與對(duì)比特幣協(xié)議的心得,區(qū)塊鏈大本營(yíng)編譯如下。
近期,媒體行業(yè)對(duì)比特幣表現(xiàn)出極大的熱情,這鼓舞著我從網(wǎng)絡(luò)底層的數(shù)據(jù)流開(kāi)始,認(rèn)真學(xué)習(xí)比特幣的工作原理。通常人們會(huì)使用錢(qián)包軟件來(lái)進(jìn)行比特幣交易,錢(qián)包軟件在方便用戶的同時(shí),向用戶隱藏了比特幣的交易流程,而我想親自動(dòng)手來(lái)體驗(yàn)比特幣交易,我的目標(biāo)是用Python手動(dòng)創(chuàng)建一筆比特幣交易,以十六進(jìn)制數(shù)據(jù)的形式將交易廣播到比特幣網(wǎng)絡(luò)中,然后觀察這筆交易是怎么被加入到區(qū)塊鏈中的。事實(shí)證明,這個(gè)過(guò)程很有趣,希望你也對(duì)它感興趣。
在本篇文章中,首先我會(huì)對(duì)比特幣進(jìn)行一個(gè)簡(jiǎn)單的概述,之后,我會(huì)從以下幾個(gè)方面帶領(lǐng)你們學(xué)習(xí)比特幣:創(chuàng)建一個(gè)比特幣地址(比特幣中的賬戶),進(jìn)行一筆比特幣交易,簽署交易,將交易廣播到比特幣網(wǎng)絡(luò)中,最后等待交易的確認(rèn)。
比特幣簡(jiǎn)述:
首先,我會(huì)介紹一下比特幣系統(tǒng)是怎么運(yùn)轉(zhuǎn)的,然后再深入探討整個(gè)細(xì)節(jié)。比特幣是一個(gè)基于點(diǎn)對(duì)點(diǎn)網(wǎng)絡(luò)的電子貨幣,你可以用現(xiàn)金在網(wǎng)上購(gòu)買(mǎi)比特幣,用比特幣向他人轉(zhuǎn)賬,在有些商家,你可以像使用支付寶一樣使用比特幣付款,當(dāng)然,你也可以賣(mài)出所持有的比特幣換回現(xiàn)金。
簡(jiǎn)而言之,在比特幣網(wǎng)絡(luò)中,分布式賬本(區(qū)塊鏈)記錄并隨時(shí)更新著每個(gè)比特幣的所有權(quán)。與銀行不同的是,比特幣并沒(méi)有與個(gè)人或個(gè)人的賬戶綁定,相反的,比特幣只屬于一個(gè)個(gè)比特幣地址,比如:1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa。這里你可能已經(jīng)繞暈了,難道這段字符中藏著比特幣?當(dāng)然不是,比特幣地址是比特幣網(wǎng)絡(luò)中的一個(gè)身份,也可以通俗地說(shuō)是你在比特幣中開(kāi)的一個(gè)“銀行賬戶”,我們用這個(gè)“賬戶”來(lái)進(jìn)行交易。在網(wǎng)站:blockchain.info中,你可以查到所有的交易信息:
?比特幣賬戶信息
但是怎么證明這個(gè)賬戶是我的呢,不急,先往下看,你的疑問(wèn)我會(huì)為你一一解答。
比特幣交易
如何像使用現(xiàn)金一樣使用比特幣呢?答案是創(chuàng)建一筆交易。在一筆交易中,比特幣的所有者(上文提到過(guò)比特幣的所有者是比特幣地址)將所有權(quán)轉(zhuǎn)移到一個(gè)新的比特幣地址。比特幣的一個(gè)顛覆性創(chuàng)新就是通過(guò)鼓勵(lì)節(jié)點(diǎn)記賬(也叫礦工挖礦),將交易記錄放在一個(gè)分布式的數(shù)據(jù)庫(kù)中。交易被集合在區(qū)塊中,大概每十分鐘比特幣網(wǎng)絡(luò)中產(chǎn)生一個(gè)新的區(qū)塊,成為交易記錄的一部分,稱(chēng)為區(qū)塊鏈。加入到區(qū)塊鏈中的交易可以被認(rèn)為是一筆成功的交易?,F(xiàn)在問(wèn)題來(lái)了,誰(shuí)來(lái)給你記賬呢?是礦工,礦工的挖礦過(guò)程就是在往區(qū)塊鏈中記賬,礦工要核實(shí)每筆交易是否正確,核實(shí)完后,礦工們就開(kāi)始算一道很難的數(shù)學(xué)題(密碼學(xué)中的哈希函數(shù)),最早算出答案的人就能生成一個(gè)區(qū)塊,也叫挖出了一個(gè)新的區(qū)塊,這個(gè)區(qū)塊將成為區(qū)塊鏈的新一部分。
也許你會(huì)問(wèn)了,明明是記賬,干著會(huì)計(jì)的活,為什么要叫挖礦呢?和傳統(tǒng)的在地下挖礦石一樣,比特幣挖礦也是會(huì)有收獲的。挖礦是一種新發(fā)行比特幣的過(guò)程,當(dāng)前,每挖到一個(gè)礦,礦工會(huì)得到系統(tǒng)獎(jiǎng)勵(lì)的12.5個(gè)比特幣,按目前一個(gè)比特幣接近一萬(wàn)美元的市價(jià),這就是一筆12.5萬(wàn)美元的巨款。此外,礦工還可以獲得本區(qū)塊中所有的交易費(fèi),舉例來(lái)說(shuō),在高度為512587的區(qū)塊中,幸運(yùn)的礦工總共收獲了12.829個(gè)比特幣。正因如此,礦工之間的競(jìng)爭(zhēng)十分激烈,采礦的難度與礦工間激烈的競(jìng)爭(zhēng)是比特幣安全的重要保證,因?yàn)檫@樣可以保證沒(méi)有壞人能操縱系統(tǒng)。
點(diǎn)對(duì)點(diǎn)網(wǎng)絡(luò)
比特幣并沒(méi)有一個(gè)中央服務(wù)器,相反,比特幣在一個(gè)點(diǎn)對(duì)點(diǎn)網(wǎng)絡(luò)中運(yùn)行。如果你運(yùn)行一個(gè)比特幣節(jié)點(diǎn),那你就成了網(wǎng)絡(luò)的一部分。比特幣網(wǎng)絡(luò)中的節(jié)點(diǎn)彼此交換自己存儲(chǔ)的交易,區(qū)塊,以及IP地址信息(用于節(jié)點(diǎn)間建立連接互相通信)。當(dāng)你第一次連接到比特幣網(wǎng)絡(luò),你的節(jié)點(diǎn)會(huì)從隨機(jī)挑選的節(jié)點(diǎn)中下載區(qū)塊鏈的信息。反過(guò)來(lái),你的節(jié)點(diǎn)也會(huì)向后加入者提供信息。當(dāng)你要?jiǎng)?chuàng)建一筆比特幣交易時(shí),你要把這筆交易發(fā)送給一些節(jié)點(diǎn),這些節(jié)點(diǎn)會(huì)在比特幣網(wǎng)絡(luò)中廣播這筆交易,直到全網(wǎng)都收到這筆交易。礦工們會(huì)收集你的交易信息,生成一個(gè)含有你這筆交易的區(qū)塊,向全網(wǎng)廣播,這時(shí),你的節(jié)點(diǎn)也會(huì)收到這個(gè)區(qū)塊信息,通過(guò)驗(yàn)證,這筆交易被加入到了區(qū)塊鏈中,你就交易成功了。
加密技術(shù)
現(xiàn)在回到證明比特幣賬戶是誰(shuí)的這個(gè)問(wèn)題。比特幣使用數(shù)字簽名技術(shù)以確保只有比特幣賬戶的所有者才能使用賬戶中的比特幣。比特幣地址的所有者擁有與該地址相匹配的私鑰,當(dāng)花費(fèi)比特幣時(shí),你用要這個(gè)私鑰在交易上簽名,證明自己是這個(gè)賬戶的所有者。這有點(diǎn)像現(xiàn)實(shí)生活中的蓋章,蓋章就意味著授權(quán)。怎么驗(yàn)證呢,公鑰與比特幣賬戶相關(guān)聯(lián),用公鑰就可以驗(yàn)證簽名是否正確。這樣就解決了比特幣賬戶是誰(shuí)的這個(gè)問(wèn)題。
怎么來(lái)區(qū)分不同的交易呢?交易和區(qū)塊都使用密碼學(xué)上的哈希值進(jìn)行索引,是不是有點(diǎn)耳熟,對(duì),在比特幣協(xié)議中,多處使用到了哈希函數(shù),礦工們剛才算的數(shù)學(xué)題就是在算哈希函數(shù)。
比特幣協(xié)議探究
在接下來(lái)的文章里,我將逐步介紹我是怎樣手動(dòng)進(jìn)行一次比特幣交易的。首先,我生成了一個(gè)比特幣賬戶以及對(duì)應(yīng)的公鑰,私鑰。接下來(lái)我發(fā)起了一筆比特幣交易,我向這個(gè)新生成的賬戶轉(zhuǎn)了一小筆比特幣。期間手動(dòng)簽署這筆交易很困難,它花費(fèi)了我很多的時(shí)間。最后,我將這筆交易發(fā)送到比特幣網(wǎng)絡(luò),等待它被加入?yún)^(qū)塊鏈。本文的其余部分會(huì)詳細(xì)地介紹這些步驟。
事實(shí)證明,手動(dòng)進(jìn)行比特幣交易比我想象中的更加困難。正如你所看到的,比特幣的協(xié)議有些許混亂:它使用了大端格式數(shù)字(高位編址,將高序字節(jié)存儲(chǔ)在起始地址),小端格式數(shù)字(低位編址,將低序字節(jié)存儲(chǔ)在起始位置),固定長(zhǎng)度數(shù)字,可變長(zhǎng)度數(shù)字,自定義編碼格式,DER編碼格式以及各種加密算法。因此,僅僅是將數(shù)據(jù)轉(zhuǎn)換為正確的格式就浪費(fèi)了很多時(shí)間。
我遇到的第二個(gè)難題就是加密,嘗試一下手動(dòng)加密,你就會(huì)發(fā)現(xiàn)密碼學(xué)對(duì)人們多不友好,甚至可以說(shuō)是無(wú)情。即使你只輸錯(cuò)了一個(gè)字節(jié),交易就會(huì)因出錯(cuò)被拒絕,而且它不會(huì)告訴你哪里出錯(cuò)了,你只能重來(lái)。
最后,手動(dòng)簽署交易的過(guò)程也比想象中難得多,簽署交易時(shí)每個(gè)環(huán)節(jié)都必須零失誤,要么又要退回重來(lái)。
比特幣地址和密鑰
第一步,我創(chuàng)建了一個(gè)比特幣地址。通常情況下,人們都是使用比特幣客戶端軟件來(lái)創(chuàng)建比特幣地址和與之相關(guān)的密鑰。本著學(xué)習(xí)的態(tài)度,我寫(xiě)了一些Python代碼來(lái)生成比特幣地址,從而揭示地址創(chuàng)建的機(jī)理。
比特幣使用了一系列的密鑰和地址,下圖解釋了它們的關(guān)系。首先你要?jiǎng)?chuàng)建一個(gè)隨機(jī)的256位的私鑰,這個(gè)私鑰用于在花費(fèi)比特幣時(shí)簽署交易。因此,私鑰必須保密,否則你的比特幣可能會(huì)被盜用。
橢圓曲線數(shù)字簽名算法(Elliptic Curve Digital Signature Algorithm,ECDSA,美國(guó)政府的標(biāo)準(zhǔn),接下來(lái)我們會(huì)討論它)會(huì)從私鑰中生成一個(gè)512位的公鑰,這個(gè)公鑰用于驗(yàn)證交易的簽名。但不方便的是,比特幣協(xié)議中需要在這個(gè)公鑰上添加了前綴04,這個(gè)公鑰在交易簽署之前不會(huì)被泄露,不像其它系統(tǒng)中公鑰就是為了公之于眾的。
比特幣地址與公鑰的關(guān)系
下一步就是生成與他人交易時(shí)使用的比特幣地址了。512位的公鑰太長(zhǎng)不方便使用,因此使用SHA-256和RIPEMD哈希算法將其縮小為160位。然后使用比特幣定義的Base58Check 編碼將密鑰編碼為ASCII(American Standard Code for Information Interchange,美國(guó)信息交換標(biāo)準(zhǔn)代碼)格式。得到的地址(例如上文中的:1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa)就是你接收別人比特幣時(shí)要發(fā)布的地址。需要注意的是,你無(wú)法從比特幣地址中復(fù)原出公鑰或私鑰。如果你丟失了你的私鑰(比如說(shuō)你把私鑰存在你的硬盤(pán)上,但硬盤(pán)丟失),你的比特幣將永遠(yuǎn)丟失。
最后,錢(qián)包交換格式密鑰(WIF)用于將私鑰添加到你的錢(qián)包軟件中,這只是將私鑰進(jìn)行Base58Check編碼轉(zhuǎn)換為ASCII格式,這一步是可逆的,而且很容易經(jīng)過(guò)逆變換恢復(fù)出256位的私鑰。(圖中有我的私鑰,我很好奇是否有人會(huì)用我的私鑰去偷(通過(guò)私鑰簽署交易,從而轉(zhuǎn)走)我那價(jià)值80美分的比特幣,然而真有人那么做了,可以在
https://blockchain.info/address/1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa 看到,也算是為教學(xué)做貢獻(xiàn)了。)
總之,共有三種密鑰:私鑰,公鑰,公鑰的哈希值,經(jīng)過(guò)使用Base58Check編碼,它們對(duì)外都是以ASCII格式表示。私鑰是其中最重要的密鑰,因?yàn)榛ㄙM(fèi)比特幣時(shí)需要私鑰簽署交易,而且其他的密鑰都可以從私鑰中產(chǎn)生。公鑰的哈希值就是你們剛看的的比特幣地址。
我使用下面的代碼片段來(lái)生成WIF格式的私鑰和地址。私鑰只是一個(gè)隨機(jī)的256位的數(shù)字,使用橢圓曲線數(shù)字簽名算法從私鑰中生成公鑰,公鑰使用SHA-256算法,RIPEMD-160算法進(jìn)行哈希計(jì)算,再經(jīng)Base58編碼并進(jìn)行校驗(yàn)后得到比特幣地址。最后,私鑰用Base58Check編碼以生成用于將私鑰輸入錢(qián)包軟件的WIF編碼。注意,這段Python隨機(jī)函數(shù)代碼在密碼學(xué)上安全性并不高,如果你想要嘗試這一步驟,建議使用更安全的錢(qián)包軟件來(lái)生成比特幣地址和密鑰。
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
從內(nèi)部分析一筆交易
交易是比特幣系統(tǒng)的基本操作,也許你會(huì)認(rèn)為交易就是簡(jiǎn)單地把比特幣從一個(gè)地址轉(zhuǎn)移到另一個(gè)地址,但交易其實(shí)并不簡(jiǎn)單。一筆交易包含一個(gè)或多個(gè)輸入和輸出,交易中的每個(gè)輸入的地址都提供比特幣,每個(gè)輸出的地址都接受比特幣。
一筆簡(jiǎn)單的比特幣交易,交易C花費(fèi)了從交易A和交易B獲得的0.008個(gè)比特幣,其中0.001個(gè)比特幣被當(dāng)作交易費(fèi)付給礦工
上圖顯示了一筆簡(jiǎn)單的比特幣交易“C”,在這筆交易中,有0.005個(gè)比特幣是在交易A中獲得的,0.003個(gè)比特幣是在交易B中獲得的。(圖中箭頭是由新交易的輸入指向得到這些比特幣的交易的輸出,所以比特幣的流向是逆著箭頭方向的。)對(duì)于輸出,有0.003個(gè)比特幣給了第一個(gè)比特幣地址,有0.004個(gè)比特幣給了第二個(gè)比特幣地址,剩余的0.001個(gè)比特幣作為交易費(fèi)付給礦工。請(qǐng)注意,本次交易并沒(méi)有影響到在交易A中另一個(gè)輸出為0.015的比特幣。
在一筆交易中,輸入的比特幣地址必須花出所有的比特幣,假如你在之前的交易收到了100個(gè)比特幣,但你只想花1個(gè)比特幣,創(chuàng)建這筆交易你必須花完所有的100個(gè)比特幣,那剩下的99個(gè)比特幣怎么辦呢?解決方案就是在交易中再增加一個(gè)輸出,將剩余的99個(gè)比特幣轉(zhuǎn)給自己。這樣你就可以花費(fèi)任意數(shù)額的比特幣。
通常交易要支付交易費(fèi),如果一筆交易中輸入的比特幣總和大于輸出的比特幣的總和,剩余的費(fèi)用就是給礦工的交易費(fèi)。這筆費(fèi)用并沒(méi)有明確要求,但是對(duì)于礦工而言,沒(méi)有交易費(fèi)的交易就會(huì)被列為低優(yōu)先級(jí)交易,可能要等上幾天才會(huì)被處理甚至被礦工直接丟棄。交易費(fèi)通常并不高,但它可能影響著你的交易。
手動(dòng)創(chuàng)建一筆交易
如下圖所示,在我的實(shí)驗(yàn)中我發(fā)起了一筆只有一個(gè)輸入一個(gè)輸出的交易。我在Coinbase上買(mǎi)了一些比特幣,并將0.00101234個(gè)比特幣放入地址:
1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5中,這筆交易哈希為:
81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48,我的目標(biāo)是創(chuàng)建一筆交易,將這些比特幣轉(zhuǎn)入我的另一個(gè)地址:
1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,扣除0.0001個(gè)比特幣的交易費(fèi)后,目標(biāo)地址將獲得0.00091234個(gè)比特幣。
比特幣交易結(jié)構(gòu)實(shí)例
? Blockchain.info上的交易記錄
https://blockchain.info/address/1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5?filter=4
按照協(xié)議標(biāo)準(zhǔn),創(chuàng)建這筆交易很簡(jiǎn)單。如下表所示,這筆交易只有一個(gè)輸入,源自于81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48中的輸出0(第一個(gè)輸出)。輸出為0.00091234個(gè)比特幣(91234在十六進(jìn)制中用0x016462表示),它以小端格式存儲(chǔ)在值區(qū)域中。加密過(guò)程中的scriptSig和scriptPubKey較為復(fù)雜,我們稍后再做討論。
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ù)據(jù)打包成二進(jìn)制文件。簽署交易較為困難,我們等一會(huì)兒再說(shuō)。
# 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
比特幣交易怎樣簽署
下圖為我們簡(jiǎn)單描述了交易是如何簽署并相互連接的。針對(duì)中間這筆從比特幣地址B轉(zhuǎn)賬到比特幣地址C的交易。交易的內(nèi)容(包括前一個(gè)交易的哈希值(索引))被進(jìn)行哈希計(jì)算并用B的私鑰簽名。另外,B的公鑰也被包含在了交易中。
通過(guò)執(zhí)行幾個(gè)簡(jiǎn)單運(yùn)算,任何人都能驗(yàn)證B是否簽署了這筆交易。首先,B的公鑰與之前收到這筆比特幣交易的地址做驗(yàn)證,證明B的公鑰有效。(正如前面所說(shuō)的,地址很容易從公鑰中計(jì)算獲得)。接下來(lái),可以通過(guò)B的公鑰驗(yàn)證B交易簽名的真?zhèn)?。這些步驟能確保交易的有效性和交易得到B的授權(quán)。比特幣于眾不同的一點(diǎn)是,B的公鑰在B發(fā)起交易之前是不公開(kāi)的。
在比特幣系統(tǒng)中,比特幣通過(guò)區(qū)塊鏈上的一筆筆交易在不同的地址間傳遞。區(qū)塊鏈上的每一筆交易都能被驗(yàn)證以確保比特幣交易的有效性。
比特幣腳本語(yǔ)言
你可能會(huì)以為僅僅通過(guò)在交易內(nèi)容中附上簽名就可以簽署比特幣交易,其實(shí)不然,這個(gè)過(guò)程十分復(fù)雜。實(shí)際上,每一筆交易中都包含一個(gè)“小程序”,用于確認(rèn)交易是否有效。這個(gè)“小程序”用腳本語(yǔ)言寫(xiě)成,通過(guò)這種基于堆棧的比特幣腳本語(yǔ)言,我們可以應(yīng)對(duì)許多復(fù)雜的比特幣支付場(chǎng)景。例如,托管系統(tǒng)可以設(shè)定只要經(jīng)過(guò)三分之二的用戶授權(quán),就可執(zhí)行交易的規(guī)則,也可以設(shè)置其他的合約。
腳本語(yǔ)言十分復(fù)雜,大約有80種操作碼,包括算數(shù)計(jì)算,按位操作,字符串處理,條件語(yǔ)句和堆棧操作。腳本語(yǔ)言也包含一些必要的密碼學(xué)操作(SHA-256,RIPEMD等等)作為原語(yǔ)(原語(yǔ)是執(zhí)行過(guò)程中不可被打斷的基本操作,你可以理解為一段代碼)。為了確保腳本語(yǔ)言可以運(yùn)行完畢自動(dòng)退出,該語(yǔ)言不支持任何循環(huán)操作,因此它不是圖靈完備的。然而,實(shí)際上,它只支持少數(shù)類(lèi)型的交易。
前一個(gè)交易中的腳本稱(chēng)為scriptPubKey,當(dāng)前交易中的腳本稱(chēng)為scriptSig。要驗(yàn)證交易時(shí),先執(zhí)行scriptSig,然后再執(zhí)行scriptPubKey。如果兩個(gè)腳本都成功執(zhí)行,交易就被認(rèn)定為有效,交易中的比特幣就可以成功花出。否則,交易無(wú)效。要注意的是前一個(gè)交易中的scriptPubKey規(guī)定了花費(fèi)比特幣的條件,當(dāng)前交易的scriptSig必須滿足這個(gè)條件。
在一個(gè)標(biāo)準(zhǔn)的交易中,scriptSig腳本將從私鑰中生成的簽名并壓入堆棧中,再壓入公鑰。接下來(lái)scriptPubKey腳本會(huì)執(zhí)行運(yùn)算先驗(yàn)證公鑰的有效性,再驗(yàn)證簽名的有效性。
正如腳本中所表示,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
當(dāng)這段代碼執(zhí)行時(shí),PUSHDATA操作首先會(huì)把簽名壓入堆棧,接著把公鑰壓入堆棧。OPHASH-160操作計(jì)算公鑰的160位哈希值,PUSHDATA操作再把交易中的輸入地址(輸入賬號(hào))壓入堆棧,然后,OP-EQUALVERIFY操作驗(yàn)證驗(yàn)證前兩個(gè)堆棧中的值是否相等(驗(yàn)證這筆交易中你使用的比特幣是否屬于你自己)-如果公鑰的哈希等于之前交易中的輸出地址,這就證明公鑰是有效的(證明這個(gè)比特幣是你的)。最后,OP_CHECKSIG操作將檢查交易的簽名是否與堆棧里的公鑰和簽名匹配,匹配就證明簽名是有效的(證明交易的到了你的授權(quán))
簽署交易
我發(fā)現(xiàn)簽署這筆交易是手動(dòng)使用比特幣時(shí)最難的地方,這一過(guò)程出奇地困難且容易出錯(cuò)。簽名的基本思想很簡(jiǎn)單,使用橢圓曲線簽名算法和私鑰來(lái)生成交易的數(shù)字簽名,但細(xì)節(jié)非常棘手。簽署交易的過(guò)程可以通過(guò)這19個(gè)步驟描述。
簽署交易的19個(gè)步驟
對(duì)交易的簽名讓我面臨巨大的挑戰(zhàn),這涉及到一個(gè)如何在交易內(nèi)容中還沒(méi)有加入簽名時(shí)簽署這筆交易的問(wèn)題。為了避免這個(gè)問(wèn)題,在計(jì)算生成簽名之前,我把scriptPubKey這個(gè)腳本從上一筆交易復(fù)制到當(dāng)前交易中(當(dāng)前這筆交易正在被簽署),然后將簽名轉(zhuǎn)換為腳本語(yǔ)言的代碼,創(chuàng)建嵌入在當(dāng)前交易中的scriptSig腳本。對(duì)于具有多個(gè)輸入的交易,簽署交易環(huán)節(jié)更加復(fù)雜,因?yàn)槊總€(gè)輸入都需要單獨(dú)的簽名,這里我就不做詳細(xì)討論了。
哈希值這一步驟難倒了我。在簽名之前,交易中有一個(gè)臨時(shí)附加的哈希值常量。對(duì)于常規(guī)的交易,這個(gè)值是SIGHASH_ALL(0x00000001)。簽名后,這個(gè)哈希值將從交易內(nèi)容的最后刪除,附加到scriptSig腳本中。
在比特幣中另一件令人討厭的事情是雖然簽名和公鑰都是512位的橢圓曲線值,但它們的表示方式完全不同:簽名用DER編碼方式編碼,而公鑰用純字節(jié)表示。另外,兩個(gè)值都有一個(gè)額外的字節(jié),但位置并不一致:SIGHASH_ALL這個(gè)附加的哈希值常量放在簽名后面,而04這個(gè)值放在公鑰前面。
由于ECDSA算法需要使用隨機(jī)數(shù),所以調(diào)試簽名十分困難。每次計(jì)算出的簽名都會(huì)有所不同,因此無(wú)法與已知正確的簽名進(jìn)行比較。
正是由于上述的復(fù)雜性,我花了很長(zhǎng)時(shí)間才得到了一個(gè)簽名。不過(guò),最終我找出了簽名代碼中所有的錯(cuò)誤,并成功用它簽署了一筆交易。這是我使用的簽名代碼:
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í)必須執(zhí)行的腳本。需要注意的是,這個(gè)腳本將在未來(lái)花費(fèi)這些比特幣的時(shí)候執(zhí)行。它包含以十六進(jìn)制表示而不是以Base58Check表示的目標(biāo)地址1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,腳本的效果是只有這個(gè)目標(biāo)地址的私鑰所有者才能使用比特幣,因此目標(biāo)地址實(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 |
最終的交易
經(jīng)過(guò)上述的一系列操作,我們完成了最終的交易。但是,別忘了,此時(shí)的交易還沒(méi)加入?yún)^(qū)塊鏈中,接收方還沒(méi)有收到你的比特幣。
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 |
小插曲:橢圓曲線簽名
比特幣的簽名算法使用到了橢圓曲線簽名算法,這么實(shí)用的功能,你可能會(huì)好奇它是怎么做到的?在當(dāng)年英國(guó)數(shù)學(xué)家安德魯·懷爾斯攻克費(fèi)馬大定理時(shí),我第一次接觸到了橢圓曲線的算法。橢圓曲線的數(shù)學(xué)思想很有意思,所以在這里我給大家做一個(gè)快速的概述。
橢圓曲線這個(gè)叫法令人迷惑,因?yàn)闄E圓曲線并不是橢圓,而且看起來(lái)也不像橢圓,甚至橢圓曲線與橢圓相關(guān)性都很少。通俗地講,橢圓曲線就是滿足一個(gè)簡(jiǎn)單方程y ^ 2 = x ^ 3 + ax + b的曲線。比特幣中使用的稱(chēng)為secp256k1的橢圓曲線,它滿足的方程為y ^ 2 = x ^ 3 + 7。
secp256k1橢圓曲線
橢圓曲線的一個(gè)重要特性就是你可以用一個(gè)簡(jiǎn)單的規(guī)則來(lái)定義橢圓曲線上點(diǎn)的相加:如果在曲線上繪制一條直線,這條直線與曲線交與A,B,C三個(gè)點(diǎn),那么這個(gè)加法定義為A+B+C=0。由這個(gè)加法的定義,我們可以定義整數(shù)乘法:例如4A = A + A + A + A。
為什么橢圓曲線在密碼學(xué)上很有用?因?yàn)闄E圓曲線做整數(shù)乘法運(yùn)算速度很快,但做除法時(shí)需要蠻力。例如,你可以快速地計(jì)算一個(gè)乘法12345678*A = Q,但是如果你只知道A和Q,求解n*A=Q中的n十分困難。因此在橢圓曲線算法中,這里的12345678將是私鑰,曲線上的點(diǎn)Q將是公鑰。
在密碼學(xué)中,點(diǎn)的坐標(biāo)并不是它在曲線上的實(shí)值點(diǎn),而是對(duì)整數(shù)的模數(shù)。橢圓曲線的一個(gè)好用的特性就是對(duì)實(shí)數(shù)或模數(shù)進(jìn)行運(yùn)算的數(shù)學(xué)運(yùn)算幾乎相同。正因?yàn)槿绱?,比特幣的橢圓曲線并不像上面的圖片,而是一團(tuán)雜亂無(wú)章的256位點(diǎn)集(想想在一個(gè)空間中充滿了大量雜亂無(wú)章的點(diǎn))。
橢圓曲線數(shù)字簽名算法(ECDSA)接收交易的哈希值,使用該交易數(shù)據(jù),私鑰,以及一個(gè)隨機(jī)數(shù)從橢圓曲線上生成一個(gè)新的點(diǎn),從而實(shí)現(xiàn)對(duì)交易的簽名。任何擁有公鑰,交易數(shù)據(jù),和簽名的人都可以通過(guò)做一個(gè)簡(jiǎn)單的橢圓曲線運(yùn)算來(lái)驗(yàn)證簽名的有效性。讀到這里,你應(yīng)該明白了為什么只有擁有私鑰的人才能簽署消息,但擁有公鑰的任何人都可以驗(yàn)證該消息。
把交易發(fā)送到比特幣網(wǎng)絡(luò)
回到交易中來(lái),別忘了此時(shí)我們的交易還沒(méi)有被加入到區(qū)塊鏈中,還不是一筆有效交易。剛剛我創(chuàng)建并簽署了一筆交易。下一步就是將這筆交易發(fā)送到比特幣網(wǎng)絡(luò)中,網(wǎng)絡(luò)中的礦工會(huì)收集交易并把它打包進(jìn)區(qū)塊中。
如何找到比特幣網(wǎng)絡(luò)的節(jié)點(diǎn)
首先我要在比特幣的點(diǎn)對(duì)點(diǎn)網(wǎng)絡(luò)中找到一個(gè)節(jié)點(diǎn)。節(jié)點(diǎn)的列表會(huì)隨節(jié)點(diǎn)的進(jìn)出動(dòng)態(tài)更新,當(dāng)一個(gè)比特幣節(jié)點(diǎn)連接到另一個(gè)節(jié)點(diǎn)時(shí),它們就會(huì)不斷交換彼此新發(fā)現(xiàn)的比特幣節(jié)點(diǎn)信息,因此,新節(jié)點(diǎn)加入的消息會(huì)快速地傳遍整個(gè)網(wǎng)絡(luò)。
然而,新的比特幣節(jié)點(diǎn)如何第一次找到比特幣節(jié)點(diǎn)?這是一個(gè)先有雞還是先有蛋的問(wèn)題。比特幣節(jié)點(diǎn)通過(guò)以下幾種方法來(lái)解決這個(gè)問(wèn)題。有幾個(gè)可信的比特幣節(jié)點(diǎn)會(huì)以bitseed.xf2.org的域名在DNS系統(tǒng)(Domain Name System,域名系統(tǒng),萬(wàn)維網(wǎng)上作為域名和IP地址相互映射的一個(gè)分布式數(shù)據(jù)庫(kù))上注冊(cè),通過(guò)執(zhí)行nslookup命令,你就可以得到這些節(jié)點(diǎn)的IP地址,只要有一個(gè)在工作即可。如果很不幸它們都沒(méi)有工作的話,你可以試著連接那幾個(gè)已經(jīng)在你的客戶端中硬編碼記錄下來(lái)的地址。
Nslookup命令可以用來(lái)尋找比特幣節(jié)點(diǎn)
當(dāng)用戶啟動(dòng)或停止比特幣客戶端時(shí),節(jié)點(diǎn)就會(huì)加入或離開(kāi)比特幣網(wǎng)絡(luò)。所以連接節(jié)點(diǎn)有很大的不確定性,在我實(shí)驗(yàn)時(shí),就遇到了連接的節(jié)點(diǎn)已經(jīng)離開(kāi)比特幣網(wǎng)絡(luò)的情況,如果你想重復(fù)我的實(shí)驗(yàn),最好多找?guī)讉€(gè)節(jié)點(diǎn),可能需要多次嘗試才能找到一個(gè)運(yùn)行著的節(jié)點(diǎn)。
與比特幣節(jié)點(diǎn)通信
一旦獲得了一個(gè)正在工作的比特幣節(jié)點(diǎn)的IP地址,當(dāng)務(wù)之急就通過(guò)這個(gè)節(jié)點(diǎn)是把我的交易發(fā)送到比特幣的點(diǎn)對(duì)點(diǎn)網(wǎng)絡(luò)中。使用點(diǎn)對(duì)點(diǎn)的網(wǎng)絡(luò)協(xié)議十分簡(jiǎn)單,我在端口8333上打開(kāi)了一個(gè)到任意對(duì)等端的TCP連接,發(fā)送消息,然后接受反饋消息。比特幣的點(diǎn)對(duì)點(diǎn)協(xié)議對(duì)用戶很友好,即使我的請(qǐng)求數(shù)據(jù)出錯(cuò)時(shí),還是繼續(xù)與我保持通信。
重要提示:正如一些人指出的那樣,如果你想重復(fù)我的實(shí)驗(yàn),切記要使用比特幣的測(cè)試網(wǎng)絡(luò),在測(cè)試網(wǎng)絡(luò)上,你可以使用“虛擬”的比特幣來(lái)進(jìn)行交易。因?yàn)樵谡鎸?shí)網(wǎng)絡(luò)上,萬(wàn)一你不小心,有可能會(huì)失去所有的比特幣。還記得上面提到的那個(gè)100個(gè)比特幣轉(zhuǎn)賬1個(gè)的交易么,如果你忘了將剩余的比特幣轉(zhuǎn)給自己,那么剩余的99個(gè)比特幣就會(huì)作為交易費(fèi)支付給礦工。但是本著科學(xué)的態(tài)度,我并不在意在真實(shí)的比特幣網(wǎng)絡(luò)中損失我這些價(jià)值1美元的比特幣。
協(xié)議中包含24種不同的信息種類(lèi)。每一條信息都是一個(gè)簡(jiǎn)單的二進(jìn)制大對(duì)象(binary large object ,BLOB,是一個(gè)可以存儲(chǔ)二進(jìn)制文件的容器),其中包含一個(gè)ASCII命令和一個(gè)適用該命令的二進(jìn)制有效參數(shù)。該協(xié)議可以在比特幣的維基上查詢。
連接到比特幣網(wǎng)絡(luò)的第一步就是通過(guò)交換客戶端版本信息來(lái)建立連接。首先,我發(fā)送了一條客戶端版本信息,其中包含我的協(xié)議版本號(hào),IP地址和其他內(nèi)容。比特幣節(jié)點(diǎn)也向我回復(fù)了它的版本信息。在此之后,我應(yīng)該回復(fù)一個(gè)verack信息(version acknowledgement,版本確認(rèn))來(lái)確認(rèn)它的版本信息。正如我所說(shuō),比特幣點(diǎn)對(duì)點(diǎn)網(wǎng)絡(luò)協(xié)議對(duì)用戶十分友好,即使我跳過(guò)了verack信息,之后的操作也是一切正常。
交換版本信息這一步并不簡(jiǎn)單,因?yàn)樾畔⒕哂袠?biāo)準(zhǔn)的格式,不過(guò)不用害怕,可以用幾行代碼來(lái)創(chuàng)建這些信息。下面代碼段中的makeMessage函數(shù)可以由隨機(jī)數(shù),命令名以及命令的參數(shù)來(lái)生成一條消息。getVersionMessage函數(shù)通過(guò)將各個(gè)字段打包在一起來(lái)為版本消息創(chuàng)建參數(shù)。
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
發(fā)送交易tx
我使用下面精簡(jiǎn)的Python代碼把我的交易發(fā)送到比特幣網(wǎng)絡(luò)中,這個(gè)代碼發(fā)送一條客戶端版本信息,接受(也可以忽略)比特幣節(jié)點(diǎn)的版本信息和verack信息。最后將我的交易以tx信息發(fā)送。代碼中這個(gè)16進(jìn)制的字符串是我之前創(chuàng)建的交易。
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(一個(gè)抓取,分析網(wǎng)絡(luò)封包的軟件)軟件的截圖顯示出我是如何將交易發(fā)送到比特幣網(wǎng)絡(luò)中的。我用Python編寫(xiě)了腳本來(lái)分析網(wǎng)絡(luò)數(shù)據(jù),為了簡(jiǎn)單起見(jiàn),在這里我使用Wireshark。從圖中可以看到我的這筆tx交易。
Wireshark中抓取的這筆正在上傳至比特幣網(wǎng)絡(luò)的交易tx
為了實(shí)時(shí)監(jiān)控我這筆交易的進(jìn)度,我在比特幣網(wǎng)絡(luò)中新運(yùn)行了一個(gè)節(jié)點(diǎn),在把我交易發(fā)到比特幣網(wǎng)絡(luò)5秒鐘之后,另一個(gè)節(jié)點(diǎn)給我發(fā)送了這個(gè)tx消息,其中包含我剛剛發(fā)送的這筆交易的哈希,由此可見(jiàn),在僅僅這幾秒中,我的交易已經(jīng)傳遍了比特幣網(wǎng)絡(luò),至少也是比特幣網(wǎng)絡(luò)的一部分。
交易成功:我的交易被加入?yún)^(qū)塊鏈
在將我的交易發(fā)送比特幣網(wǎng)絡(luò)之后,我需要等待它被礦工開(kāi)采出來(lái)加入到區(qū)塊鏈中,然后才能宣稱(chēng)我的實(shí)驗(yàn)圓滿成功。10分鐘后,我的比特幣節(jié)點(diǎn)收到一條含有新區(qū)塊信息的inv消息(參見(jiàn)下圖Wireshark抓到的網(wǎng)絡(luò)封包),檢查這個(gè)區(qū)塊后發(fā)現(xiàn)我的交易被包含在了區(qū)塊中,證明我的交易是有效的,我的實(shí)驗(yàn)成功了。通過(guò)我的比特幣錢(qián)包軟件和在線查詢,再一次確認(rèn)了我已經(jīng)交易成功??梢哉f(shuō),經(jīng)過(guò)不斷的努力,我成功手動(dòng)創(chuàng)建了一筆交易,并讓比特幣系統(tǒng)接受了它。(當(dāng)然了,我也經(jīng)過(guò)了幾次失敗的嘗試,這些錯(cuò)誤的交易都消失在了網(wǎng)絡(luò)之中,永遠(yuǎn)都不會(huì)被檢索到。
Wireshark中抓取的新區(qū)塊產(chǎn)生的封包信息
我的交易是被當(dāng)時(shí)哈希算力(挖礦速度)最大的礦池(多個(gè)礦工一起挖礦)GHash.IO挖出,區(qū)塊高度為279068,區(qū)塊哈希為0000000000000001a27b1d6eb8c405410398ece796e742da3b3e35363c2219ee,在上圖Wireshark數(shù)據(jù)包中inv消息的哈希值是經(jīng)前后反轉(zhuǎn)得到的ee192……。你應(yīng)該會(huì)發(fā)現(xiàn)區(qū)塊的哈希值以大量的0開(kāi)頭,在一個(gè)16進(jìn)制的哈希值中發(fā)現(xiàn)一個(gè)以這么多0開(kāi)頭的數(shù),這就是為什么挖礦如此困難的原因。這個(gè)區(qū)塊中由462筆交易,我的交易是其中之一。
高度為279068的區(qū)塊以及我發(fā)起的這筆交易
(https://blockchain.info/block-index/341440/0000000000000001a27b1d6eb8c405410398ece796e742da3b3e35363c2219ee)
挖到這個(gè)區(qū)塊的礦工們收到了25個(gè)比特幣的獎(jiǎng)勵(lì),交易費(fèi)總共是0.104個(gè)比特幣,按當(dāng)時(shí)的市價(jià)分別為19000美元和80美元。我支付了0.0001個(gè)比特幣的交易費(fèi),大約是我交易額的10%,按當(dāng)時(shí)的市價(jià)為8美分。
結(jié)論
手動(dòng)進(jìn)行比特幣交易比我想象中困難得多,但是在這個(gè)過(guò)程中我學(xué)到了很多,希望你也是。我的Python代碼僅僅是為了介紹,如果你想跟我一樣用Python手動(dòng)進(jìn)行比特幣交易,也可以試試這幾個(gè)項(xiàng)目。
https://en.bitcoin.it/wiki/Bitcoin-python
https://github.com/richardkiss/pycoin
https://github.com/jgarzik/python-bitcoinlib
寫(xiě)在最后
2017年是區(qū)塊鏈的井噴之年,經(jīng)過(guò)一年的積攢,2018年將迎來(lái)區(qū)塊鏈的落地之年,區(qū)塊鏈會(huì)逐漸顛覆各行各業(yè)。對(duì)于個(gè)人,區(qū)塊鏈的機(jī)會(huì)會(huì)越來(lái)越多,也許你錯(cuò)過(guò)了比特幣的投資,不妨現(xiàn)在抓住區(qū)塊鏈這個(gè)風(fēng)口,投資自己,多學(xué)習(xí)相關(guān)知識(shí),區(qū)塊鏈大有可為,投身區(qū)塊鏈的你將大有作為!
*聲明:推送內(nèi)容及圖片來(lái)源于網(wǎng)絡(luò),部分內(nèi)容會(huì)有所改動(dòng),版權(quán)歸原作者所有,如來(lái)源信息有誤或侵犯權(quán)益,請(qǐng)聯(lián)系我們刪除或授權(quán)事宜。