天天看點

比特币核心開發引擎如何運作

比特币真的很酷。當然,有人在想它是否是一種有用的技術,無論我們目前是否處于加密貨币泡沫中,或者它目前面臨的治理問題是否會得到解決…但在純粹的技術層面上,神秘的Satoshi Nakamoto創造了令人印象深刻的技術。

不幸的是,雖然有很多資源可以對比特币的工作原理給出進階解釋,我強烈推薦的一個這樣的視訊資源Anders Brownworth的blockchain visual 101,但是底層資訊比較少。在我看來,如果你檢視10000英尺的視圖,那麼你可以正确地了解這一點。

作為一個新手來說,我發現自己渴望了解比特币如何運作的機制。幸運的是,由于比特币本質上是去中心化的并且是對等的,是以任何人都能夠開發符合協定的用戶端。為了更好地了解比特币的運作方式,我決定編寫自己的小玩具比特币用戶端,該用戶端能夠向比特币區塊鍊釋出交易。

這篇文章介紹了建立一個最低限度可行的比特币用戶端的過程,該用戶端可以建立一個交易并将其送出給比特币點對點網絡,以便它包含在區塊鍊中。如果你隻是閱讀原始代碼,請随時檢視我的Github repo。

位址生成

要成為比特币網絡的一部分,必須有一個位址,你可以從中發送和接收資金。比特币使用公鑰加密,并且位址基本上是從公鑰私鑰派生的公鑰的哈希版本。令人驚訝的是,與大多數公鑰加密不同,公鑰在儲存之前也會加密,直到資金從位址發送——但稍後會有更多的不同和驚訝。

快速了解術語:在比特币中,客戶使用術語錢包

wallet

來表示位址集合。在協定級别沒有錢包的概念,隻有位址。

比特币使用橢圓曲線公鑰加密技術作為其位址。在超進階别,橢圓曲線加密用于從私鑰生成公鑰,與RSA相同,但占用空間較小。如果你有興趣學習一些關于它如何工作的數學知識,那麼Cloudflare的入門書是一個很棒的資源。

從256位私鑰開始,生成比特币位址的過程如下所示:

比特币核心開發引擎如何運作

在Python中,我使用ecsda庫來完成橢圓曲線加密的繁重工作。下面的代碼片段擷取了一個公鑰通過高度令人難忘的(并且非常不安全)私鑰

0xFEEDB0BDEADBEEF

(前面填充了足夠的零,使其成為64個十六進制字元長,或256位)。如果你想在位址中存儲任何實際值,你需要一種更安全的生成私鑰的方法!

作為一個有趣的測試,我最初使用私鑰

0xFACEBEEF

建立了一個位址并發送了它0.0005BTC,1個月後,有人**偷了我的0.0005BTC!**我想人們必須偶爾會使用簡單的公共私鑰去搜尋位址。你真的必須使用正确的密鑰推導技術!

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的

x

y

坐标隻是連接配接在一起。由于ECSDA的工作方式,如果你知道

x

值,

y

值隻能取兩個值,一個偶數,一個奇數。使用該資訊,可以使用

x

y

的極性來表達公鑰。這将公鑰大小從65位減少到33位,并且密鑰(和随後的計算位址)被稱為壓縮。對于壓縮公鑰,前置值将為

0x02

0x03

,具體取決于

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

的用戶端,并且因為如果不這樣做,同行不會對我不同,我将忽略他們的版本消息而不向他們發送确認。在我連接配接時發送版本消息足以讓我以後發送更有意義的消息。

運作上面的代碼段列印出以下内容。它當然看起來很有希望——

Satoshi

Verack

在轉儲中看到的好詞!如果我的版本消息格式錯誤,則節點根本不會響應。

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>*\[email protected]\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語言工程師快速入門區塊鍊開發的最佳選擇。

彙智網原創翻譯,轉載請标明出處。這裡是原文窺探比特币核心如何運轉