1. 程式人生 > >如何使用原始比特幣協議bitcoin protocol

如何使用原始比特幣協議bitcoin protocol

媒體對比特幣的關注讓我開始瞭解比特幣的真正運作方式,直至流經網路的位元組數。普通人使用隱藏真實情況的軟體,但我想親自了解比特幣協議。我的目標是直接使用比特幣系統:手動建立比特幣交易,將其作為十六進位制資料提供給系統,並檢視它是如何處理的。事實證明這比我預期的要困難得多,但我在這個過程中學到了很多東西,希望你會發現它很有趣。

本篇博文首先簡要介紹比特幣,然後跳轉到低階細節:建立比特幣地址,進行交易,簽署交易,將交易提供給對等網路,並觀察結果。

比特幣的快速概述

在深入研究細節之前,我將首先快速概述比特幣的工作原理。比特幣是一種相對較新的數字貨幣,可以通過網際網路傳輸。你可以用Coinbase或MtGox等網站上的美元或其他傳統資金購買比特幣,將比特幣傳送給其他人,在某些地方用它們買東西,然後將比特幣兌換成美元。為了略微簡化,比特幣由分散式資料庫中的條目組成,該資料庫跟蹤比特幣的所有權。與銀行不同,比特幣與使用者或賬戶無關。相反,比特幣由比特幣地址擁有,例如1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa

比特幣交易

交易是消費比特幣的機制。在交易中,某些比特幣的所有者將所有權轉移到新地址。比特幣的一個關鍵創新是如何通過挖掘在分散式資料庫中記錄交易。交易被分組為塊,大約每10分鐘傳送一個新的交易塊,成為交易日誌的一部分,稱為區塊鏈,表示交易已經(或多或少)正式進行。比特幣挖掘是將交易放入塊中的過程,以確保每個人都具有一致的交易日誌檢視。為了挖掘區塊,礦工們必須找到一種極其罕見的解決方案來解決(否則無意義的)加密問題。找到此解決方案會生成一個已開採的塊,該塊將成為官方區塊鏈的一部分。

挖掘也是比特幣進入系統的新機制。當塊成功挖掘時,塊中會生成新的比特幣並支付給礦工。這個採礦獎金很大——目前每塊25比特幣(約19,000美元)。此外,礦工獲得與區塊中的交易相關的任何費用。因此,採礦與許多試圖開採礦塊的人競爭非常激烈。採礦的難度和競爭力是比特幣安全的關鍵部分,因為它確保沒有人可以用壞塊淹沒系統。

點對點網路

沒有集中的比特幣伺服器。相反,比特幣在點對點網路上執行。如果你執行比特幣客戶端,你將成為該網路的一部分。網路上的節點彼此交換其他對等體的交易,塊和地址。首次連線到網路時,客戶端會從某個隨機節點或節點下載區塊鏈。反過來,你的客戶端可能會向其他節點提供資料。當你建立比特幣交易時,你將其傳送給某個對等方,該對等方將其傳送給其他對等方,依此類推,直到它到達整個網路。礦工獲取你的交易,生成包含你的交易的挖掘區塊,並將此挖掘的區塊傳送給對等方。最終,你的客戶端將收到該塊,你的客戶端將顯示該交易已處理完畢。

加密

比特幣使用數字簽名來確保只有比特幣的所有者可以使用它們。 比特幣地址的所有者具有與該地址相關聯的私鑰。 為了花費比特幣,他們用這個私鑰簽署交易,證明他們是所有者。 (這有點像簽署物理檢查以使其有效。)公鑰與每個比特幣地址相關聯,任何人都可以使用它來驗證數字簽名。
塊和交易由其內容的256位加密雜湊標識。 此雜湊值用於比特幣協議中的多個位置。 此外,查詢特殊雜湊是挖掘塊的難題。

深入原始比特幣協議

