窺探比特幣核心機制如何運轉

比特幣真的很酷。固然,有人在想它是不是一種有用的技術,不管咱們目前是否處於加密貨幣泡沫中,或者它目前面臨的治理問題是否會獲得解決......但在純粹的技術層面上,神祕的Satoshi Nakamoto創造了使人印象深入的技術。php

不幸的是,雖然有不少資源能夠對比特幣的工做原理給出高級解釋,我強烈推薦的一個這樣的視頻資源Anders Brownworth的blockchain visual 101,可是底層信息比較少。在我看來,若是你查看10000英尺的視圖,那麼你能夠正確地理解這一點。html

做爲一個新手來講,我發現本身渴望瞭解比特幣如何運做的機制。幸運的是,因爲比特幣本質上是去中心化的而且是對等的,因此任何人都可以開發符合協議的客戶端。爲了更好地瞭解比特幣的運做方式,我決定編寫本身的小玩具比特幣客戶端,該客戶端可以向比特幣區塊鏈發佈交易。java

這篇文章介紹了建立一個最低限度可行的比特幣客戶端的過程,該客戶端能夠建立一個交易並將其提交給比特幣點對點網絡,以便它包含在區塊鏈中。若是你只是閱讀原始代碼,請隨時查看個人Github reponode

地址生成

要成爲比特幣網絡的一部分,必須有一個地址,你能夠從中發送和接收資金。比特幣使用公鑰加密,而且地址基本上是從公鑰私鑰派生的公鑰的哈希版本。使人驚訝的是,與大多數公鑰加密不一樣,公鑰在保存以前也會加密,直到資金從地址發送——但稍後會有更多的不一樣和驚訝。python

快速瞭解術語:在比特幣中,客戶使用術語錢包wallet來表示地址集合。在協議級別沒有錢包的概念,只有地址。android

比特幣使用橢圓曲線公鑰加密技術做爲其地址。在超高級別,橢圓曲線加密用於從私鑰生成公鑰,與RSA相同,但佔用空間較小。若是你有興趣學習一些關於它如何工做的數學知識,那麼Cloudflare的入門書是一個很棒的資源。git

從256位私鑰開始,生成比特幣地址的過程以下所示:程序員

比特幣地址生成

在Python中,我使用ecsda庫來完成橢圓曲線加密的繁重工做。下面的代碼片斷獲取了一個公鑰經過高度使人難忘的(而且很是不安全)私鑰0xFEEDB0BDEADBEEF(前面填充了足夠的零,使其成爲64個十六進制字符長,或256位)。若是你想在地址中存儲任何實際值,你須要一種更安全的生成私鑰的方法!github

做爲一個有趣的測試,我最初使用私鑰0xFACEBEEF建立了一個地址併發送了它0.0005BTC,1個月後,有人**偷了個人0.0005BTC!**我想人們必須偶爾會使用簡單的公共私鑰去搜索地址。你真的必須使用正確的密鑰推導技術!web

from ecdsa import SECP256k1, SigningKey

def get_private_key(hex_string):
    return bytes.fromhex(hex_string.zfill(64)) # pad the hex string to the required 64 characters

def get_public_key(private_key):
    # this returns the concatenated x and y coordinates for the supplied private address
    # the prepended 04 is used to signify that it's uncompressed
    return (bytes.fromhex("04") + SigningKey.from_string(private_key, curve=SECP256k1).verifying_key.to_string())

private_key = get_private_key("FEEDB0BDEADBEEF")
public_key = get_public_key(private_key)

運行此代碼獲取私鑰(十六進制)

0000000000000000000000000000000000000000000000000feedb0bdeadbeef

和公鑰(十六進制)

04d077e18fd45c031e0d256d75dfa8c3c21c589a861c4c33b99e64cf613113fcff9fc9d90a9d81346bcac64d3c01e6e0ef0828543edad73c0e257b845812cc8d28

