亚洲精品无码久久久久sm,久久久久久伊人高潮影院,亚洲精品拍拍央视网出文,国产日产一卡2卡3卡4,久久国产精品久久喷水

一文看懂怎樣用 Python 創(chuàng)建比特幣交易

2018-04-04 15:20

 

比特幣價(jià)格的上上下下,始終撩動著每一個人無比關(guān)切的小心臟。從去年初的 800 美元左右,飛漲到去年底到 19783.21 美元最高點(diǎn),不到1年,便有將近 25 倍的升值速度。盡管眼下又掉回 8000 多美元的價(jià)格,但價(jià)格差不多能搞出去年同期一個數(shù)量級,幣圈人士“過去一年比以往 10 年掙的都多”,已經(jīng)是不爭的事實(shí)。

而對區(qū)塊鏈開發(fā)者來說,據(jù)說也已經(jīng)有拿到年新 500 萬的天價(jià)。所以“跑步進(jìn)入?yún)^(qū)塊鏈”,已經(jīng)成為不少程序員的共識。但是看過很多遠(yuǎn)離,我們?nèi)绾尾拍苎杆偕鲜帜??國外網(wǎng)友 Ken Shirriff 在博客中分享了他在手動茶古劍比特幣交易時(shí)的代碼與對比特幣協(xié)議的心得,區(qū)塊鏈大本營編譯如下。

 

近期,媒體行業(yè)對比特幣表現(xiàn)出極大的熱情,這鼓舞著我從網(wǎng)絡(luò)底層的數(shù)據(jù)流開始,認(rèn)真學(xué)習(xí)比特幣的工作原理。通常人們會使用錢包軟件來進(jìn)行比特幣交易,錢包軟件在方便用戶的同時(shí),向用戶隱藏了比特幣的交易流程,而我想親自動手來體驗(yàn)比特幣交易,我的目標(biāo)是用Python手動創(chuàng)建一筆比特幣交易,以十六進(jìn)制數(shù)據(jù)的形式將交易廣播到比特幣網(wǎng)絡(luò)中,然后觀察這筆交易是怎么被加入到區(qū)塊鏈中的。事實(shí)證明,這個過程很有趣,希望你也對它感興趣。

 

在本篇文章中,首先我會對比特幣進(jìn)行一個簡單的概述,之后,我會從以下幾個方面帶領(lǐng)你們學(xué)習(xí)比特幣:創(chuàng)建一個比特幣地址(比特幣中的賬戶),進(jìn)行一筆比特幣交易,簽署交易,將交易廣播到比特幣網(wǎng)絡(luò)中,最后等待交易的確認(rèn)。

 

比特幣簡述:

 

首先,我會介紹一下比特幣系統(tǒng)是怎么運(yùn)轉(zhuǎn)的,然后再深入探討整個細(xì)節(jié)。比特幣是一個基于點(diǎn)對點(diǎn)網(wǎng)絡(luò)的電子貨幣,你可以用現(xiàn)金在網(wǎng)上購買比特幣,用比特幣向他人轉(zhuǎn)賬,在有些商家,你可以像使用支付寶一樣使用比特幣付款,當(dāng)然,你也可以賣出所持有的比特幣換回現(xiàn)金。

 

簡而言之,在比特幣網(wǎng)絡(luò)中,分布式賬本(區(qū)塊鏈)記錄并隨時(shí)更新著每個比特幣的所有權(quán)。與銀行不同的是,比特幣并沒有與個人或個人的賬戶綁定,相反的,比特幣只屬于一個個比特幣地址,比如:1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa。這里你可能已經(jīng)繞暈了,難道這段字符中藏著比特幣?當(dāng)然不是,比特幣地址是比特幣網(wǎng)絡(luò)中的一個身份,也可以通俗地說是你在比特幣中開的一個“銀行賬戶”,我們用這個“賬戶”來進(jìn)行交易。在網(wǎng)站:blockchain.info中,你可以查到所有的交易信息:

 

?比特幣賬戶信息

 

但是怎么證明這個賬戶是我的呢,不急,先往下看,你的疑問我會為你一一解答。

 

比特幣交易

 