本文的其餘部分將逐步討論我如何使用原始比特幣協議。首先,我生成了比特幣地址和金鑰接下來,我做了一筆交易,將少量的比特幣轉移到這個地址。簽署此交易給我帶來了很多時間和困難。最後,我將這筆交易送入比特幣點對點網路並等待它開採。本文的其餘部分將詳細介紹這些步驟。事實證明,實際使用比特幣協議比我預期的更難。正如你將看到的,該協議有點混亂:它使用大尾數字,小尾數數字,固定長度數字,可變長度數字,自定義編碼,DER編碼和各種加密演算法,看似隨意。因此,將資料轉換為正確的格式會有很多煩人的操作。

直接使用協議的第二個複雜因素是加密,這是非常不可原諒的。如果你得到一個位元組錯誤,則會拒絕該交易,而不知道問題出在何處。

我遇到的最後一個困難是簽署交易的過程比必要的困難得多,需要糾正很多細節。特別是,簽名的交易版本與實際使用的版本非常不同。

比特幣地址和金鑰

我的第一步是建立一個比特幣地址。通常,你使用比特幣客戶端軟體來建立地址和相關金鑰。但是,我寫了一些Python程式碼來建立地址,準確顯示幕後發生的事情。比特幣使用各種鍵和地址,因此下圖可能有助於解釋它們。首先建立一個隨機的256位私鑰。需要私鑰來簽署交易,從而轉移(支出)比特幣。因此,私鑰必須保密,否則你的比特幣可能被盜。

Elliptic Curve DSA演算法從私鑰生成512位公鑰。(橢圓曲線加密將在後面討論。)此公鑰用於驗證交易上的簽名。不方便的是,比特幣協議為公鑰添加了字首04。在簽署交易之前不會公開公鑰,這與大多數公鑰公開的系統不同。

比特幣金鑰和地址的關係如何

下一步是生成與其他人共享的比特幣地址。由於512位公鑰不方便大,因此使用SHA-256和RIPEMD雜湊演算法將其分解為160位。然後使用比特幣的自定義Base58Check編碼以ASCII編碼金鑰。結果地址,例如1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,是人們為了接收比特幣而釋出的地址。請注意,你無法從該地址確定公鑰或私鑰。如果你丟失了私鑰(例如丟棄了硬碟),你的比特幣將永遠丟失。

最後,電子錢包交換格式金鑰(WIF)用於向客戶端錢包軟體新增私鑰。這只是將私鑰的Base58Check編碼轉換為ASCII,這很容易被反轉以獲得256位私鑰。(我很好奇是否有人會使用上面的私鑰來竊取我的80美分的比特幣,當然有人這樣做了。)

總而言之,有三種類型的金鑰:私鑰,公鑰和公鑰的hash,它們使用Base58Check編碼在ASCII外部表示。私鑰是重要的金鑰,因為它需要訪問比特幣,而其他金鑰可以從中生成。公鑰雜湊是你看到的比特幣地址。

我使用以下程式碼片段生成WIF格式的私鑰和地址。私鑰只是一個隨機的256位數字。ECDSA加密庫從私鑰生成公鑰。比特幣地址由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)

在交易中

交易是比特幣系統的基本操作。你可能希望交易只是將一些比特幣從一個地址移動到另一個地址,但它比這更復雜。比特幣交易在一個或多個輸入和輸出之間移動比特幣。 每個輸入都是提供比特幣的交易和地址。每個輸出都是接收比特幣的地址,以及到達該地址的比特幣數量。

一個比特幣交易樣本。交易C從交易A和B中花費.008比特幣

上圖顯示了一個示例交易“C”。在此交易中,0.005BTC取自交易A中的地址,而0.003BTC取自交易B中的地址。請注意,箭頭是對先前輸出的引用,因此向後轉到比特幣流。)對於輸出,0.003BTC指向第一個地址,0.004BTC指向第二個地址。剩餘的0.001BTC作為費用給到該區塊的礦工。請注意,交易A的其他輸出中的0.015 BTC不會用於此交易。