預先設置公鑰的0x04表示這是一個未壓縮的公鑰,這意味着ECDSA的xy座標只是鏈接在一塊兒。因爲ECSDA的工做方式,若是你知道x值,y值只能取兩個值,一個偶數,一個奇數。使用該信息,可使用xy的極性來表達公鑰。這將公鑰大小從65位減小到33位,而且密鑰(和隨後的計算地址)被稱爲壓縮。對於壓縮公鑰,前置值將爲0x020x03,具體取決於y的極性。未壓縮的公鑰最經常使用於比特幣,因此這也是我在這裏使用的。

從這裏開始,要從公鑰生成比特幣地址,公鑰是sha256哈希,而後是cookedmd160哈希。這種雙重哈希提供了額外的安全層,而ripemd160哈希提供了sha256的256位哈希的160位哈希,縮短了地址的長度。一個有趣的結果是,兩個不一樣的公鑰能夠哈希到同一個地址!可是,對於2^160個不一樣的地址,這不太可能很快發生。

import hashlib

def get_public_address(public_key):
    address = hashlib.sha256(public_key).digest()

    h = hashlib.new('ripemd160')
    h.update(address)
    address = h.digest()

    return address

public_address = get_public_address(public_key)

上面的代碼生成一個公共地址c8db639c24f6dc026378225e40459ba8a9e54d1a。這有時被稱爲哈希160地址。

如前所述,一個有趣的觀點是,從私鑰到公鑰的轉換以及從公鑰到公共地址的轉換都是單向轉換。若是你有地址,則向後工做以查找關聯公鑰的惟一方法是解決SHA256哈希。這與大多數公鑰加密略有不一樣,如在公鑰中發佈公鑰並隱藏你的私鑰。在這種狀況下,隱藏公鑰和私鑰,併發布地址(哈希公鑰)。

隱藏公鑰是有充分理由的。雖然從相應的公鑰計算私鑰一般是不可行的,可是若是生成私鑰的方法已被泄露,那麼訪問公鑰使得推斷私鑰變得容易得多。在2013年,這個臭名昭着的Android比特幣錢包事件。Android有一個產生隨機數的關鍵弱點,它爲攻擊者打開了一個向量,能夠從公鑰中找到私鑰。這也是爲何不鼓勵在比特幣中重複使用地址的緣由——簽署交易時,你須要透露你的公鑰。若是在從地址發送交易後不重用地址,則無需擔憂該地址的公鑰被公開。

表達比特幣地址的標準方法是使用它的Base58Check編碼。該編碼僅是地址的表示(所以能夠被解碼/反轉)。Base58Check生成1661HxZpSy5jhcJ2k6av2dxuspa8aafDac格式的地址。Base58Check編碼提供了一個較短的地址來表達,而且還有一個內置的校驗和,容許檢測錯誤的地址。在幾乎每一個比特幣客戶端中,你的地址的Base58Check編碼就是你將看到的地址。Base58Check還包含一個版本號,我在下面的代碼中將其設置爲0——這表示該地址是一個pubkey哈希。

# 58 character alphabet used
BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

def base58_encode(version, public_address):
    """
    Gets a Base58Check string
    See https://en.bitcoin.it/wiki/base58Check_encoding
    """
    version = bytes.fromhex(version)
    checksum = hashlib.sha256(hashlib.sha256(version + public_address).digest()).digest()[:4]
    payload = version + public_address + checksum

    result = int.from_bytes(payload, byteorder="big")

    print(result)

    # count the leading 0s
    padding = len(payload) - len(payload.lstrip(b'\0'))
    encoded = []

    while result != 0:
        result, remainder = divmod(result, 58)
        encoded.append(BASE58_ALPHABET[remainder])

    return padding*"1" + "".join(encoded)[::-1]

bitcoin_address = base58_encode("00", public_address)

畢竟,從個人私有密鑰feedb0bdeadbeef開始(前面填充零),個人比特幣地址爲1KK2xni6gmTtdnSGRiuAf94jciFgRjDj7W!