如何像使用現(xiàn)金一樣使用比特幣呢?答案是創(chuàng)建一筆交易。在一筆交易中,比特幣的所有者(上文提到過比特幣的所有者是比特幣地址)將所有權(quán)轉(zhuǎn)移到一個新的比特幣地址。比特幣的一個顛覆性創(chuàng)新就是通過鼓勵節(jié)點(diǎn)記賬(也叫礦工挖礦),將交易記錄放在一個分布式的數(shù)據(jù)庫中。交易被集合在區(qū)塊中,大概每十分鐘比特幣網(wǎng)絡(luò)中產(chǎn)生一個新的區(qū)塊,成為交易記錄的一部分,稱為區(qū)塊鏈。加入到區(qū)塊鏈中的交易可以被認(rèn)為是一筆成功的交易?,F(xiàn)在問題來了,誰來給你記賬呢?是礦工,礦工的挖礦過程就是在往區(qū)塊鏈中記賬,礦工要核實(shí)每筆交易是否正確,核實(shí)完后,礦工們就開始算一道很難的數(shù)學(xué)題(密碼學(xué)中的哈希函數(shù)),最早算出答案的人就能生成一個區(qū)塊,也叫挖出了一個新的區(qū)塊,這個區(qū)塊將成為區(qū)塊鏈的新一部分。

 

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

 

點(diǎn)對點(diǎn)網(wǎng)絡(luò)

 

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

 

加密技術(shù)

 

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

 

怎么來區(qū)分不同的交易呢?交易和區(qū)塊都使用密碼學(xué)上的哈希值進(jìn)行索引,是不是有點(diǎn)耳熟,對,在比特幣協(xié)議中,多處使用到了哈希函數(shù),礦工們剛才算的數(shù)學(xué)題就是在算哈希函數(shù)。

 

比特幣協(xié)議探究

 

在接下來的文章里,我將逐步介紹我是怎樣手動進(jìn)行一次比特幣交易的。首先,我生成了一個比特幣賬戶以及對應(yīng)的公鑰,私鑰。接下來我發(fā)起了一筆比特幣交易,我向這個新生成的賬戶轉(zhuǎn)了一小筆比特幣。期間手動簽署這筆交易很困難,它花費(fèi)了我很多的時(shí)間。最后,我將這筆交易發(fā)送到比特幣網(wǎng)絡(luò),等待它被加入?yún)^(qū)塊鏈。本文的其余部分會詳細(xì)地介紹這些步驟。

 

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

 

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

 

最后,手動簽署交易的過程也比想象中難得多,簽署交易時(shí)每個環(huán)節(jié)都必須零失誤,要么又要退回重來。

 

比特幣地址和密鑰

 

第一步,我創(chuàng)建了一個比特幣地址。通常情況下,人們都是使用比特幣客戶端軟件來創(chuàng)建比特幣地址和與之相關(guān)的密鑰。本著學(xué)習(xí)的態(tài)度,我寫了一些Python代碼來生成比特幣地址,從而揭示地址創(chuàng)建的機(jī)理。

 

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

 

橢圓曲線數(shù)字簽名算法(Elliptic Curve Digital Signature Algorithm,ECDSA,美國政府的標(biāo)準(zhǔn),接下來我們會討論它)會從私鑰中生成一個512位的公鑰,這個公鑰用于驗(yàn)證交易的簽名。但不方便的是,比特幣協(xié)議中需要在這個公鑰上添加了前綴04,這個公鑰在交易簽署之前不會被泄露,不像其它系統(tǒng)中公鑰就是為了公之于眾的。

 

比特幣地址與公鑰的關(guān)系

 

下一步就是生成與他人交易時(shí)使用的比特幣地址了。512位的公鑰太長不方便使用,因此使用SHA-256和RIPEMD哈希算法將其縮小為160位。然后使用比特幣定義的Base58Check 編碼將密鑰編碼為ASCII(American Standard Code for Information Interchange,美國信息交換標(biāo)準(zhǔn)代碼)格式。得到的地址(例如上文中的:1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa)就是你接收別人比特幣時(shí)要發(fā)布的地址。需要注意的是,你無法從比特幣地址中復(fù)原出公鑰或私鑰。如果你丟失了你的私鑰(比如說你把私鑰存在你的硬盤上,但硬盤丟失),你的比特幣將永遠(yuǎn)丟失。

 