使用的每個輸入必須完全花在交易中。如果一個地址在一個交易中收到了100個比特幣而你只想花1個比特幣,那麼交易必須花費所有100個。解決方案是使用第二個輸出進行更改,這會將99個剩餘比特幣返回給你。

交易還可以包括費用。如果在將輸入相加並減去輸出後仍有任何比特幣剩餘,則餘額是支付給礦工的費用。該費用並非嚴格要求,但免費交易對礦工來說不是優先考慮的事項,可能幾天不會處理,也可能完全丟棄。交易的典型費用是0.0002比特幣(約20美分),因此費用很低但不是微不足道的。

手動建立交易

對於我的實驗,我使用了一個帶有一個輸入和一個輸出的簡單交易,如下所示。我開始使用Coinbase的比特幣並將0.00101234比特幣放入地址1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5,這是交易81b4c832...我的目標是建立一個交易,將這些比特幣轉移到我上面建立的地址,1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,減去0.0001比特幣的費用。因此,目標地址將接收0.00091234比特幣。

比特幣交易示例的結構

遵循規範,可以非常容易地組裝無符號交易,如下所示。有一個輸入,它使用來自交易81b4c832...輸出0(第一個輸出)81b4c832...請注意,此交易雜湊在交易中不方便地反轉。輸出量為0.00091234比特幣(91234為十六進位制的0x016462),以小尾數格式儲存在值欄位中。加密部分———criptSigscriptPubKey更復雜,稍後將對其進行討論。

這是我用來生成這個無符號交易的程式碼。這只是將資料打包成二進位制的問題。簽署交易是困難的部分,你將在下面看到。

# 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
        )

比特幣交易如何簽署

下圖給出瞭如何簽署和連結交易的簡化檢視。考慮中間交易,將比特幣從地址B轉移到地址C.交易的內容(包括先前交易的雜湊)被雜湊並用B的私鑰簽名。此外,B的公鑰包含在交易中。

通過執行幾個步驟,任何人都可以驗證交易是否被B授權。首先,B的公鑰必須與前一個交易中的B地址相對應,證明公鑰是有效的。(如前所述,地址可以很容易地從公鑰中匯出。)接下來,可以使用交易中B的公鑰來驗證B的交易簽名。這些步驟確保交易有效並由B授權。比特幣的一個意外部分是B的公鑰在交易中使用之前不會公開。

使用這個系統,比特幣通過一系列交易從一個地址傳遞到另一個地址。可以驗證鏈中的每個步驟以確保有效地使用比特幣。請注意,交易通常可以有多個輸入和輸出,因此鏈分支到樹中。

比特幣交易如何連結在一起

比特幣指令碼語言

你可能希望僅通過在交易中包含簽名來簽署比特幣交易,但該過程要複雜得多。事實上,每個交易中都有一個小程式可以執行以確定交易是否有效。該程式是用Script編寫的,這是一種基於堆疊的比特幣指令碼語言。複雜的贖回條件可以用這種語言表達。例如,託管系統可能需要三個特定使用者中的兩個必須簽署交易才能使用它。或者可以設定各種型別的合約。

Script語言非常複雜,有大約80種不同的操作碼。它包括算術運算,按位運算,字串運算,條件運算和堆疊操作。該語言還包括必要的加密操作(SHA-256,RIPEMD等)作為基元。為了確保指令碼終止,該語言不包含任何迴圈操作。(因此,它不是Turing-complete。)但實際上,只支援幾種型別的交易。

為了使比特幣交易有效,兌換指令碼的兩個部分必須成功執行。舊交易中的指令碼稱為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首先將簽名推送到堆疊。下一個PUSHDATA將公鑰推送到堆疊。接下來,OP_DUP複製堆疊上的公鑰。OP_HASH160計算公鑰的160位雜湊值。PUSHDATA推送所需的比特幣地址。然後OP_EQUALVERIFY驗證前兩個堆疊值是否相等。來自新交易的公鑰雜湊與舊地址中的地址匹配。這證明公鑰是有效的。接下來,OP_CHECKSIG檢查交易的簽名是否與堆疊上的公鑰和簽名匹配。這證明簽名是有效的。