有了地址,如今能夠得到一些比特幣!爲了讓比特幣進入個人地址,我從btcmarkets以澳元購買了0.0045BTC(在撰寫本文時,大約11美圓)。使用btcmarket的交易門戶網站,我將其轉移到上述地址,在此過程當中損失了0.0005BTC到交易費用。你能夠在交易95855ba9f46c6936d7b5ee6733c81e715ac92199938ce30ac3e1214b8c2cd8d7中的區塊鏈上看到此交易。

鏈接到p2p網絡

如今我有一個地址包含一些比特幣,事情變得更有趣。若是我想將比特幣發送到其餘地方,則須要鏈接到比特幣節點網絡。

導言

我在第一次瞭解比特幣時遇到的一個難點是,考慮到網絡的分散性,網絡中的同行如何找到其餘同行?若是沒有集中的權限,比特幣客戶端如何知道如何引導並開始與網絡的其餘部分進行通訊?

事實證實,理想主義提出了實用性,而且在最初的同伴發現過程當中有最少許的集中化。新客戶端找到要鏈接的節點的主要方式是使用DNS查找到由比特幣社區成員維護的任意數量的DNS種子DNS seed服務器。

事實證實,DNS很是適合引導客戶端的目的,由於DNS協議運行在UDP上而且很輕,很難用於DDoS。IRC被用做之前的引導方法,但因爲其對DDoS攻擊的弱點而被終止。

種子被硬編碼爲比特幣核心的源代碼,但核心開發人員可能會對其進行更改。

下面的Python代碼鏈接到DNS種子DNS seed並隨意選擇要鏈接的第一個節點。使用套接字庫,代碼基本上執行nslookup,並在針對種子節點seed.bitcoin.sipa.be運行查詢時返回第一個結果的ipv4地址。

import socket

# use a dns request to a seed bitcoin DNS server to find a node
nodes = socket.getaddrinfo("seed.bitcoin.sipa.be", None)

# arbitrarily choose the first node
node = nodes[0][4][0]

運行以後,返回的地址是208.67.251.126,這是一個友好的節點,我能夠鏈接到!

和新節點打個招呼

節點之間的比特幣鏈接是經過TCP進行的。在鏈接到節點時,比特幣協議的開始握手消息是版本消息。在節點交換版本消息以前,不會接受其餘消息。

比特幣協議消息在比特幣開發人員參考中有詳細記錄。使用開發人員參考做爲指南,能夠在Python中構建版本消息,以下面的代碼段所示。大多數數據都是至關無趣的,用於創建節點鏈接的管理數據。若是你對所附詳細信息的詳細信息感興趣,請閱讀開發人員參考。

version = 70014
services = 1 # not a full node, cant provide any data
timestamp = int(time.time())
addr_recvservices = 1
addr_recvipaddress = socket.inet_pton(socket.AF_INET6, "::ffff:127.0.0.1") #ip address of receiving node in big endian
addr_recvport = 8333
addr_transservices = 1
addr_transipaddress = socket.inet_pton(socket.AF_INET6, "::ffff:127.0.0.1")
addr_transport = 8333
nonce = 0
user_agentbytes = 0
start_height = 329167
relay = 0

使用Python的結構庫struct library,版本有效負載數據被打包成正確的格式,特別注意數據的字節順序和字節寬度。將數據打包成正確的格式很重要,不然接收節點將沒法理解它接收的原始字節。

payload = struct.pack("<I", version)
payload += struct.pack("<Q", services)
payload += struct.pack("<Q", timestamp)
payload += struct.pack("<Q", addr_recvservices)
payload += struct.pack("16s", addr_recvipaddress)
payload += struct.pack(">H", addr_recvport)
payload += struct.pack("<Q", addr_transservices)
payload += struct.pack("16s", addr_transipaddress)
payload += struct.pack(">H", addr_transport)
payload += struct.pack("<Q", nonce)
payload += struct.pack("<H", user_agentbytes)
payload += struct.pack("<I", start_height)

一樣,開發人員參考中提供了應該打包這些數據的方式。最後,在比特幣網絡上傳輸的每一個有效載荷須要加上一個header,header包含有效載荷的長度,校驗和以及有效載荷的消息類型。header還包含爲全部主要比特幣消息設置的magic常量0xF9BEB4D9。如下函數獲取帶有標頭的比特幣消息。

