比特幣交易

理解交易對比特幣系統是如何工作的是非常重要的,可以說比特幣整個的工作流程就是圍繞着交易展開的。下面我們先敘述一下比特幣交易流程,在宏觀上對交易有個認識,後面會講一下交易數據結構,具體在程序中是怎麼定義的。下面先讓我們看一下比特幣交易流程。

比特幣交易流程

比特幣交易並不是通常意義上的一手交錢一手交貨的交易,而是轉賬。如果每一筆轉賬都需要構造一筆交易數據會比較笨拙,爲了使得價值易於組合與分割,比特幣的交易被設計爲可以納入多個輸入和輸出,即一筆交易可以轉賬給多個人。從生成到在網絡中傳播,再到通過工作量證明、整個網絡節點驗證,最終記錄到區塊鏈,就是區塊鏈交易的整個生命週期。整個區塊鏈交易流程如下圖所示:
這裏寫圖片描述

  1. 交易的生成——構造一筆交易,設置交易的輸入輸出,並簽名確認。
  2. 交易的傳播——A將交易廣播至全網,節點收到交易驗證通過後再廣播給其他節點,並都將收到的交易納入一個區塊中等待工作量證明。(對B而言,該筆比特幣會即時顯示在比特幣錢包中,但直到區塊確認成功後纔可用。目前一筆比特幣從支付到最終確認成功,一般要經過6個區塊確認之後才能真正確認到賬)
  3. 工作量證明——每個節點通過相當於解一道數學題的工作量證明機制,從而獲得創建新區塊的權力,並爭取得到數字貨幣的獎勵,交易上鍊。(新比特幣在此過程中產生)
  4. 整個網絡節點驗證——當一個節點找到解時,它就向全網廣播該區塊,並由全網其他節點驗證。如果驗證通過則向其他節點廣播並進行第5步。
  5. 記錄到區塊鏈——上一步驗證通過後,將新區塊加入到最長的區塊鏈中。之後繼續競爭下一個區塊,這樣就形成了一個合法記賬的區塊鏈。(每個區塊的創建時間大約在10分鐘。隨着全網算力的不斷變化,每個區塊的產生時間會隨算力增強而縮短、隨算力減弱而延長。其原理是根據最近產生的2016個區塊的時間差,自動調整每個區塊的生成難度,使得每個區塊的生成時間是10分鐘)

比特幣難度調整公式(新難度計算公式): New Difficulty=Old Difficulty×(Actual Time of Last 2016 Blocks/20160 minutes)New \ Difficulty=Old \ Difficulty \times (Actual \ Time\ of\ Last\ 2016\ Blocks/20160 \ minutes).

到這裏,你已經理解了一筆交易從創建到寫入區塊鏈的整個流程,下面我們討論一下其中的細節。先看一下交易的數據結構。

比特幣交易數據結構

一筆比特幣交易是一個含有輸入和輸出的數據結構,直白點就是要說明清楚這邊交易的錢從哪裏來,錢到哪裏去。我們看一下在源碼中交易的字段有那些,主要字段:

  • 輸入列表vin
  • 輸出列表vout
  • 協議版本nVersion
  • 鎖定時間nLockTime
/** The basic transaction that is broadcasted on the network and contained in
 * blocks.  A transaction can contain multiple inputs and outputs.  */
class CTransaction
{
public:
    // Default transaction version.
    static const int32_t CURRENT_VERSION=2;

    // Changing the default transaction version requires a two step process: first
    // adapting relay policy by bumping MAX_STANDARD_VERSION, and then later date
    // bumping the default CURRENT_VERSION at which point both CURRENT_VERSION and
    // MAX_STANDARD_VERSION will be equal.
    static const int32_t MAX_STANDARD_VERSION=2;

    // The local variables are made const to prevent unintended modification
    // without updating the cached hash value. However, CTransaction is not
    // actually immutable; deserialization and assignment are implemented,
    // and bypass the constness. This is safe, as they update the entire
    // structure, including the hash.
    const std::vector<CTxIn> vin;
    const std::vector<CTxOut> vout;
    const int32_t nVersion;
    const uint32_t nLockTime;

private:
    /** Memory only. */
    const uint256 hash;
    const uint256 m_witness_hash;

    uint256 ComputeHash() const;
    uint256 ComputeWitnessHash() const;

public:
// ...... 下面代碼這裏省略掉了......
    
};

這裏先解釋一下nLockTime字段,後面如果學習閃電網絡的話會用到這個字段。

// Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp.
static const unsigned int LOCKTIME_THRESHOLD = 500000000;

鎖定時間定義了能被加到區塊鏈裏最早的交易時間。在大多數交易裏,它被設置成0,表示立即執行。如果鎖定時間大於0且小於5億,就被視爲區塊高度,在此區塊高度之前,該交易不能被包含在區塊鏈裏。如果鎖定時間大於5億,則被當做一個UNIX時間戳(1970年1月1日以來的秒數),在這個時間點之前,該交易不能被包含在區塊鏈裏。

協議版本nVersion很好理解,就是明確這筆交易參照的規則協議。

最重要的是交易輸入和輸出,在分析交易輸入和輸出之前我們先使用BitcoinCore的命令行界面(getrawtransactiondecodeawtransaction)來檢索一筆“原始”交易,對其進行解碼,並查看它包含的內容。 交易被解碼後是這個樣子,結果如下:

你也可以通過區塊鏈瀏覽器查看這筆交易(塊高度: 277316 , 交易ID :0627052b6f28912f2703066a912ea577f2ce4da4caa5a5fbd8a57286c345c2f2)

{
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid":"7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",      // 交易ID
      "vout": 0,        // 輸出索引(vout),用於標識來自該交易的哪個UTXO被引用(第一個爲零)
      "scriptSig": "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",  // 解鎖腳本,滿足放置在UTXO上的條件,解鎖它用於支出
      "sequence": 4294967295    // 序列號
    }
 ],
  "vout": [
    {
      "value": 0.01500000,
      "scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
    },
    {
      "value": 0.08450000,
      "scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
    }
  ]
}

到這裏,你對交易應該有了一個感性的認識了,這筆交易有1個輸入,2個輸出。下面我們接着看交易輸入輸出。

交易輸入

/** An input of a transaction.  It contains the location of the previous
 * transaction's output that it claims and a signature that matches the
 * output's public key. */
class CTxIn
{
public:
    COutPoint prevout;      // 指向輸入中引用的UTXO
    CScript scriptSig;      // 解鎖腳本,首先檢索引用的UTXO,檢查其鎖定腳本,然後使用它來構建所需的解鎖腳本以滿足此要求。
    uint32_t nSequence;     // 序列號
    CScriptWitness scriptWitness; //!< Only serialized through CTransaction    ,見證腳本

//.......省略下面部分代碼.............
};

/** An outpoint - a combination of a transaction hash and an index n into its vout */
class COutPoint
{
public:
    uint256 hash;   // 交易ID
    uint32_t n;     // 輸出索引,表明是交易中的第幾個輸出

// ......省略下面部分代碼......
};

可以看到,交易輸入就是上一筆交易的輸出,需要構建解鎖腳本以證明你對該筆輸出的所有權,然後你可以花費這筆輸出。

交易輸出

/** An output of a transaction.  It contains the public key that the next input
 * must be able to sign with to claim it.
 */
class CTxOut
{
public:
    CAmount nValue;             // 輸出金額
    CScript scriptPubKey;       // 鎖定腳本(腳本公鑰) ,對於一個比特幣交易來說,交易本身是不用關心輸出的地址,交易只需要關心鎖定腳本,當使用的時候能使用正確的解鎖腳本即可動用比特幣。

// ......省略下面部分代碼......
};

輸出更簡單,就是指定轉賬金額,輸出給誰,構建一個鎖定腳本,只有轉賬接收者才能解鎖成功。擁有該輸出金額的所有權。

交易費

可以看到,交易的數據結構沒有交易費的字段。相替代地,交易費是指輸入和輸出之間的差值。從所有輸入中扣掉所有輸出之後的多餘的量會被礦工作爲交易費收集走:

交易費即輸入總和減輸出總和的餘量:交易費 = 求和(所有輸入) - 求和(所有輸出)

交易費,最直接的影響是與交易被處理的優先級有關,交易費用高的交易會被礦工優先打包處理,會在較短的時間經過驗證上鍊,而交易費用低的交易則會最後處理。同時,交易費用的設置,使得在比特幣網絡上發交易是有成本的,避免了大量可能的無用交易。

查詢交易

可通過區塊鏈瀏覽器查詢每一筆比特幣交易,比特幣區塊鏈瀏覽器有很多,這裏使用 https://btc.com 進行查詢。

比特幣交易腳本和腳本語言