簽署交易

我發現簽署交易是手動使用比特幣最困難的部分,其過程非常困難且容易出錯。基本思想是使用ECDSA橢圓曲線演算法和私鑰生成交易的數字簽名,但細節很棘手。簽名過程已通過19個步驟(更多資訊)進行了描述。單擊下面的縮圖以獲取該過程的詳細圖表。

最大的複雜因素是簽名出現在交易中間,這就提出了在簽名之前如何簽署交易的問題。為避免此問題,在計算簽名之前,將scriptPubKey指令碼從源交易複製到支出交易(即正在簽名的交易)中。然後將簽名轉換為指令碼語言中的程式碼,建立嵌入在交易中的scriptSig指令碼。似乎在簽名期間使用先前交易的scriptPubKey是出於歷史原因而不是任何邏輯原因。對於具有多個輸入的交易,簽名甚至更復雜,因為每個輸入都需要單獨的簽名,但我不會詳細介紹。

絆倒我的一步是雜湊型別。在簽名之前,交易具有臨時附加的雜湊型別常量。對於常規交易,這是SIGHASH_ALL(0x00000001)。簽名後,此雜湊型別將從交易結束時刪除並附加到scriptSig

關於比特幣協議的另一個令人討厭的事情是簽名和公鑰都是512位橢圓曲線值,但它們以完全不同的方式表示:簽名用DER編碼編碼,但公鑰表示為普通位元組。此外,兩個值都有一個額外的位元組,但位置不一致:簽名後放置SIGHASH_ALL,型別04放在公鑰之前。

由於ECDSA演算法使用隨機數,因此除錯簽名變得更加困難。因此,每次計算時簽名都不同,因此無法與已知良好的簽名進行比較。

更新(2014年2月):每次簽名更改的一個重要副作用是,如果重新簽名交易,交易的雜湊值將會更改。這稱為交易可維護性。還有一些方法可以讓第三方以微不足道的方式修改交易,從而改變雜湊,而不是交易的意義。儘管多年來人們已經知道,但是可塑性最近在MtGox(新聞稿)中引起了很大的問題(2014年2月)。

由於這些複雜情況,我花了很長時間才能使簽名工作。但最終,我從簽名程式碼中獲得了所有錯誤,併成功簽署了一項交易。這是我使用的程式碼片段。

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 signed_txn

最終的scriptSig包含簽名以及源地址的公鑰(1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5)。這證明我可以使用這些比特幣,使交易有效。

最終的scriptPubKey包含必須成功使用比特幣的指令碼。請注意,在使用比特幣時,此指令碼將在以後的某個任意時間執行。它包含以十六進位制表示的目標地址(1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa),而不是Base58Check。結果是隻有該地址的私鑰的所有者可以使用比特幣,因此該地址實際上是所有者。

最後的交易

一旦完成所有必要的方法,就可以組裝最終的交易。

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

最終交易如下所示。這將上面的scriptSig和scriptPubKey與前面描述的unsigned交易相結合。

切線:理解橢圓曲線

比特幣使用橢圓曲線作為簽名演算法的一部分。在解決費馬最後定理的背景下,我之前聽說過橢圓曲線,所以我很好奇它們是什麼。橢圓曲線的數學很有意思,所以我會繞道而行,快速概述一下。名稱橢圓曲線令人困惑:橢圓曲線不是橢圓形,看起來不像橢圓,它們與橢圓幾乎沒有關係。橢圓曲線是滿足相當簡單的方程y2=x3+ax+b的曲線。比特幣使用稱為secp256k1的特定橢圓曲線,其簡單方程為y2=x3+7。

比特幣使用的橢圓曲線公式