def get_bitcoin_message(message_type, payload):
    header = struct.pack(">L", 0xF9BEB4D9)
    header += struct.pack("12s", bytes(message_type, 'utf-8'))
    header += struct.pack("<L", len(payload))
    header += hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]

    return header + payload

將數據打包成正確的格式,並附上header,它能夠發送給咱們的同行!

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((node, 8333))
s.send(get_bitcoin_message("version", payload))
print(s.recv(1024))

比特幣協議要求在接收版本消息時,節點應響應Verack確認消息。由於我正在構建一個小小的for fun的客戶端,而且由於若是不這樣作,同行不會對我不一樣,我將忽略他們的版本消息而不向他們發送確認。在我鏈接時發送版本消息足以讓我之後發送更有意義的消息。

運行上面的代碼段打印出如下內容。它固然看起來頗有但願——SatoshiVerack在轉儲中看到的好詞!若是個人版本消息格式錯誤,則節點根本不會響應。

b'\xf9\xbe\xb4\xd9version\x00\x00\x00\x00\x00f\x00\x00\x00\xf8\xdd\x9aL\x7f\x11\x01\x00\r\x00\x00\x00\x00\x00\x00\x00\xddR1Y\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xcb\xce\x1d\xfc\xe9j\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xb8>*\x88@I\x8e\x10/Satoshi:0.14.0/t)\x07\x00\x01\xf9\xbe\xb4\xd9verack\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]\xf6\xe0\xe2'

比特幣交易

要進行比特幣轉帳,必須向比特幣網絡廣播交易。

關鍵的是,須要理解的最重要的想法是比特幣地址的餘額僅由地址能夠花費的"未花費的交易輸出"(Unspent Transaction Outputs 簡稱UTXO)的數量組成。當Bob向Alice發送比特幣時,他真的只是在建立一個UTXO,Alice(以及只有Alice)之後能夠用來建立另外一個UTXO併發送比特幣。所以,比特幣地址的餘額由它可以轉移到另外一個地址的比特幣數量來定義,而不是它所擁有的比特幣數量。

要強調的是,當有人說他們有X比特幣時,他們真的說他們能夠花費的全部UTXO總和爲X比特幣的價值。區別是微妙但重要的,比特幣地址的平衡不是直接記錄在任何地方,而是能夠經過對它能夠花費的UTXO求和來找到。當我意識到這絕對是「哦,這就是它的運做方式!」時刻。

這樣作的反作用是交易輸出能夠是未花費(UTXO)或徹底花費。不可能只花掉某人已經花費給你的一半的輸出,而後在之後花掉其他的時間。若是你確實想要花掉你收到的輸出的一小部分,你能夠發送你要發送的分數,同時將其他部分發回給本身。簡化版本以下圖所示。

比特幣交易

建立交易輸出時,將使用鎖定條件建立交易輸出,以便未來某人經過所謂的交易腳本來使用它。最多見的是,這種鎖定條件是:「要花費此輸出,你須要證實你擁有與特定公共地址相對應的私鑰」。這稱爲Pay-to-Public-Key-Hash腳本。可是,經過比特幣腳本,其餘類型的條件也是可能的。例如,能夠建立能夠由任何能夠解決某個哈希的任何人花費的輸出,或者能夠建立任何人均可以花費的交易。

經過Script,能夠建立簡單的基於合同的交易。腳本是一種基於堆棧的基本語言,其中的操做數量集中在檢查哈希值的相等性和驗證簽名。腳本不是圖靈完整的,也沒有能力進行任何循環。以此爲基礎的競爭加密貨幣以太坊可以擁有智能合約,它具備圖靈完整語言。關於在加密貨幣中加入圖靈完整語言的效用,必要性和安全性存在不少爭議,但我會將這種爭論留給其餘人!