最后,錢包交換格式密鑰(WIF)用于將私鑰添加到你的錢包軟件中,這只是將私鑰進(jìn)行Base58Check編碼轉(zhuǎn)換為ASCII格式,這一步是可逆的,而且很容易經(jīng)過逆變換恢復(fù)出256位的私鑰。(圖中有我的私鑰,我很好奇是否有人會用我的私鑰去偷(通過私鑰簽署交易,從而轉(zhuǎn)走)我那價(jià)值80美分的比特幣,然而真有人那么做了,可以在

https://blockchain.info/address/1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa 看到,也算是為教學(xué)做貢獻(xiàn)了。)

 

總之,共有三種密鑰:私鑰,公鑰,公鑰的哈希值,經(jīng)過使用Base58Check編碼,它們對外都是以ASCII格式表示。私鑰是其中最重要的密鑰,因?yàn)榛ㄙM(fèi)比特幣時(shí)需要私鑰簽署交易,而且其他的密鑰都可以從私鑰中產(chǎn)生。公鑰的哈希值就是你們剛看的的比特幣地址。

 

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

 

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)的基本操作,也許你會認(rèn)為交易就是簡單地把比特幣從一個地址轉(zhuǎn)移到另一個地址,但交易其實(shí)并不簡單。一筆交易包含一個或多個輸入和輸出,交易中的每個輸入的地址都提供比特幣,每個輸出的地址都接受比特幣。

 

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

 

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

 

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

 

通常交易要支付交易費(fèi),如果一筆交易中輸入的比特幣總和大于輸出的比特幣的總和,剩余的費(fèi)用就是給礦工的交易費(fèi)。這筆費(fèi)用并沒有明確要求,但是對于礦工而言,沒有交易費(fèi)的交易就會被列為低優(yōu)先級交易,可能要等上幾天才會被處理甚至被礦工直接丟棄。交易費(fèi)通常并不高,但它可能影響著你的交易。

 

手動創(chuàng)建一筆交易

 

如下圖所示,在我的實(shí)驗(yàn)中我發(fā)起了一筆只有一個輸入一個輸出的交易。我在Coinbase上買了一些比特幣,并將0.00101234個比特幣放入地址:

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

81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48,我的目標(biāo)是創(chuàng)建一筆交易,將這些比特幣轉(zhuǎn)入我的另一個地址:

1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,扣除0.0001個比特幣的交易費(fèi)后,目標(biāo)地址將獲得0.00091234個比特幣。

比特幣交易結(jié)構(gòu)實(shí)例

 

? Blockchain.info上的交易記錄

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

 

按照協(xié)議標(biāo)準(zhǔn),創(chuàng)建這筆交易很簡單。如下表所示,這筆交易只有一個輸入,源自于81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48中的輸出0(第一個輸出)。輸出為0.00091234個比特幣(91234在十六進(jìn)制中用0x016462表示),它以小端格式存儲在值區(qū)域中。加密過程中的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)制文件。簽署交易較為困難,我們等一會兒再說。

 


 

# 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轉(zhuǎn)賬到比特幣地址C的交易。交易的內(nèi)容(包括前一個交易的哈希值(索引))被進(jìn)行哈希計(jì)算并用B的私鑰簽名。另外,B的公鑰也被包含在了交易中。 

 

通過執(zhí)行幾個簡單運(yùn)算,任何人都能驗(yàn)證B是否簽署了這筆交易。首先,B的公鑰與之前收到這筆比特幣交易的地址做驗(yàn)證,證明B的公鑰有效。(正如前面所說的,地址很容易從公鑰中計(jì)算獲得)。接下來,可以通過B的公鑰驗(yàn)證B交易簽名的真?zhèn)?。這些步驟能確保交易的有效性和交易得到B的授權(quán)。比特幣于眾不同的一點(diǎn)是,B的公鑰在B發(fā)起交易之前是不公開的。 

 

在比特幣系統(tǒng)中,比特幣通過區(qū)塊鏈上的一筆筆交易在不同的地址間傳遞。區(qū)塊鏈上的每一筆交易都能被驗(yàn)證以確保比特幣交易的有效性。

 

比特幣腳本語言

 

你可能會以為僅僅通過在交易內(nèi)容中附上簽名就可以簽署比特幣交易,其實(shí)不然,這個過程十分復(fù)雜。實(shí)際上,每一筆交易中都包含一個“小程序”,用于確認(rèn)交易是否有效。這個“小程序”用腳本語言寫成,通過這種基于堆棧的比特幣腳本語言,我們可以應(yīng)對許多復(fù)雜的比特幣支付場景。例如,托管系統(tǒng)可以設(shè)定只要經(jīng)過三分之二的用戶授權(quán),就可執(zhí)行交易的規(guī)則,也可以設(shè)置其他的合約。 

 

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

 