橢圓曲線的一個重要特性是你可以使用一個簡單的規則在曲線上定義點的新增:如果你在曲線中繪製一條直線並且它擊中三個點A,B和C,則新增由A+定義B+C=0。由於橢圓曲線的特殊性質,以這種方式定義的加法“正常”起作用並形成一個組。如果定義了加法,則可以定義整數乘法:例如4A=A+A+A+A。

使橢圓曲線在加密方面有用的原因是它可以快速進行整數乘法,但除法基本上需要強力。例如,你可以非常快速地計算諸如12345678A=Q的乘積(通過計算2的冪),但是如果你只知道A和Q求解nA=Q很難。在橢圓曲線加密中,密碼12345678將是私鑰,曲線上的點Q將是公鑰。

在密碼學中,座標不是在曲線上使用實值點,而是以模數為模的整數。橢圓曲線的一個令人驚訝的特性是,無論使用實數還是模運算,數學運算都非常相似。因此,比特幣的橢圓曲線看起來不像上面的圖片,而是一個隨機的256位點混亂(想象一個大的灰色方塊點)。

橢圓曲線數字簽名演算法(ECDSA)採用訊息雜湊,然後使用訊息,私鑰和隨機數進行一些簡單的橢圓曲線演算法,以在曲線上生成給出簽名的新點。擁有公鑰,訊息和簽名的任何人都可以執行一些簡單的橢圓曲線演算法來驗證簽名是否有效。因此,只有具有私鑰的人才能簽署訊息,但是具有公鑰的任何人都可以驗證該訊息。

有關橢圓曲線的更多資訊,請參閱參考文獻。

將我的交易傳送到p2p網路

留下橢圓曲線,此時我建立了一個交易並簽名。下一步是將其傳送到點對點網路,在那裡它將被礦工接收併合併到一個塊中。

如何找到同行

使用p2p網路的第一步是找到一個對等體。每當有人執行客戶端時,對等體列表每隔幾秒就會更改一次。一旦節點連線到對等節點,它們就會在發現新對等體時通過交換addr訊息來共享新對等體。因此,新同伴迅速通過該系統傳播。

然而,關於如何找到第一個同伴,有一個雞與蛋的問題。比特幣客戶通過幾種方法解決了這個問題。幾個可靠的對等體在DNS中以bitseed.xf2.org的名稱註冊。通過執行nslookup,客戶端獲取這些對等端的IP地址,並希望其中一個可以工作。如果這不起作用,則將對等體的種子列表硬編碼到客戶端中。

nslookup可用於查詢比特幣對等體

當普通使用者啟動和停止比特幣客戶端時,同行進入和離開網路,因此客戶端有大量的營業額。我使用的客戶現在不太可能正常運營,所以如果你想做實驗,你需要找到新的同行。 你可能需要嘗試一堆才能找到有效的方法。

和同齡人交談

一旦我獲得了工作對等體的地址,下一步就是將我的交易傳送到對等網路。使用點對點協議非常簡單。我在埠8333上打開了與任意對等方的TCP連線,開始傳送訊息,並依次接收訊息。 比特幣點對點協議非常寬容;即使我完全搞砸了請求,同行也會保持溝通。

重要提示:正如一些人所指出的,如果你想進行實驗,你應該使用比特幣測試網,這可以讓你試驗“虛假”的比特幣,因為如果你搞砸了真正的網路,很容易丟失你的寶貴的比特幣。(例如,如果你忘記了交易中的更改地址,那麼多餘的比特幣將作為費用交給礦工。)但我想我會使用真正的比特幣網路並冒險使用價值1.00美元的比特幣。

該協議由大約24種不同的訊息型別組成。每條訊息都是一個相當簡單的二進位制blob,包含一個ASCII命令名和一個適合該命令的二進位制有效負載。該協議在比特幣維基上有詳細記錄 。