在標準術語中,比特幣交易由輸入和輸出組成。輸入是UTXO(如今正在使用),輸出是新的UTXO。單個輸入能夠有多個輸出,但輸入須要徹底用於交易。礦工提出的任何輸入剩餘部分都是採礦費。

對於個人這個用來玩兒的客戶端,我但願可以將先前從交易所轉移的比特幣發送到個人FEEDB0BDEADBEEF地址。使用與之前相同的過程,我使用私鑰(在填充以前)生成另外一個地址BADCAFEFABC0FFEE。這產生了地址1QGNXLzGXhWTKF3HTSjuBMQQyUYFkWfgVC

建立原生交易

建立交易是首先打包「原生交易」而後簽署原生交易。一樣,開發人員參考文獻描述了進入交易的內容。構成交易的內容以下所示,但首先是幾個註釋:

  • 常見的比特幣說法使用術語簽名腳本和pubkey腳本,我以爲有點混亂。簽名腳本用於知足咱們想要在交易中使用的UTXO的條件,pubkey腳本用於設置花費咱們正在建立的UTXO所需的條件。簽名腳本的更好名稱多是解鎖腳本,而pubkey腳本的更好名稱多是鎖定腳本。
  • 比特幣交易價值在Satoshis中指定。Satoshi表明比特幣中最小的可分割部分,表明比特幣的百萬分之一。

爲簡單起見,下面顯示的是一個輸出和一個輸入的交易。具備多個輸入和輸出的更復雜的交易能夠以相同的方式建立。

暫時忽略簽名腳本和pubkey腳本,很容易看到原生交易的其餘字段應該包含哪些內容。要將個人FEEDB0BDEADBEEF地址中的資金髮送到個人BADCAFEFABC0FFEE地址,我會查看交易所建立的交易。這給了我:

  • 交易ID爲95855ba9f46c6936d7b5ee6733c81e715ac92199938ce30ac3e1214b8c2cd8d7
  • 發送到個人地址的輸出是第二個輸出,輸出1(輸出數字爲0索引)。
  • 輸出數量爲1,由於我想將FEEDB0BDEADBEEF中的全部內容發送到BADCAFEFABC0FFEE
  • 價值最多可達400,000Satoshis。容許一些費用必須低於此值。我將容許20,000 Satoshi做爲費用,所以將設定值爲380,000。
  • 鎖定時間將設置爲0,這容許在任什麼時候間或塊中包含交易。

對於咱們交易的Pubkey腳本,咱們使用Pay to Pubkey hash(或p2pk)腳本。該腳本確保只有擁有哈希到所提供的比特幣地址的公鑰的人才可以花費所建立的輸出,而且由持有公鑰的相應私鑰的人生成所提供的簽名。

要解鎖已被p2pk腳本鎖定的交易,用戶將提供其公鑰和原生交易的哈希簽名。對公鑰進行哈希處理,並與建立腳本的地址進行比較,並對提供的公鑰進行簽名驗證。若是公鑰的哈希值和地址相等,而且驗證了簽名,則可使用輸出。

在比特幣腳本操做數中,p2pk腳本以下所示。

OP_DUP
OP_HASH160
<Length of address in bytes>
<Bitcoin address>
OP_EQUALVERIFY
OP_CHECKSIG

將操做數轉換爲它們的值(能夠在wiki上找到)並輸入公共地址(在Base58Check編碼以前)以十六進制形式給出如下腳本:

0x76
0xA9
0x14
0xFF33195EC053D6E58D5FD3CC67747D3E1C71B280
0x88
0xAC

對於咱們發送給的私鑰0xBADCAFEFABC0FFEE,使用前面顯示的代碼從私鑰導出地址找到該地址。

簽署交易

(p2pk)交易中籤名腳本有兩個獨立但有些相關的用法:

  • 該腳本經過提供哈希到已發送UTXO的地址的哈希來驗證(解鎖)咱們正在嘗試花費的UTXO。
  • 該腳本還對咱們提交給網絡的交易進行簽名,這樣任何人都沒法在不使簽名失效的狀況下修改交易。