前一個交易中的腳本稱為scriptPubKey,當(dāng)前交易中的腳本稱為scriptSig。要驗(yàn)證交易時(shí),先執(zhí)行scriptSig,然后再執(zhí)行scriptPubKey。如果兩個腳本都成功執(zhí)行,交易就被認(rèn)定為有效,交易中的比特幣就可以成功花出。否則,交易無效。要注意的是前一個交易中的scriptPubKey規(guī)定了花費(fèi)比特幣的條件,當(dāng)前交易的scriptSig必須滿足這個條件。 

 

在一個標(biāo)準(zhǔn)的交易中,scriptSig腳本將從私鑰中生成的簽名并壓入堆棧中,再壓入公鑰。接下來scriptPubKey腳本會執(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操作首先會把簽名壓入堆棧,接著把公鑰壓入堆棧。OPHASH-160操作計(jì)算公鑰的160位哈希值,PUSHDATA操作再把交易中的輸入地址(輸入賬號)壓入堆棧,然后,OP-EQUALVERIFY操作驗(yàn)證驗(yàn)證前兩個堆棧中的值是否相等(驗(yàn)證這筆交易中你使用的比特幣是否屬于你自己)-如果公鑰的哈希等于之前交易中的輸出地址,這就證明公鑰是有效的(證明這個比特幣是你的)。最后,OP_CHECKSIG操作將檢查交易的簽名是否與堆棧里的公鑰和簽名匹配,匹配就證明簽名是有效的(證明交易的到了你的授權(quán))

 

簽署交易

 

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

 

 

簽署交易的19個步驟

 

對交易的簽名讓我面臨巨大的挑戰(zhàn),這涉及到一個如何在交易內(nèi)容中還沒有加入簽名時(shí)簽署這筆交易的問題。為了避免這個問題,在計(jì)算生成簽名之前,我把scriptPubKey這個腳本從上一筆交易復(fù)制到當(dāng)前交易中(當(dāng)前這筆交易正在被簽署),然后將簽名轉(zhuǎn)換為腳本語言的代碼,創(chuàng)建嵌入在當(dāng)前交易中的scriptSig腳本。對于具有多個輸入的交易,簽署交易環(huán)節(jié)更加復(fù)雜,因?yàn)槊總€輸入都需要單獨(dú)的簽名,這里我就不做詳細(xì)討論了。

 

哈希值這一步驟難倒了我。在簽名之前,交易中有一個臨時(shí)附加的哈希值常量。對于常規(guī)的交易,這個值是SIGHASH_ALL(0x00000001)。簽名后,這個哈希值將從交易內(nèi)容的最后刪除,附加到scriptSig腳本中。

 

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

 

由于ECDSA算法需要使用隨機(jī)數(shù),所以調(diào)試簽名十分困難。每次計(jì)算出的簽名都會有所不同,因此無法與已知正確的簽名進(jìn)行比較。

 

正是由于上述的復(fù)雜性,我花了很長時(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í)必須執(zhí)行的腳本。需要注意的是,這個腳本將在未來花費(fèi)這些比特幣的時(shí)候執(zhí)行。它包含以十六進(jìn)制表示而不是以Base58Check表示的目標(biāo)地址1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,腳本的效果是只有這個目標(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)過上述的一系列操作,我們完成了最終的交易。但是,別忘了,此時(shí)的交易還沒加入?yún)^(qū)塊鏈中,接收方還沒有收到你的比特幣。

 

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í)用的功能,你可能會好奇它是怎么做到的?在當(dāng)年英國數(shù)學(xué)家安德魯·懷爾斯攻克費(fèi)馬大定理時(shí),我第一次接觸到了橢圓曲線的算法。橢圓曲線的數(shù)學(xué)思想很有意思,所以在這里我給大家做一個快速的概述。

 

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

 

 

secp256k1橢圓曲線

 

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

 

為什么橢圓曲線在密碼學(xué)上很有用?因?yàn)闄E圓曲線做整數(shù)乘法運(yùn)算速度很快,但做除法時(shí)需要蠻力。例如,你可以快速地計(jì)算一個乘法12345678*A = Q,但是如果你只知道A和Q,求解n*A=Q中的n十分困難。因此在橢圓曲線算法中,這里的12345678將是私鑰,曲線上的點(diǎn)Q將是公鑰。

 

在密碼學(xué)中,點(diǎn)的坐標(biāo)并不是它在曲線上的實(shí)值點(diǎn),而是對整數(shù)的模數(shù)。橢圓曲線的一個好用的特性就是對實(shí)數(shù)或模數(shù)進(jìn)行運(yùn)算的數(shù)學(xué)運(yùn)算幾乎相同。正因?yàn)槿绱?,比特幣的橢圓曲線并不像上面的圖片,而是一團(tuán)雜亂無章的256位點(diǎn)集(想想在一個空間中充滿了大量雜亂無章的點(diǎn))。

 

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

 