腳本是區塊鏈上實現自動驗證、自動執行合約的重要技術。腳本類似一套規則,它約束着接收方怎樣才能花掉這個輸出上鎖定的資產。交易和合法性驗證也依賴於腳本。我們看一下比特幣交易中的鎖定腳本和解鎖腳本。

鎖定腳本和解鎖腳本

鎖定腳本是在輸出交易上加上的條件,通過一段腳本語言來實現,位於交易的輸出。解鎖腳本只有滿足鎖定腳本要求的條件,才能花掉這個腳本上對應的資產,位於交易的輸入。解釋腳本是通過類似編程領域裏的“虛擬機”,它分佈式運行在區塊鏈網絡裏的每一個節點。

每一個比特幣驗證節點會通過同時執行鎖定和解鎖腳本來驗證一筆交易。每個輸入都包含一個解鎖腳本,並引用了之前存在的UTXO。 驗證軟件將複製解鎖腳本,檢索輸入所引用的UTXO,並從該UTXO複製鎖定腳本。然後依次執行解鎖和鎖定腳本。如果解鎖腳本滿足鎖定腳本條件,則輸入有效。所有輸入都是獨立驗證的,作爲交易總體驗證的一部分。

下圖是最常見類型的比特幣交易(P2PKH:對公鑰哈希的付款)的解鎖和鎖定腳本的示例,顯示了在腳本驗證之前從解鎖和鎖定腳本的並置產生的組合腳本:
image

比特幣的腳本語言被稱爲基於堆棧的語言,因爲它使用一種被稱爲堆棧的數據結構。腳本語言通過從左到右處理每個項目來執行腳本。數字(數據常量)被推到堆棧上。操作碼(Operators)從堆棧中推送或彈出一個或多個參數,對其進行操作,並可能將結果推送到堆棧上。

解鎖和鎖定腳本的單獨執行

在最初版本的比特幣客戶端中,解鎖和鎖定腳本是以連鎖的形式存在,並被依次執行的。出於安全因素考慮,比特幣開發者們修改了這個特性——因爲存在“允許異常解鎖腳本推送數據入棧並且污染鎖定腳本”的漏洞。而在當前的方案中,這兩個腳本是隨着堆棧的傳遞被分別執行的。

首先,使用堆棧執行引擎執行解鎖腳本。如果解鎖腳本在執行過程中未報錯(例如:沒有“懸掛”操作碼),則複製主堆棧(而不是備用堆棧),並執行鎖定腳本。如果從解鎖腳本中複製而來的堆棧數據執行鎖定腳本的結果爲“TRUE",那麼解鎖腳本就成功地滿足了鎖定腳本所設置的條件,因此,該輸入是一個能使用該UTXO的有效授權。如果在合併腳本後的結果不是”TRUE“以外的任何結果,輸入都是無效的,因爲它不能滿足UTXO中所設置的使用該筆資金的條件。

下面以最常見類型的比特幣交易(P2PKH:對公鑰哈希的付款)的解鎖和鎖定腳本爲例(見上圖),說明執行過程。
image

image

Coinbase交易

上面講的都是普通情況下的交易,但有一個例外,即存在一種被稱爲“幣基交易”(Coinbase Transaction)的特殊交易,它是每個區塊中的第一筆交易,這種交易存在的原因是作爲對挖礦的獎勵,創造出全新的可花費比特幣用來支付給“贏家”礦工。

引申思考

爲什麼比特幣交易要設計成這種輸入輸出的結構,與以太坊等區塊鏈的結構是不同的。我的理解,是要回到比特幣設計的初衷,比特幣是一個非中心化的點對點電子支付系統,非中心化實現這一系統,最大的問題在於如何解決雙花問題。當然,如何解決雙花問題不是僅僅設計一個交易數據結構就可以解決的,還需要結合Pow共識算法及整個比特幣區塊鏈實現流程。設計成這種數據結構,一個最大的好處是每筆交易的比特幣都是可追溯的,你甚至一直可以追溯到它的誕生,挖礦獎勵。因爲每一筆交易(花費的比特幣)都是可追溯的,作弊者就無法構造一個無中生有的比特幣,它無法構造出合法的未花費交易輸出。每一筆交易都會通過廣播盡力廣播給所有比特幣節點。

如果您還沒有理解,沒有關係,因爲僅僅掌握比特幣交易數據結構是不夠的,它只是比特幣區塊鏈系統設計的一部分,另外歡迎搜索並關注微信公衆號:讓我思考一下,獲取更多區塊鏈相關技術分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章