可是,原生交易包含簽名腳本,該腳本應包含原生交易的簽名!在簽署原生交易以前,經過將咱們正在使用的UTXO的Pubkey腳本放在簽名腳本槽中來解決這個雞和蛋的問題。據我所知,使用Pubkey做爲佔位符彷佛沒有任何合理的理由,它實際上多是任意數據。

在對原生交易進行哈希處理以前,還須要附加Hashtype value。最多見的hashtype值是SIGHASH_ALL,它對整個結構進行簽名,以便不能修改輸入或輸出。連接的Wiki頁面列出了其餘哈希類型,這些哈希類型能夠容許在簽名交易以後修改輸入和輸出的組合。

如下函數彙總了原生交易值的python字典。

def get_p2pkh_script(pub_key):
    """
    This is the standard 'pay to pubkey hash' script
    """
    # OP_DUP then OP_HASH160 then 20 bytes (pub address length)
    script = bytes.fromhex("76a914")

    # The address to pay to
    script += pub_key

    # OP_EQUALVERIFY then OP_CHECKSIG
    script += bytes.fromhex("88ac")

    return script

def get_raw_transaction(from_addr, to_addr, transaction_hash, output_index, satoshis_spend):
    """
    Gets a raw transaction for a one input to one output transaction
    """
    transaction = {}
    transaction["version"] = 1
    transaction["num_inputs"] = 1

    # transaction byte order should be reversed:
    # https://bitcoin.org/en/developer-reference#hash-byte-order
    transaction["transaction_hash"] = bytes.fromhex(transaction_hash)[::-1]
    transaction["output_index"] = output_index

    # temporarily make the signature script the old pubkey script
    # this will later be replaced. I'm assuming here that the previous
    # pubkey script was a p2pkh script here
    transaction["sig_script_length"] = 25
    transaction["sig_script"] = get_p2pkh_script(from_addr)

    transaction["sequence"] = 0xffffffff
    transaction["num_outputs"] = 1
    transaction["satoshis"] = satoshis_spend
    transaction["pubkey_length"] = 25
    transaction["pubkey_script"] = get_p2pkh_script(to_addr)
    transaction["lock_time"] = 0
    transaction["hash_code_type"] = 1

    return transaction

使用如下值調用代碼會建立我有興趣製做的原生交易。

private_key = address_utils.get_private_key("FEEDB0BDEADBEEF")
public_key = address_utils.get_public_key(private_key)
from_address = address_utils.get_public_address(public_key)
to_address = address_utils.get_public_address(address_utils.get_public_key(address_utils.get_private_key("BADCAFEFABC0FFEE")))

transaction_id = "95855ba9f46c6936d7b5ee6733c81e715ac92199938ce30ac3e1214b8c2cd8d7"  
satoshis = 380000
output_index = 1

raw = get_raw_transaction(from_address, to_address, transaction_id, output_index, satoshis)

看到我使用私鑰生成to_address可能會讓人感到困惑。這只是爲了方便起見,並顯示如何找到to_address。若是你正在與其餘人進行交易,你會詢問他們的公共地址並轉移到該地址,你不須要知道他們的私鑰。

爲了可以簽署並最終將交易傳輸到網絡,原生交易須要適當地打包。這是在get_packed_transaction函數中實現的,我不會在這裏複製,由於它實際上只是更多的struct打包代碼。若是你有興趣,能夠在個人Github倉庫中的bitcoin_transaction_utils.pyPython文件中找到它。

這容許我定義一個將產生簽名腳本的函數。生成簽名腳本後,它應替換佔位符簽名腳本。

def get_transaction_signature(transaction, private_key):
    """
    Gets the sigscript of a raw transaction
    private_key should be in bytes form
    """
    packed_raw_transaction = get_packed_transaction(transaction)
    hash = hashlib.sha256(hashlib.sha256(packed_raw_transaction).digest()).digest()
    public_key = address_utils.get_public_key(private_key)
    key = SigningKey.from_string(private_key, curve=SECP256k1)
    signature = key.sign_digest(hash, sigencode=util.sigencode_der)
    signature += bytes.fromhex("01") #hash code type

    sigscript = struct.pack("<B", len(signature))
    sigscript += signature
    sigscript += struct.pack("<B", len(public_key))
    sigscript += public_key

    return sigscript