把交易發(fā)送到比特幣網(wǎng)絡(luò)

 

回到交易中來,別忘了此時(shí)我們的交易還沒有被加入到區(qū)塊鏈中,還不是一筆有效交易。剛剛我創(chuàng)建并簽署了一筆交易。下一步就是將這筆交易發(fā)送到比特幣網(wǎng)絡(luò)中,網(wǎng)絡(luò)中的礦工會收集交易并把它打包進(jìn)區(qū)塊中。

 

如何找到比特幣網(wǎng)絡(luò)的節(jié)點(diǎn)

 

首先我要在比特幣的點(diǎn)對點(diǎn)網(wǎng)絡(luò)中找到一個節(jié)點(diǎn)。節(jié)點(diǎn)的列表會隨節(jié)點(diǎn)的進(jìn)出動態(tài)更新,當(dāng)一個比特幣節(jié)點(diǎn)連接到另一個節(jié)點(diǎn)時(shí),它們就會不斷交換彼此新發(fā)現(xiàn)的比特幣節(jié)點(diǎn)信息,因此,新節(jié)點(diǎn)加入的消息會快速地傳遍整個網(wǎng)絡(luò)。

 

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

 

 

Nslookup命令可以用來尋找比特幣節(jié)點(diǎn)

 

當(dāng)用戶啟動或停止比特幣客戶端時(shí),節(jié)點(diǎn)就會加入或離開比特幣網(wǎng)絡(luò)。所以連接節(jié)點(diǎn)有很大的不確定性,在我實(shí)驗(yàn)時(shí),就遇到了連接的節(jié)點(diǎn)已經(jīng)離開比特幣網(wǎng)絡(luò)的情況,如果你想重復(fù)我的實(shí)驗(yàn),最好多找?guī)讉€節(jié)點(diǎn),可能需要多次嘗試才能找到一個運(yùn)行著的節(jié)點(diǎn)。

 

與比特幣節(jié)點(diǎn)通信

 

一旦獲得了一個正在工作的比特幣節(jié)點(diǎn)的IP地址,當(dāng)務(wù)之急就通過這個節(jié)點(diǎn)是把我的交易發(fā)送到比特幣的點(diǎn)對點(diǎn)網(wǎng)絡(luò)中。使用點(diǎn)對點(diǎn)的網(wǎng)絡(luò)協(xié)議十分簡單,我在端口8333上打開了一個到任意對等端的TCP連接,發(fā)送消息,然后接受反饋消息。比特幣的點(diǎn)對點(diǎn)協(xié)議對用戶很友好,即使我的請求數(shù)據(jù)出錯時(shí),還是繼續(xù)與我保持通信。 

 

重要提示:正如一些人指出的那樣,如果你想重復(fù)我的實(shí)驗(yàn),切記要使用比特幣的測試網(wǎng)絡(luò),在測試網(wǎng)絡(luò)上,你可以使用“虛擬”的比特幣來進(jìn)行交易。因?yàn)樵谡鎸?shí)網(wǎng)絡(luò)上,萬一你不小心,有可能會失去所有的比特幣。還記得上面提到的那個100個比特幣轉(zhuǎn)賬1個的交易么,如果你忘了將剩余的比特幣轉(zhuǎn)給自己,那么剩余的99個比特幣就會作為交易費(fèi)支付給礦工。但是本著科學(xué)的態(tài)度,我并不在意在真實(shí)的比特幣網(wǎng)絡(luò)中損失我這些價(jià)值1美元的比特幣。

 

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

 

連接到比特幣網(wǎng)絡(luò)的第一步就是通過交換客戶端版本信息來建立連接。首先,我發(fā)送了一條客戶端版本信息,其中包含我的協(xié)議版本號,IP地址和其他內(nèi)容。比特幣節(jié)點(diǎn)也向我回復(fù)了它的版本信息。在此之后,我應(yīng)該回復(fù)一個verack信息(version acknowledgement,版本確認(rèn))來確認(rèn)它的版本信息。正如我所說,比特幣點(diǎn)對點(diǎn)網(wǎng)絡(luò)協(xié)議對用戶十分友好,即使我跳過了verack信息,之后的操作也是一切正常。 

 