連線到對等方的第一步是通過交換版本訊息來建立連線。首先,我傳送一個版本訊息,其中包含我的協議版本號,地址和其他一些內容。對等體發回其版本訊息。在此之後,節點應該用verack訊息確認版本訊息。(正如我所提到的,該協議是寬容的 - 即使我跳過了那個行列,一切正常。)

生成版本訊息並不是完全無關緊要的,因為它有一堆欄位,但它可以用幾行Python建立。下面的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)

傳送交易:tx

我使用下面的精簡Python指令碼將交易傳送到對等網路。該指令碼傳送版本訊息,接收(並忽略)對等方的版本和維拉訊息,然後將該交易作為tx訊息傳送。十六進位制字串是我之前建立的交易。

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')))

以下螢幕截圖顯示瞭如何在Wireshark網路分析程式中傳送我的交易。我編寫了Python指令碼來處理比特幣網路流量,但為了簡單起見,我將在這裡使用Wireshark。“tx”訊息型別在ASCII轉儲中可見,在我的交易開始的下一行(01 00 …)後面。

上傳到比特幣的交易,如Wireshark所示

為了監視我的交易的進度,我有一個套接字開啟給另一個隨機對等體。傳送我的交易五秒後,另一個對等體傳送了一條tx訊息,其中包含我剛剛傳送的交易的雜湊值。因此,我的交易只需幾秒鐘即可在對等網路或至少部分網路中傳遞。

勝利:我的交易被開採了

在將我的交易傳送到對等網路後,我需要等待它才能獲得勝利。十分鐘後,我的指令碼收到一條帶有新塊的inv訊息(參見下面的Wireshark描述)。檢查此塊顯示它包含我的交易,證明我的交易有效。我還可以通過檢視我的比特幣錢包和線上查詢來驗證此交易是否成功。因此,經過大量的努力,我成功地手動建立了一個交易並讓它被系統接受。(不用說,我的前幾次交易嘗試都沒有成功,我的錯誤交易消失在網路中,永遠不會被再次看到。)

比特幣中的新塊,如Wireshark中所示

我的交易是由大型GHash.IO礦池開採的,塊為#279068,雜湊為0000000000000001a27b1d6eb8c405410398ece796e742da3b3e35363c2219ee。(雜湊在上面的inv訊息中反轉:ee19…)請注意,雜湊以大量零開始——在quintillion值中找到這樣的字面意思是使挖掘變得如此困難的原因。這個特殊的塊包含462個交易,其中我的交易只有一個。

為了開採這個區塊,礦工們獲得了25比特幣的獎勵,總費用為0.104比特幣,分別約為19,000美元和80美元。我支付了0.0001比特幣的費用,約佔我交易的8美分或10%。挖掘過程非常有趣,但我將把它留給以後的文章。

結論

使用原始的比特幣協議比我預期的要困難,但我一路上學到了很多關於比特幣的知識,我希望你也做到了。我的程式碼純粹是為了演示——如果你真的想通過Python使用比特幣,請使用真正的庫而不是我的程式碼。

======================================================================

分享一些以太坊、EOS、比特幣等區塊鏈相關的互動式線上程式設計實戰教程:

  • EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、賬戶與錢包、發行代幣、智慧合約開發與部署、使用程式碼與智慧合約互動等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
  • java以太坊開發教程,主要是針對java和android程式設計師進行區塊鏈以太坊開發的web3j詳解。
  • python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
  • php以太坊,主要是介紹使用php進行智慧合約開發互動,進行賬號建立、交易、轉賬、代幣開發以及過濾器和交易等內容。
  • 以太坊入門教程,主要介紹智慧合約與dapp應用開發,適合入門。
  • 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
  • C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括賬戶管理、狀態與交易、智慧合約開發與互動、過濾器和交易等。
  • java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Java程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
  • php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Php程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
  • tendermint區塊鏈開發詳解,本課程適合希望使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI介面、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操程式碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。

匯智網原創翻譯,轉載請標明出處。這裡是原文如何使用原始比特幣協議