本質上,簽名腳本是做爲我正在嘗試使用的上一個交易的pubkey腳本的輸入提供的,這樣我就能夠證實我能夠將我如今用做輸入的輸出用做輸入。下面顯示了這種工做原理的機制,它來自比特幣維基。從上到下,每行都是腳本的一次迭代。這是一個a pay to pubkey hash pubkey,如前所述,這是最多見的腳本。它也是我正在建立的交易和我正在兌換的交易使用的腳本。

若是提供的公鑰未哈希到腳本中的公鑰哈希,或者提供的簽名與提供的公鑰不匹配,則此腳本將失敗。這確保只有持有pubkey腳本中的地址私鑰的人才可以使用輸出。

你能夠看到這是我第一次須要在任何地方提供個人公鑰。到目前爲止,只有個人公共地址已經發布。這裏必須提供公鑰,由於它容許驗證交易已簽名的簽名。

使用get_transaction_signature函數,咱們如今能夠簽署並打包咱們的交易準備好傳輸!這涉及使用真實簽名腳本替換佔位符簽名腳本並從交易中刪除hash_code_type,以下所示。

signature = get_transaction_signature(raw, private_key )

raw["sig_script_length"] = len(signature)
raw["sig_script"] = signature
del raw["hash_code_type"]

transaction = get_packed_transaction(raw)

發佈交易