交換版本信息這一步并不簡單,因?yàn)樾畔⒕哂袠?biāo)準(zhǔn)的格式,不過不用害怕,可以用幾行代碼來創(chuàng)建這些信息。下面代碼段中的makeMessage函數(shù)可以由隨機(jī)數(shù),命令名以及命令的參數(shù)來生成一條消息。getVersionMessage函數(shù)通過將各個字段打包在一起來為版本消息創(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

 

我使用下面精簡的Python代碼把我的交易發(fā)送到比特幣網(wǎng)絡(luò)中,這個代碼發(fā)送一條客戶端版本信息,接受(也可以忽略)比特幣節(jié)點(diǎn)的版本信息和verack信息。最后將我的交易以tx信息發(fā)送。代碼中這個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(一個抓取,分析網(wǎng)絡(luò)封包的軟件)軟件的截圖顯示出我是如何將交易發(fā)送到比特幣網(wǎng)絡(luò)中的。我用Python編寫了腳本來分析網(wǎng)絡(luò)數(shù)據(jù),為了簡單起見,在這里我使用Wireshark。從圖中可以看到我的這筆tx交易。

 

 

 

Wireshark中抓取的這筆正在上傳至比特幣網(wǎng)絡(luò)的交易tx

 

為了實(shí)時(shí)監(jiān)控我這筆交易的進(jìn)度,我在比特幣網(wǎng)絡(luò)中新運(yùn)行了一個節(jié)點(diǎn),在把我交易發(fā)到比特幣網(wǎng)絡(luò)5秒鐘之后,另一個節(jié)點(diǎn)給我發(fā)送了這個tx消息,其中包含我剛剛發(fā)送的這筆交易的哈希,由此可見,在僅僅這幾秒中,我的交易已經(jīng)傳遍了比特幣網(wǎng)絡(luò),至少也是比特幣網(wǎng)絡(luò)的一部分。

 

交易成功:我的交易被加入?yún)^(qū)塊鏈

 

在將我的交易發(fā)送比特幣網(wǎng)絡(luò)之后,我需要等待它被礦工開采出來加入到區(qū)塊鏈中,然后才能宣稱我的實(shí)驗(yàn)圓滿成功。10分鐘后,我的比特幣節(jié)點(diǎn)收到一條含有新區(qū)塊信息的inv消息(參見下圖Wireshark抓到的網(wǎng)絡(luò)封包),檢查這個區(qū)塊后發(fā)現(xiàn)我的交易被包含在了區(qū)塊中,證明我的交易是有效的,我的實(shí)驗(yàn)成功了。通過我的比特幣錢包軟件和在線查詢,再一次確認(rèn)了我已經(jīng)交易成功??梢哉f,經(jīng)過不斷的努力,我成功手動創(chuàng)建了一筆交易,并讓比特幣系統(tǒng)接受了它。(當(dāng)然了,我也經(jīng)過了幾次失敗的嘗試,這些錯誤的交易都消失在了網(wǎng)絡(luò)之中,永遠(yuǎn)都不會被檢索到。

 

Wireshark中抓取的新區(qū)塊產(chǎn)生的封包信息

 

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

高度為279068的區(qū)塊以及我發(fā)起的這筆交易

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

 

挖到這個區(qū)塊的礦工們收到了25個比特幣的獎勵,交易費(fèi)總共是0.104個比特幣,按當(dāng)時(shí)的市價(jià)分別為19000美元和80美元。我支付了0.0001個比特幣的交易費(fèi),大約是我交易額的10%,按當(dāng)時(shí)的市價(jià)為8美分。

 

結(jié)論

 

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

 

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

https://github.com/richardkiss/pycoin

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

 

 

寫在最后

2017年是區(qū)塊鏈的井噴之年,經(jīng)過一年的積攢,2018年將迎來區(qū)塊鏈的落地之年,區(qū)塊鏈會逐漸顛覆各行各業(yè)。對于個人,區(qū)塊鏈的機(jī)會會越來越多,也許你錯過了比特幣的投資,不妨現(xiàn)在抓住區(qū)塊鏈這個風(fēng)口,投資自己,多學(xué)習(xí)相關(guān)知識,區(qū)塊鏈大有可為,投身區(qū)塊鏈的你將大有作為!

*聲明:推送內(nèi)容及圖片來源于網(wǎng)絡(luò),部分內(nèi)容會有所改動,版權(quán)歸原作者所有,如來源信息有誤或侵犯權(quán)益,請聯(lián)系我們刪除或授權(quán)事宜。