隨着交易的打包和簽名,這是告訴網絡有關它的問題。使用本文先前在bitcoin_p2p_message_utils.py中定義的一些函數,下面的代碼將比特幣消息頭放在傳輸上並將其傳輸給節點。如前所述,首先須要向節點發送版本消息,以便它接受後續消息。

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((get_bitcoin_peer(), 8333))
s.send(get_bitcoin_message("version", get_version_payload())
s.send(get_bitcoin_message("tx", transaction

發送交易是讓這項工做最煩人的部分。若是我提交了一個結構不正確或簽名不正確的交易,那麼對等方一般只是刪除了鏈接,或者在稍微好一點的狀況下,發回了神祕的錯誤消息。一個這樣的(很是惱人的)錯誤消息是S值沒必要要地高S value is unnecessarily high,這是由使用sigencode_der的ECSDA編碼方法對交易哈希進行簽名引發的。儘管簽名有效,但顯然比特幣礦工不喜歡以容許網絡中的垃圾郵件格式化的ECSDA簽名。解決方案是使用sigencode_der_canonize函數,該函數負責格式化其餘格式的簽名。一個簡單但很是難以調試的問題!

不管如何,當我看到個人交易進入區塊鏈時,我最終獲得了它而且很是興奮!知道個人小巧,手工製做的交易如今將永遠成爲比特幣分類帳的一部分,這是一種很棒的成就感。

當我提交交易時,個人交易費實際上與中位數相比很是低(我使用比特幣費用網站進行檢查),所以礦工大約花了5個小時決定將其包含在一個區塊內。我經過查看交易確認的數量來檢查這一點—— 這是對交易塊的深度塊數的衡量。在撰寫本文時,這是190個確認...意味着在塊以後個人交易在,還有另外190個區塊。這能夠很是安全地被認爲已經確認,由於它會在網絡上進行一次使人印象深入的攻擊,重寫190個塊來刪除個人交易。

結論

我但願你經過閱讀這篇文章對比特幣的工做方式有所瞭解,我知道在我花了幾個月把這一切放在一塊兒的時候我確實作到了!雖然這裏提供的大部分信息都不太適用——你一般只是使用一個能爲你完成全部工做的客戶——我認爲對事情的運做方式有了更深刻的瞭解,可讓你更好地瞭解幕後發生的事情。而且讓你更加自信地使用該技術。

若是你想仔細閱讀代碼,或進一步瞭解玩具示例,請查看個人相關Github repo。在比特幣世界中有不少能夠進一步探索的空間,我只是真正關注比特幣的一個很是常見的用例。除了在兩個地址之間傳遞價值以外,還有空間能夠作更酷的功績!我也沒有觸及如何挖掘,向區塊鏈添加交易的過程,工做..這是另外一個兔子洞一塊兒。

若是你已經讀過這篇文章,你可能已經意識到我轉移到1QGNXLzGXhWTKF3HTSjuBMpQyUYFkWfgVC的380000Satoshi(或0.0038BTC)能夠有足夠的智慧,任何人均可以使用..由於本文中存在該地址的私鑰。我很想知道須要多長時間才能被轉移,並但願不管是誰拿走它均可以使用我在這裏詳述的一些技術來實現這一目標!若是你只是將私鑰加載到錢包應用程序中,它會很是蹩腳,但我想我沒法阻止你!在撰寫本文時,這筆金額價值約10美圓,但若是比特幣「登月」,誰知道它可能值多少錢! (編輯:它如今已經消失了!在發表這篇文章幾個小時以後,由1KgoPFVDNcx7H2VY9bB2dxxP9yNM2Nar1N拿走了。好好玩!)

若是你正在尋找一個地址來發送比特幣,當你正在玩這些東西,或者若是你認爲這篇文章有價值足以保證提示——個人地址18uKa5c9S84tkN1ktuG568CR23vmeU7F5H很樂意接受任何小額捐款!或者,若是你想對我說錯誤,我很樂意聽到它。

更多資源

若是你發現這篇文章頗有趣,能夠查看一些其餘資源:

  • 掌握比特幣這本書解釋了比特幣的技術細節。我沒有徹底閱讀過這篇文章,可是在略讀時看起來它是一個很好的信息。
  • Ken Sheriff的博客文章是一個很好的信息來源,涵蓋了不少與本文相同的主題,但遺憾的是,當我寫完這篇文章時,我才發現這一點。若是你對本文中的任何內容一無所知,那麼閱讀Ken的優秀帖子將是一個很好的起點。
  • 前面提到過,但Anders Brownworth的 blockchain visual 101 video是區塊鏈技術如何運做的絕佳頂級視圖。
  • 除非你是痛苦的受虐狂,不然我也建議你不要從頭開始作任何事情,除非你有興趣爲學習目的這樣作。pycoin庫是一個Python比特幣庫,能夠爲你節省一些麻煩。
  • 爲了節省本身的痛苦,建議使用比特幣測試網絡,而不是像我同樣使用主網。也就是說,當代碼錯誤的風險正在失去真正的金錢時,它會更有趣!
  • 最後,可能值得重複的是,本文附帶的代碼能夠在個人Github repo中找到。

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

分享一些比特幣、以太坊、EOS等區塊鏈相關的交互式在線編程實戰教程:

  • java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Java代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
  • php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Php代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
  • EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、帳戶與錢包、發行代幣、智能合約開發與部署、使用代碼與智能合約交互等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
  • java以太坊開發教程,主要是針對java和android程序員進行區塊鏈以太坊開發的web3j詳解。
  • python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
  • php以太坊,主要是介紹使用php進行智能合約開發交互,進行帳號建立、交易、轉帳、代幣開發以及過濾器和交易等內容。
  • 以太坊入門教程,主要介紹智能合約與dapp應用開發,適合入門。
  • 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
  • ERC721以太坊通證明戰,課程以一個數字藝術品創做與分享DApp的實戰開發爲主線,深刻講解以太坊非同質化通證的概念、標準與開發方案。內容包含ERC-721標準的自主實現,講解OpenZeppelin合約代碼庫二次開發,實戰項目採用Truffle,IPFS,實現了通證以及去中心化的通證交易所。
  • C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括帳戶管理、狀態與交易、智能合約開發與交互、過濾器和交易等。
  • tendermint區塊鏈開發詳解,本課程適合但願使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI接口、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操代碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。

匯智網原創翻譯,轉載請標明出處。這裏是原文窺探比特幣核心如何運轉

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息