比特幣學習筆記——————5、 交易

FLy_鵬程萬里發表於2018-06-12


5.1 簡介

比特幣交易是比特幣系統中最重要的部分。根據比特幣系統的設計原理,系統中任何其他的部分都是為了確保比特幣交易可以被生成、能在比特幣網路中得以傳播和通過驗證,並最終新增入全球比特幣交易總賬簿(比特幣區塊鏈)。比特幣交易的本質是資料結構,這些資料結構中含有比特幣交易參與者價值轉移的相關資訊。比特幣區塊鏈是全球複式記賬總賬簿,每個比特幣交易都是在比特幣區塊鏈上的一個公開記錄。

在這一章,我們將會剖析比特幣交易的多種形式、所包含的資訊、如何被建立、如何被驗證以及如何成為所有比特幣交易永久記錄的一部分。

5.2 比特幣交易的生命週期

一筆比特幣交易的生命週期起始於它被建立的那一刻,也就是誕生(origination)。 隨後,比特幣交易會被一個或者多個簽名加密,這些簽名標誌著對該交易指向的比特幣資金的使用許可。接下來,比特幣交易被廣播到比特幣網路中。在比特幣網路中,每一個節點(比特幣交易參與者)驗證、並將交易在網路中進行廣播,直到這筆交易被網路中大多數節點接收。最終,比特幣交易被一個挖礦節點驗證,並被新增到區塊鏈上一個記錄著許多比特幣交易的區塊中。

一筆比特幣交易一旦被記錄到區塊鏈上並被足夠多的後續區塊確認,便成為比特幣總賬簿的一部分,並被所有比特幣交易參與者認可為有效交易。於是,被這筆交易分配到一個新所有者名下的比特幣資金可以在新的交易中被使用——這使得所有權鏈得以延伸且再次開啟一個新的比特幣交易生命週期。

5.2.1 建立比特幣交易

將一筆比特幣交易理解成紙質支票或許有助於加深我們對它的理解。與支票相似,一筆比特幣交易其實是一個有著貨幣轉移目的的工具,這個工具只有在交易被執行時才會在金融體系中體現,而且交易發起人並不一定是簽署該筆交易的人。

比特幣交易可以被任何人線上上或線下建立,即便建立這筆交易的人不是這個賬戶的授權簽字人。比如,一個負責應付賬款的櫃員在處理應付票據時可能會需要CEO簽名。相似地,這個負責應付賬款的櫃員可以建立比特幣交易,然後讓CEO對它進行數字簽名,從而使之有效。一張支票是指定一個特定賬戶作為資金來源的,但是比特幣交易指定以往的一筆交易作為其資金來源,而不是一個特定賬戶。

一旦一筆比特幣交易被建立,它會被資金所有者(們)簽名。如果它是合法建立並簽名的,則該筆交易現在就是有效的,它包含了轉移這筆資金所需要的所有資訊。最終,有效的比特幣交易必須能接入比特幣網路,從而使之能被傳送,直至抵達下一個登記在公共總賬薄(區塊鏈)的挖礦節點。

5.2.2 廣播交易至比特幣網路

首先,一筆交易需要傳遞至比特幣網路,才能被傳播,也才能加入區塊鏈中。本質上,一筆比特幣交易只是300到400位元組的資料,而且它們必須被髮送到成千上萬個比特幣節點中的任意一個。只要傳送者能使用多於一個比特幣節點來確保這筆交易被傳播,那麼傳送者並不需要信任用來傳播該筆交易的單一節點。相應地,這些節點不需要信任傳送者,也不用建立傳送者的“身份檔案”。由於這筆交易是經過簽名且不含任何機密資訊、私鑰或密碼,因此它可被任何潛在的便利網路公開地傳播。信用卡交易包含敏感資訊,而且依賴加密網路連線完成資訊傳輸,但比特幣交易可在任意網路環境下被髮送。只要這筆交易可以到達能將它廣播到比特幣網路的比特幣節點,這筆交易是如何被傳輸至第一個節點的並不重要。

比特幣交易因此可以通過未加密網路(例如WiFi、藍芽、NFC、ChirP、條形碼或者複製貼上至一個網頁表格)被髮送到比特幣網路。在一些極端情況下,一筆比特幣交易可以通過封包無線電、衛星或短波、擴頻或跳頻以避免被偵測或阻塞通訊的方式進行傳輸。一筆比特幣交易甚至可被編為文字資訊中的表情符號並被發表到線上論壇,或被髮送成一條簡訊或一條Skype聊天資訊。因為比特幣將金錢變成了一種資料結構,所以在本質上是不可能阻止任何人建立並執行比特幣交易的。

5.2.3 比特幣交易在比特幣網路中的傳播

一旦一筆比特幣交易被髮送到任意一個連線至比特幣網路的節點,這筆交易將會被該節點驗證。如果交易被驗證有效,該節點將會將這筆交易傳播到這個節點所連線的其他節點;同時,交易發起者會收到一條表示交易有效並被接受的返回資訊。如果這筆交易被驗證為無效,這個節點會拒絕接受這筆交易且同時返回給交易發起者一條表示交易被拒絕的資訊。

比特幣網路是一個點對點網路,這意味著每一個比特幣節點都連線到一些其他的比特幣節點(這些其他的節點是在啟動點對點協議時被發現的)。整個比特幣網路形成了一個鬆散地連線、且沒有固定拓撲或任何結構的“蛛網”——這使得所有節點的地位都是同等的。比特幣交易相關資訊(包括交易和區塊)被傳播——從每一個節點到它連線的其他節點。一筆剛通過驗證且並被傳遞到比特幣網路中任意節點的交易會被髮送到三到四個相鄰節點,而每一個相鄰節點又會將交易傳送到三至四個與它們相鄰的節點。以此類推,在幾秒鐘之內,一筆有效的交易就會像指數級擴散的波一樣在網路中傳播,直到所有連線到網路的節點都接收到它。

比特幣網路被設計為能高效且靈活地傳遞交易和區塊至所有節點的模式,因而比特幣網路能抵禦入侵。為了避免垃圾資訊的濫發、拒絕服務攻擊或其他針對比特幣系統的惡意攻擊,每一個節點在傳播每一筆交易之前均進行獨立驗證。 一個異常交易所能到達的節點不會超過一個。"8.3 交易的獨立校驗"一節將詳細介紹決定比特幣交易是否有效的原則。

5.3 交易結構

一筆比特幣交易是一個含有輸入值和輸出值的資料結構,該資料結構植入了將一筆資金從初始點(輸入值)轉移至目標地址(輸出值)的程式碼資訊。比特幣交易的輸入值和輸出值與賬號或者身份資訊無關。你應該將它們理解成一種被特定祕密資訊鎖定的一定數量的比特幣。只有擁有者或知曉這個祕密資訊的人可以解鎖。一筆比特幣交易包含一些欄位,如表5-1所示。

表5-1 交易結構

大小欄位描述
4位元組版本明確這筆交易參照的規則
1-9位元組輸入數量被包含的輸入的數量
不定輸入一個或多個交易輸入
1-9位元組輸出數量被包含的輸出的數量
不定輸出一個或多個交易輸出
4位元組時鐘時間一個UNIX時間戳或區塊號

交易的鎖定時間

鎖定時間定義了能被加到區塊鏈裡的最早的交易時間。在大多數交易裡,它被設定成0,用來表示立即執行。如果鎖定時間不是0並且小於5億,就被視為區塊高度,意指在這個指定的區塊高度之前,該交易沒有被包含在區塊鏈裡。如果鎖定時間大於5億,則它被當作是一個Unix紀元時間戳(從1970年1月1日以來的秒數),並且在這個指定時點之前,該交易沒有被包含在區塊鏈裡。鎖定時間的使用相當於將一張紙質支票的生效時間予以後延。

5.4 交易的輸出和輸入

比特幣交易的基本單位是未經使用的一個交易輸出,簡稱UTXO。UTXO是不能再分割、被所有者鎖住或記錄於區塊鏈中的並被整個網路識別成貨幣單位的一定量的比特幣貨幣。比特幣網路監測著以百萬為單位的所有可用的(未花費的)UTXO。當一個使用者接收比特幣時,金額被當作UTXO記錄到區塊鏈裡。這樣,一個使用者的比特幣會被當作UTXO分散到數百個交易和數百個區塊中。實際上,並不存在儲存比特幣地址或賬戶餘額的地點,只有被所有者鎖住的、分散的UTXO。“一個使用者的比特幣餘額”,這個概念是一個通過比特幣錢包應用建立的派生之物。比特幣錢包通過掃描區塊鏈並聚合所有屬於該使用者的UTXO來計算該使用者的餘額。


在比特幣的世界裡既沒有賬戶,也沒有餘額,只有分散到區塊鏈裡的UTXO。

一個UTXO可以是一“聰”的任意倍。就像美元可以被分割成表示兩位小數的“分”一樣,比特幣可以被分割成表示八位小數的“聰”。儘管UTXO可以是任意值,但只要它被創造出來了,就像不能被切成兩半的硬幣一樣不可再分了。如果一個UTXO比一筆交易所需量大,它仍會被當作一個整體而消耗掉,但同時會在交易中生成零頭。例如,你有20比特幣的UTXO並且想支付1比特幣,那麼你的交易必須消耗掉整個20比特幣的UTXO並且產生兩個輸出:一個是支付了1比特幣給接收人,另一個是支付19比特幣的找零到你的錢包。這樣的話,大部分比特幣交易都會產生找零。

想象一下,一位顧客要買1.5元的飲料。她掏出她的錢包並努力從所有硬幣和鈔票中找出一種組合來湊齊她要支付的1.5元。如果可能的話,她會選剛剛好的零錢(比如一張1元紙幣和5個一毛硬幣)或者是小面額的組合(比如3個五毛硬幣)。如果都不行的話,她會用一張大面額的鈔票,比如5元紙幣。如果她把過多的錢,比如5元,給了商店老闆,她會拿到3.5元的找零,並把找零放回她的錢包以供未來使用。

類似的,一筆比特幣交易可以有任意數值,但必須從使用者可用的UTXO中建立出來。使用者不能再把UTXO進一步細分,就像不能把一元紙幣撕開而繼續當貨幣使用一樣。使用者的錢包應用通常會從使用者可用的UTXO中選取多個可用的個體來拼湊出一個大於或等於一筆交易所需的比特幣量。

就像現實生活中一樣,比特幣應用可以使用一些策略來滿足付款需要:組合若干小的個體,算出準確的找零;或者使用一個比交易值大的個體然後進行找零。所有這些複雜的、由可支付的UTXO完成的組合,都是由使用者的錢包自動完成,並不為使用者所見。只有當你以程式設計方式用UTXO來構建原始交易時,這些才與你有關。

被交易消耗的UTXO被稱為交易輸入,由交易建立的UTXO被稱為交易輸出。通過這種方式,一定量的比特幣價值在不同所有者之間轉移,並在交易鏈中消耗和建立UTXO。一筆比特幣交易通過使用所有者的簽名來解鎖UTXO,並通過使用新的所有者的比特幣地址來鎖定並建立UTXO。

對於輸出和輸入鏈來說,有一個例外,它是一種特殊的交易型別,稱為Coinbase交易。這是每個區塊中的首個交易。這種交易存在的原因是作為對挖礦的獎勵而產生全新的可用於支付的比特幣給“贏家”礦工。這也就是為什麼比特幣可以在挖礦過程中被創造出來,我們將在第8章中進行詳述。


輸入和輸出,哪一個是先產生的呢?先有雞還是先有蛋呢?嚴格來講,先產生輸出,因為可以創造新比特幣的coinbase交易沒有輸入,但它可以無中生有地產生輸出。

5.4.1 交易輸出

每一筆比特幣交易創造輸出,輸出都會被比特幣賬簿記錄下來。除特例之外(見“5.7.4 資料輸出(OP_RETURN操作符)”),幾乎所有的輸出都能創造一定數量的可用於支付的比特幣,也就是UTXO。這些UTXO被整個網路識別,並且所有者可在未來的交易中使用它們。給某人傳送比特幣實際上是創造新的UTXO,註冊到那個人的地址,並且能被他用於新的支付。

UTXO被每一個全節點比特幣客戶端在一個儲存於記憶體中的資料庫所追蹤,該資料庫也被稱為“UTXO集”或者“UTXO池”。新的交易從UTXO集中消耗(支付)一個或多個輸出。

交易輸出包含兩部分:

▷ 一定量的比特幣,被命名為“聰”,是最小的比特幣單位; 
一個鎖定指令碼,也被當作是“障礙”,提出支付輸出所必須被滿足的條件以“鎖住”這筆總額。

在前面的鎖定指令碼中提到的這個交易指令碼語言會在後面121頁的“交易指令碼和指令碼語言”一節中詳細討論。表5-2列出了交易輸出的結構。

表5-2 交易輸出結構

尺寸欄位說明
8個位元組總量用聰表示的比特幣值(10-8比特幣)
1–9個位元組(可變整數)鎖定指令碼尺寸用位元組表示的後面的鎖定指令碼長度
變長鎖定指令碼一個定義了支付輸出所需條件的指令碼

在例5-1中,我們使用blockchain.info應用程式介面來查詢特定地址的UTXO。

例5-1 一個呼叫blockchain.info應用程式介面來查詢與一個地址有關的UTXO的指令碼

# 從blockchain API中得到未花費的輸出

import json
import requests

# 樣例地址
address = '1Dorian4RoXcnBv9hnQ4Y2C1an6NJ4UrjX'

# API網址是:https://blockchain.info/unspent?active=<address>
# 它返回一個JSON物件,其中包括一個包含著UTXO的“unspent_outputs”列表,就像這樣:
#{      "unspent_outputs":[
    #{ 
#       tx_hash":"ebadfaa92f1fd29e2fe296eda702c48bd11ffd52313e986e99ddad9084062167",
#      "tx_index":51919767,
#      "tx_output_n": 1,
#      "script":"76a9148c7e252f8d64b0b6e313985915110fcfefcf4a2d88ac",
#      "value": 8000000,
#      "value_hex": "7a1200",
#      "confirmations":28691
#   },
# ...
#]} 

resp = requests.get('https://blockchain.info/unspent?active=%s' % address)
utxo_set = json.loads(resp.text)["unspent_outputs"]

for utxo in utxo_set:
    print "%s:%d - %ld Satoshis" % (utxo['tx_hash'], utxo['tx_output_n'], utxo['val 
ue'])

執行指令碼,我們將會得到“交易ID,冒號,特定UTXO的索引號,以及這個UTXO包含的聰的數額”的列表。在例5-2中,鎖定指令碼被省略了。

例5-2 執行 get-utxo.py指令碼

$ python get-utxo.py
ebadfaa92f1fd29e2fe296eda702c48bd11ffd52313e986e99ddad9084062167:1 - 8000000 Satoshis
6596fd070679de96e405d52b51b8e1d644029108ec4cbfe451454486796a1ecf:0 - 16050000 Satoshis
74d788804e2aae10891d72753d1520da1206e6f4f20481cc1555b7f2cb44aca0:0 - 5000000 Satoshis
b2affea89ff82557c60d635a2a3137b8f88f12ecec85082f7d0a1f82ee203ac4:0 - 10000000 Satoshis
...

支付條件(障礙)

交易輸出把用聰表示的一定數量的比特幣,和特定的定義了支付輸出所必須被滿足的條件的障礙,或者叫鎖定指令碼,關聯到了一起。在大多數情況下,鎖定指令碼會把輸出鎖在一個特定的比特幣地址上,從而把一定數量的比特幣的所有權轉移到新的所有者上。當Alice在Bob的咖啡店為一杯咖啡付款時,Alice的交易創造了0.015比特幣的輸出,在咖啡店的比特幣地址上成為一種障礙,或者說是被鎖在了咖啡店的比特幣地址上。那0.015比特幣輸出被記錄到區塊鏈中,並且成為UTXO的一部分,也就是作為可用餘額出現在Bob的錢包裡。當Bob選擇使用這筆款項進行支付時,他的交易會釋放障礙,通過提供一個包含Bob私鑰的解鎖指令碼來解鎖輸出。

5.4.2 交易輸入

簡單地說,交易輸入是指向UTXO的指標。它們指向特定的UTXO,並被交易雜湊和在區塊鏈中記錄UTXO的序列號作為參考。若想支付UTXO,一個交易的輸入也需要包含一個解鎖指令碼,用來滿足UTXO的支付條件。解鎖指令碼通常是一個簽名,用來證明對於在鎖定指令碼中的比特幣地址擁有所有權。

當使用者付款時,他的錢包通過選擇可用的UTXO來構造一筆交易。比如說,要支付0.015比特幣,錢包應用會選擇一個0.01 UTXO和一個0.005 UTXO,使用它們加在一起來得到想要的付款額。

在例5-3中,我們展示了一種貪心演算法來為了得到特定的付款額而選擇可用的UTXO。在例中,可用的UTXO被提供在一個常數陣列中。但在實際中,可用的UTXO被一個遠端過程呼叫比特幣核心,或者被一個如例5-1中的第三方應用程式介面,來檢索出來。

例5-3 一個計算會被髮送的比特幣總量的指令碼

# 使用貪心演算法從UTXO列表中選擇輸出。

from sys import argv 

class OutputInfo: 

    def __init__(self, tx_hash, tx_index, value): 
        self.tx_hash = tx_hash
        self.tx_index = tx_index
        self.value = value 

    def __repr__(self):
        return "<%s:%s with %s Satoshis>" % (self.tx_hash, self.tx_index, 
                                            self.value) 

# 為了傳送,從未花費的輸出列表中選出最優輸出。
# 返回輸出列表,並且把其他的改動傳送到改變地址。
def select_outputs_greedy(unspent, min_value): 
    # 如果是空的話認為是失敗了。
    if not unspent: return None 
    # 分割成兩個列表。
    lessers = [utxo for utxo in unspent if utxo.value < min_value]     greaters = [utxo for utxo in unspent if utxo.value >= min_value] 
    key_func = lambda utxo: utxo.value
    if greaters: 
        # 非空。尋找最小的greater。
        min_greater = min(greaters)
        change = min_greater.value - min_value 
        return [min_greater], change 
    # 沒有找到greaters。重新嘗試若干更小的。
    # 從大到小排序。我們需要儘可能地使用最小的輸入量。
    lessers.sort(key=key_func, reverse=True)
    result = []
    accum = 0
    for utxo in lessers: 
        result.append(utxo)
        accum += utxo.value
        if accum >= min_value: 
            change = accum - min_value
            return result, "Change: %d Satoshis" % change 
    # 沒有找到。
    return None, 0 

def main(): 
    unspent = [ 
        OutputInfo("ebad
faa92f1fd29e2fe296eda702c48bd11ffd52313e986e99ddad9084062167", 1,  8000000),
        OutputIn
fo("6596fd070679de96e405d52b51b8e1d644029108ec4cbfe451454486796a1ecf", 0, 16050000),
        OutputInfo("b2af
fea89ff82557c60d635a2a3137b8f88f12ecec85082f7d0a1f82ee203ac4", 0,  10000000),
        OutputIn
fo("7dbc497969c7475e45d952c4a872e213fb15d45e5cd3473c386a71a1b0c136a1", 0, 25000000),
        OutputIn
fo("55ea01bd7e9afd3d3ab9790199e777d62a0709cf0725e80a7350fdb22d7b8ec6", 17, 5470541),
        OutputIn
fo("12b6a7934c1df821945ee9ee3b3326d07ca7a65fd6416ea44ce8c3db0c078c64", 0, 10000000),
        OutputIn
fo("7f42eda67921ee92eae5f79bd37c68c9cb859b899ce70dba68c48338857b7818", 0, 16100000),
    ] 

    if len(argv) > 1:
        target = long(argv[1]) 
    else:
        target = 55000000 

    print "For transaction amount %d Satoshis (%f bitcoin) use: " % (target, target/ 10.0**8) 
    print select_outputs_greedy(unspent, target) 

if __name__ == "__main__": 
    main()

如果我們不使用引數執行select-utxo.py指令碼,它會試圖為一筆五千五百萬聰(0.55比特幣)的付款構造一組UTXO。如果你提供一個指定的付款額作為引數,指令碼會選擇UTXO來完成指定的付款額。在例5-4中,我們執行指令碼來試著完成一筆0.5比特幣,或者說是五千萬聰的付款。

例5-4 執行select-utxo.py

$ python select-utxo.py 50000000
For transaction amount 50000000 Satoshis (0.500000 bitcoin) use:
([<7dbc497969c7475e45d952c4a872e213fb15d45e5cd3473c386a71a1b0c136a1:0 with 25000000
Satoshis>, <7f42eda67921ee92eae5f79bd37c68c9cb859b899ce70dba68c48338857b7818:0 with 16100000 Satoshis>,
<6596fd070679de96e405d52b51b8e1d644029108ec4cbfe451454486796a1ecf:0 with 16050000 Satoshis>], 'Change: 7150000 Satoshis')

一旦UTXO被選中,錢包會為每個UTXO生成包含簽名的解鎖指令碼,由此讓它們變得可以通過滿足鎖定指令碼的條件來被支付。錢包把這些UTXO作為參考,並且連同解鎖指令碼一起作為輸入加到交易中。表5-3展示了交易輸入的結構。

表5-3 交易輸入的結構

尺寸欄位說明
32個位元組交易指向交易包含的被花費的UTXO的雜湊指標
4個位元組輸出索引被花費的UTXO的索引號,第一個是0
1–9個位元組(可變整數)解鎖指令碼尺寸用位元組表示的後面的解鎖指令碼長度
變長解鎖指令碼一個達到UTXO鎖定指令碼中的條件的指令碼
4個位元組序列號目前未被使用的交易替換功能,設成0xFFFFFFFF

序列號是用來覆蓋在交易鎖定時間之前失效的交易,這是一專案前沒有在比特幣中用到的功能。大多數交易把這個值設定成最大的整數(0xFFFFFFFF)並且被比特幣網路忽略。如果一次交易有非零的鎖定時間,那麼它至少需要有一個序列號比0xFFFFFFFF低的輸入來啟用鎖定時間。

5.4.3 交易費

大多數交易包含交易費,這是為了在網路安全方面給比特幣礦工一種補償。在第8章中,對於挖礦、費用和礦工得到的獎勵,有更詳細的討論。這一節解釋交易費是如何被包含在日常交易中的。大多數錢包自動計算並計入交易費。但是,如果你程式設計構造交易,或者使用命令列介面,你必須手動計算並計入這些費用。

交易費可當作是為了包含(挖礦)一筆交易到下一個區塊中的一種鼓勵,也可當作是對於欺詐交易和任何種類的系統濫用,在每一筆交易上通過徵收一筆小成本的稅而造成的一種妨礙。交易費被挖出這個區塊的礦工得到,並且記錄在這個交易的區塊鏈中。

交易費基於交易的尺寸,用千位元組來計算,而不是比特幣的價值。總的來說,交易費基於市場所設定,生效於比特幣網路中。礦工依據許多不同的標準,按重要性對交易進行排序,這包括費用,並且甚至可能在某種特定情況下免費處理交易。交易費影響處理優先順序,這意味著有足夠費用的交易會更可能地被包含在下一個挖出的區塊中;與此同時,交易費不足或者沒有交易費的交易可能會被推遲,基於盡力而為的原則在幾個區塊之後被處理,甚至可能根本不被處理。交易費不是強制的,而且沒有交易費的交易也許最終會被處理,但是,包含交易費將提高處理優先順序。

隨著時間的過去,交易費的計算方式和交易費在交易優先順序上的影響一直在發展。起初,交易費是網路中的一個固定常數。漸漸地,交易費的結構被放寬了,以便被市場基於網路容量和交易量而強制影響。目前最小交易費被固定在每千位元組0.0001比特幣,或者說是每千位元組萬分之一比特幣,最近一次改變是從千分之一比特幣減少到這個數值的。大多數交易少於一千位元組,但是那些包含多個輸入和輸出的交易尺寸可能更大。在未來的比特幣協議修訂版中,錢包應用預計會使用統計學分析,基於最近的幾筆交易的平均費用,來計算最恰當的費用並附在交易上。

目前礦工使用的,對包含在一個區塊中的交易,基於它們的費用劃分優先順序的演算法,在第8章有詳細解釋。

5.4.4 把交易費加到交易中

交易的資料結構沒有交易費的欄位。相反地,交易費通過所有輸入的總和,以及所有輸出的總和之間的差來表示。從所有輸入中扣掉所有輸出之後的多餘的量會被礦工收集走。

交易費被作為輸入減輸出的餘量:

交易費 = 求和(所有輸入) - 求和(所有輸出)

對於交易來說,這是一個很讓人摸不著頭腦的元素,但又是很重要的問題。因為如果你要構造你自己的交易,你必須確認你沒有疏忽地包含了一筆少於輸入的、量非常大的費用。這意味著你必須計算所有的輸入,如果必要的話進行找零,不然的話,結果就是你給了礦工一筆可觀的勞動費!

舉例來說,如果你消耗了一個20比特幣的UTXO來完成1比特幣的付款,你必須包含一筆19比特幣的找零回到你的錢包。否則,那剩下的19比特幣會被當作交易費,並且會被挖出你的交易到一個區塊中的礦工收走。儘管你會受到高優先順序的處理,並且讓一個礦工喜出望外,但這很可能不是你想要的。


如果你忘記了在手動構造的交易中增加找零的輸出,系統會把找零當作交易費來處理。“不用找了!”也許不是你想要的結果。

讓我們來看看在實際中它如何工作,重溫一下Alice在咖啡店的交易。Alice想為咖啡支付0.015比特幣。為了確保這筆交易能立即被處理,Alice想支付一筆交易費,比如說0.001。這意味著總交易成本會變成0.016。因此她的錢包需要湊齊0.016或更多的UTXO。如果需要,還要加上找零。我們假設他的錢包有一個0.2比特幣的UTXO可用。他的錢包就會消耗掉這個UTXO,創造一個新的0.015的輸出給Bob的咖啡店,另一個0.184比特幣的輸出作為找零回到Alice擁有的錢包,並留下未分配的0.001比特幣內含在交易中。

現在讓我們換個例子。Eugenia,我們在菲律賓的兒童募捐專案主管,完成了一次為孩子購買教材的籌款活動。她在世界範圍內接收到了好幾千個小數額的捐款,總額是50比特幣。所以她的錢包塞滿了非常小的UTXO。現在她想用比特幣從本地的一家出版商購買幾百本的教材。

現在Eugenia的錢包應用想要構造一個單筆大額付款交易,它必須從可用的、由很多小數額構成的大的UTXO集合中尋求錢幣來源。這意味著交易的結果是從上百個小數額的UTXO中作為輸入,但只有一個輸出用來付給出版商。輸入數量這麼巨大的交易會比一千位元組要大,也許總尺寸會達到兩至三千位元組。結果是它需要更高的交易費來滿足0.0001比特幣的網路費。

Eugenia的錢包應用會通過測量交易的大小,乘以每千位元組需要的交易費,來計算適當的交易費。很多錢包會通過多付交易費的方式來確保大交易被立即處理。高交易費不是因為Eugenia付的錢很多,而是因為她的交易很複雜並且尺寸很大——交易費是與參加交易的比特幣值無關的。

5.5 交易鏈條和孤立交易

正如我們之前所看到的那樣,交易形成一條鏈,這條鏈的形式是一筆交易消耗了先前的交易(父交易)的輸出,併為隨後的交易(子交易)創造了輸出。有的時候組成整個鏈條的所有交易依賴於他們自己——比如父交易、子交易和孫交易——而他們又被同時創造出來,來滿足複雜交易的工作流程。這需要在一個交易的父交易被簽名之前,有一個合法的子交易被簽名。舉個例子,這是CoinJoin交易使用的一項技術,這項技術可以讓多方同時加入交易,從而保護他們的隱私。

當一條交易鏈被整個網路傳送時,他們並不能總是按照相同的順序到達目的地。有時,子交易在父交易之前到達。在這種情況下,節點會首先收到一個子交易,而不能找到他參考的父交易。節點不會立即拋棄這個子交易,而是放到一個臨時池中,並等著接收它的父交易,與此同時廣播這個子交易給其他節點。沒有父交易的交易池被稱作孤立交易池。一旦接收到了父交易,所有與這個父交易建立的UTXO有關的孤塊會從池中釋放出來,遞迴地重新驗證,然後整條交易鏈就會被交易池包括進去,並等待著被區塊所挖走。交易鏈可以是任意長度並且可以被任意數量的批次同時傳走。在孤立池中保留孤塊的機制保證了其他合法的交易不會只是因為父交易被耽誤了而被拋棄,並且無論接收順序,最終整個鏈會以正確的順序重新構造出來。

記憶體中儲存的孤立交易數量是有限制的,這是為了防止針對比特幣節點的拒絕服務攻擊(DoS)。這個限制被定義在比特幣涉及到的客戶端的原始碼中的MAX_ORPHAN_TRANSACTIONS。如果池中的孤立交易數量達到了MAX_ORPHAN_TRANSACTIONS,一個或多個的、被隨機選出的孤立交易會被池拋棄,直到池的大小回到限制以內。

5.6 比特幣交易指令碼和指令碼語言

比特幣客戶端通過執行一個用類Forth指令碼語言編寫的指令碼驗證比特幣交易。鎖定指令碼被寫入UTXO,同時它往往包含一個用同種指令碼語言編寫的簽名。當一筆比特幣交易被驗證時,每一個輸入值中的解鎖指令碼被與其對應的鎖定指令碼同時(互不干擾地)執行,從而檢視這筆交易是否滿足使用條件。

如今,大多數經比特幣網路處理的交易是以“Alice付給Bob”的形式存在的。同時,它們是以一種稱為“P2PKH”(Pay-to-Public-Key-Hash)指令碼為基礎的。然而,通過使用指令碼來鎖定輸出和解鎖輸入意味著通過使用程式語言,比特幣交易可以包含無限數量的條件。當然,比特幣交易並不限於“Alice付給Bob” 的形式和模式。

這只是這個指令碼語言可以表達的可能性的冰山一角。在這一節,我們將會全面展示比特幣交易指令碼語言的各個組成部分;同時,我們也會演示如何使用它去表達複雜的使用條件以及解鎖指令碼如何去滿足這些花費條件。


比特幣交易驗證並不基於一個不變的模式,而是通過執行指令碼語言來實現。這種語言可以表達出多到數不盡的條件變種。這也是比特幣作為一種“可程式設計的貨幣”所擁有的權力。

5.6.1 指令碼建立(鎖定與解鎖)

比特幣的交易驗證引擎依賴於兩類指令碼來驗證比特幣交易:一個鎖定指令碼和一個解鎖指令碼。

鎖定指令碼是一個放在一個輸出值上的“障礙”,同時它明確了今後花費這筆輸出的條件。由於鎖定指令碼往往含有一個公鑰(即比特幣地址),在歷史上它曾被稱作一個指令碼公鑰程式碼。由於認識到這種指令碼技術存在著更為寬泛的可能性,在本書中,我們將它稱為一個“鎖定指令碼”。在大多數比特幣應用原始碼中,指令碼公鑰程式碼便是我們所說的鎖定指令碼。

解鎖指令碼是一個“解決”或滿足被鎖定指令碼在一個輸出上設定的花費條件的指令碼,同時它將允許輸出被消費。解鎖指令碼是每一筆比特幣交易輸出的一部分,而且往往含有一個被使用者的比特幣錢包(通過使用者的私鑰)生成的數字簽名。由於解鎖指令碼常常包含一個數字簽名,因此它曾被稱作ScriptSig。在大多數比特幣應用的原始碼中,ScriptSig便是我們所說的解鎖指令碼。考慮到更寬泛的鎖定指令碼要求,在本書中,我們將它稱為“解鎖指令碼”。但並非所有解鎖指令碼都一定會包含簽名。

每一個比特幣客戶端會通過同時執行鎖定和解鎖指令碼來驗證一筆交易。對於比特幣交易中的每一個輸入,驗證軟體會先檢索輸入所指向的UTXO。這個UTXO包含一個定義了花費條件的鎖定指令碼。接下來,驗證軟體會讀取試圖花費這個UTXO的輸入中所包含的解鎖指令碼,並執行這兩個指令碼。

在先前的比特幣客戶端中,解鎖和鎖定指令碼是以連鎖的形式存在的,並且是被依次執行的。出於安全因素考慮,在2010年比特幣開發者們修改了這個特性——因為存在“允許異常解鎖指令碼推送資料入棧並且汙染鎖定指令碼”的漏洞。在當今的比特幣世界中,這兩個指令碼是隨著堆疊的傳遞被分別執行的,後續將會詳細介紹。

首先,使用堆疊執行引擎執行解鎖指令碼。如果解鎖指令碼在執行過程中未報錯(沒有懸空操作符),主堆疊(非其它堆疊)將被複制,然後指令碼將被執行。如果採用從解鎖指令碼處複製而來的資料執行鎖定指令碼的結果為真,那麼解鎖指令碼就成功地滿足了鎖定指令碼所設定的條件,因而,該輸入是一個能使用該UTXO的有效授權。如果在執行完組合指令碼後的結果不是真,那麼輸入就不是有效的,因為它並未能滿足UTXO中所設定的使用該筆資金的條件。注意,UTXO是永久性地記錄在區塊鏈中的,因此它不會因一筆新交易所發起的無效嘗試而變化或受影響。只有一筆有效的能準確滿足UTXO條件的交易才會導致UTXO被標記為“已使用”,然後從有效的(未使用)UTXO集中所移除。

圖5-1是最為常見型別的比特幣交易(向公鑰雜湊進行一筆支付)的解鎖和鎖定指令碼樣本,該樣本展示了在指令碼驗證之前將解鎖指令碼和鎖定指令碼串聯而成的組合指令碼。


圖5-1

5.6.2 指令碼語言

比特幣交易指令碼語言,也稱為指令碼,是一種基於逆波蘭表示法的基於堆疊的執行語言。如果這讓您聽起來似乎在胡言亂語,很有可能是您沒學習過1960年的程式語言的緣故。指令碼是一種非常簡單的語言,這種語言被設計為能在有限的硬體上執行,這些硬體類似簡單的嵌入式裝置,如手持計算器。它僅需最少的處理即可,而且不能做許多現代程式語言可以做的事情。當涉及可程式設計的錢時,這是它的一個基於深思熟慮的安全特性。

比特幣指令碼語言被稱為基於棧語言,因為它使用的資料結構被稱為棧。棧是一個非常簡單的資料結構,它可以被理解成為一堆卡片。棧允許兩類操作:入棧和出棧。入棧是在棧頂部增加一個專案,出棧則是從棧頂部移除一個專案。

指令碼語言通過從左至右地處理每個專案的方式執行指令碼。數字(常數)被推送至堆疊,操作符向堆疊推送(或移除)一個或多個引數,對它們進行處理,甚至可能會向堆疊推送一個結果。例如,OP_ADD將從堆疊移除兩個專案,將二者相加,然後再將二者相加之和推送到堆疊。

條件操作符評估一項條件,產生一個真或假的結果。例如,OP_EQUAL從堆疊移除兩個專案,假如二者相等則推送真(表示為1),假如二者不等則推送為假(表示為0)。比特幣交易指令碼常含條件操作符,當一筆交易有效時,就會產生真的結果。


圖5-2

在圖5-2中,指令碼“2 3 OP_ADD 5 OP_EQUAL”演示了算術加法操作符OP_ADD,該操作符將兩個數字相加,然後把結果推送到堆疊,OP_EQUAL是驗算之前的兩數之和是否等於5。為了簡化起見,字首OP_在一步步的演示示例過程中將被省略。

以下是一個稍微有些複雜的指令碼,它用於計算2+7-3+1。注意,當指令碼包含多個操作符時,堆疊允許一個操作符的結果作用於下一個操作符。

2 7 OP_ADD 3 OP_SUB 1 OP_ADD 7 OP_EQUAL

請試著用紙筆自行演算指令碼,當指令碼執行完畢時,你會在堆疊得到正確的結果。

雖然大多數的解鎖指令碼都指向一個比特幣地址或公鑰,因而如果想要使用資金則需驗證所有權,但指令碼本身並不需要如此的複雜。任何解鎖和鎖定指令碼的組合如果結果為真,則為有效。前面被我們用於說明指令碼語言的簡單算術運算同樣也是一個有效的鎖定指令碼,該指令碼能用於鎖定交易輸出。

使用部分算數運算示例指令碼作用鎖定指令碼:

3 OP_ADD 5 OP_EQUAL

該指令碼能被以解鎖指令碼為輸入的一筆交易所滿足,解鎖指令碼為:

2

驗證軟體將鎖定和解鎖指令碼組合起來:

2 3 OP_ADD 5 OP_EQUAL

正如在圖5-2中所看到的,當指令碼被執行時,結果是OP_TRUE,從而使得交易有效。不僅該筆交易的輸出鎖定指令碼有效,同時UTXO也能被任何知曉這個運算技巧(知道是數字2)的人所使用。


如果堆疊頂部的結果顯示為真(標記為{0×01}),即為任何非零值或指令碼執行後堆疊為空情形,則交易有效。如果堆疊頂部的結果顯示為假(0位元組空值,標記為{})或指令碼執行被操作符禁止,如OP_VERIFY、OP_RETURN,或有條件終止如OP_ENDIF,則交易無效。詳見附錄1。

5.6.3 圖靈非完備性

比特幣指令碼語言包含許多操作,但都故意限定為一種重要的方式——沒有迴圈或者複雜流控制功能以外的其他條件的流控制。這樣就保證了指令碼語言的圖靈非完備性,這意味著指令碼的複雜性有限,交易可執行的次數也可預見。指令碼並不是一種通用語言,施加的這些限制確保該語言不被用於創造無限迴圈或其它型別的邏輯炸彈,這樣的炸彈可以植入在一筆交易中,通過引起拒絕服務的方式攻擊比特幣網路。受限制的語言能防止交易啟用機制被人當作薄弱環節而加以利用。

5.6.4 非主權驗證

比特幣交易指令碼語言是無國家主權的,沒有國家能凌駕於指令碼之上,也沒有國家會在指令碼被執行後對其進行儲存。所以需要執行指令碼的所有資訊都已包含在指令碼中。可以預見的是,一個指令碼能在任何系統上以相同的方式執行。如果您的系統對一個指令碼進行驗證,可以確信的是每一個比特幣網路中的其他系統也將對其進行驗證,這意味著一個有效的交易對每個人而言都是有效的,而且每一個人都明白這一點。這種對於結果的可預見性是比特幣系統的一項重要良性特徵。

5.7 標準交易

在比特幣最初幾年的發展過程中,開發者對可以經由客戶端進行操作的指令碼型別設定了一些限制。這些限制被編譯為一個Standard()函式,該函式定義了五種型別的標準交易。這些限制都是臨時性的,當您閱讀本書時或許已經更新。截至目前,五種標準交易指令碼是僅有的被客戶端和大多數執行客戶端的礦工們所接受的指令碼。雖然創設一個非標準交易(指令碼型別非標準化)是有可能的,但前提是必須能找到一個不遵循標準而且能將該非標準交易納入區塊的礦工。

通過檢索比特幣核心客戶端原始碼,可以看到當前有哪些交易指令碼是被認可的。

五大標準指令碼分別為P2PKH、P2PK、MS(限15個金鑰)、P2SH和OP_Return,後文將詳細介紹這五大指令碼。

5.7.1 P2PKH(Pay-to-Public-Key-Hash)

比特幣網路上的大多數交易都是P2PKH交易,此類交易都含有一個鎖定指令碼,該指令碼由公鑰雜湊實現阻止輸出功能,公鑰雜湊即為廣為人知的比特幣地址。由P2PKH指令碼鎖定的輸出可以通過鍵入公鑰和由相應私鑰創設的數字簽名得以解鎖。

例如,我們可以再次回顧一下Alice向Bob咖啡館支付的案例。Alice下達了向Bob咖啡館的比特幣地址支付0.015比特幣的支付指令,該筆交易的輸出內容為以下形式的鎖定指令碼:

OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUAL OP_CHECKSIG

指令碼中的Cafe Public Key Hash即為咖啡館的比特幣地址,但這個地址不是基於Base58Check編碼的。事實上,大多數比特幣地址都顯示為十六進位制碼,而不是大家所熟知的以1開頭的基於Bsase58Check編碼的比特幣地址。

鎖定指令碼的解鎖版指令碼是:

<Cafe Signature> <Cafe Public Key>

將兩個指令碼結合起來可以形成如下有效組合指令碼:

<Cafe Signature> <Cafe Public Key> OP_DUP OP_HASH160
<Cafe Public Key Hash> OP_EQUAL OP_CHECKSIG

只有當解鎖版指令碼與鎖定版指令碼的設定條件相匹配時,執行組合有效指令碼時才會顯示結果為真(Ture)。即只有當解鎖指令碼得到了咖啡館的有效簽名,交易執行結果才會被通過(結果為真),該有效簽名是從與公鑰雜湊相匹配的咖啡館的私鑰中所獲取的。

圖5-3和圖5-4(分兩部分)顯示了組合指令碼一步步檢驗交易有效性的過程。


圖5-3


圖5-4

5.7.2 P2PK(Pay-to-Public-Key)

與P2PKH相比,P2PK模式更為簡單。與P2PKH模式含有公鑰雜湊的模式不同,在P2PK指令碼模式中,公鑰本身已經儲存在鎖定指令碼中,而且程式碼長度也更短。P2PKH是由Satoshi建立的,主要目的一方面為使比特幣地址更簡短,另一方面也使之更方便使用。P2PK目前在Coinbase交易中最為常見,Coinbase交易由老的採礦軟體產生,目前還沒更新至P2PKH。

P2PK鎖定版指令碼形式如下:

<Public Key A> OP_CHECKSIG

用於解鎖的指令碼是一個簡單簽名:

<Signature from Private Key A>

經由交易驗證軟體確認的組合指令碼為:

<Signature from Private Key A> <Public Key A> OP_CHECKSIG

該指令碼只是CHECKSIG操作符的簡單呼叫,該操作主要是為了驗證簽名是否正確,如果正確,則返回為真(Ture)。

5.7.3 多重簽名

多重簽名指令碼設定了這樣一個條件,假如記錄在指令碼中的公鑰個數為N,則至少需提供其中的M個公鑰才可以解鎖。這也被稱為M-N組合,其中,N是記錄在指令碼中的公鑰總個數,M是使得多重簽名生效的公鑰數閥值(最少數目)。例如,對於一個2-3多重簽名組合而言,存檔公鑰數為3個,至少同時使用其中2個或者2個以上的公鑰時,才能生成啟用交易的簽名,通過驗證後才可使用這筆資金。最初,標準多重簽名指令碼的最大存檔公鑰數被限定為15個,這意味著可採用1-1乃至15-15的任意多重簽名組合,或者組合的組合來啟用交易。15個存檔公鑰數的限制也許在本書出版時已有所增加,讀者通過檢索Standard()函式可以獲得最新存檔公鑰數上限值的相關資訊。

通用的M-N多重簽名鎖定指令碼形式為:

M <Public Key 1> <Public Key 2> ... <Public Key N> N OP_CHECKMULTISIG

其中,N是存檔公鑰總數,M是要求啟用交易的最少公鑰數。

2-3多重簽名條件:

2 <Public Key A> <Public Key B> <Public Key C> 3 OP_CHECKMULTISIG

上述鎖定指令碼可由含有簽名和公鑰的指令碼予以解鎖:

OP_0 <Signature B> <Signature C>

或者由3個存檔公鑰中的任意2個相一致的私鑰簽名組合予以解鎖。

之所以要加上字首OP_0,是因為最早的CHECKMULTISIG在處理含有多個專案的過程中有個小漏洞,CHECKMULTISIG會自動忽略這個字首,它只是佔位符而已。

兩個指令碼組合將形成一個驗證指令碼:

OP_0 <Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 OP_CHECKMULTISIG

當執行時,只有當未解鎖版指令碼與解鎖指令碼設定條件相匹配時,組合指令碼才顯示得到結果為真(Ture)。上述例子中相應的設定條件即為未解鎖指令碼是否含有與3個公鑰中的任意2個相一致的私鑰的有效簽名。

5.7.4 資料輸出(OP_RETURN操作符)

比特幣的分發和時間戳賬戶機制(也即區塊鏈),其潛在運用將大大超越支付領域。許多開發者試圖充分發揮交易指令碼語言的安全性和可恢復性優勢,將其運用於電子公證服務、證券認證和智慧協議等領域。比特幣指令碼語言的早期運用主要包括在區塊鏈上創造出交易輸出。例如,為檔案記錄電子指紋,則任何人都可以通過該機制在特定的日期建立關於文件存在性的證明。

運用比特幣區塊鏈儲存與比特幣支付不相關資料的做法是一個有爭議的話題。許多開發者認為其有濫用的嫌疑,因而試圖予以阻止。另一些開發者則將之視為區塊鏈技術強大功能的有力證明,從而試圖給予大力支援。那些反對非支付相關應用的開發者認為這樣做將引致“區塊鏈膨脹”,因為所有的區塊鏈節點都將以消耗磁碟儲存空間為成本,負擔儲存此類資料的任務。更為嚴重的是,此類交易僅將比特幣地址當作自由組合的20個位元組而使用,進而會產生不能用於交易的UTXO。因為比特幣地址只是被當作資料使用,並不與私鑰相匹配,所以會導致UTXO不能被用於交易,因而是一種偽支付行為。這樣的做法將使得記憶體中的UTXO不斷增加,而且這些不能被用於交易的資料同樣也不能被移除,因此比特幣節點將永久性地擔負這些資料,這無疑是代價高昂的。

在0.9版的比特幣核心客戶端上,通過採用OP_Return操作符最終實現了妥協。OP_Return允許開發者在交易輸出上增加40位元組的非交易資料。然後,與偽交易型的UTXO不同,OP_Return創造了一種明確的可複查的非交易型輸出,此類資料無需儲存於UTXO集。OP_Return輸出被記錄在區塊鏈上,它們會消耗磁碟空間,也會導致區塊鏈規模的增加,但它們不儲存在UTXO集中,因此也不會使得UTXO記憶體膨脹,更不會以消耗代價高昂的記憶體為代價使全節點都不堪重負。

OP_RETURN指令碼的樣式:

OP_RETURN <data>

“data”部分被限制為40位元組,且多以雜湊方式呈現,如32位元組的SHA256演算法輸出。許多應用都在其前面加上字首以輔助認定。例如,電子公正服務的證明材料採用8個位元組的字首“DOCPROOF”,在十六進位制演算法中,相應的ASCII碼為44f4350524f4f46

請記住OP_RETURN不涉及可用於支付的解鎖指令碼的特點,OP_RETURN不能使用其輸出中所鎖定的資金,因此它也就沒有必要記錄在蘊含潛在成本的UTXO集中,所以OP_RETURN實際是沒有成本的。OP_RETURN常為一個金額為0的比特幣輸出,因為任何與該輸出相對應的比特幣都會永久消失。假如一筆OP_RETURN遇到指令碼驗證軟體,它將立即導致驗證指令碼和標記交易的行為無效。如果你碰巧將OP_RETURN的輸出作為另一筆交易的輸入,則該交易是無效的。

一筆標準交易(通過了isStandard()函式檢驗的)只能有一個OP_RETURN輸出。但是單個OP_RETURN輸出能與任意型別的輸出交易進行組合。

5.7.5 P2SH(Pay-to-Script-Hash)

P2SH在2012年被作為一種新型、強大、且能大大簡化複雜交易指令碼的交易型別而引入。為進一步解釋P2SH的必要性,讓我們先看一個實際的例子。

在第1章中,我們曾介紹過Mohammed,一個杜拜的電子產品進口商。Mohammed的公司採用比特幣多重簽名作為其公司會計賬簿記賬要求。多重簽名指令碼是比特幣高階指令碼最為常見的運用之一,是一種具有相當大影響力的指令碼。針對所有的顧客支付(即應收賬款),Mohammed的公司要求採用多重簽名交易。基於多重簽名機制,顧客的任何支付都需要至少兩個簽名才能解鎖,一個來自Mohammed,另一個來自其合夥人或擁有備份鑰匙的代理人。這樣的多重簽名機制能為公司治理提供管控便利,同時也能有效防範盜竊、挪用和遺失。

最終的指令碼非常長:

2 <Mohammed's Public Key> <Partner1 Public Key> <Partner2 Public Key> <Partner3 Public Key> <Attorney Public Key> 5 OP_CHECKMULTISIG

雖然多重簽名十分強大,但其使用起來還是多有不便。基於之前的指令碼,Mohammed必須在客戶付款前將該指令碼傳送給每一位客戶,而每一位顧客也必須使用特製的能產生客戶交易指令碼的比特幣錢包軟體,每位顧客還得學會如何利用指令碼來完成交易。此外,由於指令碼可能包含特別長的公鑰,最終的交易指令碼可能是最初交易指令碼長度的5倍之多。額外長度的指令碼將給客戶造成費用負擔。最後,一個長的交易指令碼將一直記錄在所有節點的隨機儲存器的UTXO集中,直到該筆資金被使用。所有這些都使得在實際交易中採用複雜輸出指令碼顯得困難重重。

P2SH正是為了解決這一實際難題而被引入的,它旨在使複雜指令碼的運用能與直接向比特幣地址支付一樣簡單。在P2SH支付中,複雜的鎖定指令碼被電子指紋所取代,電子指紋為密碼學雜湊。當一筆交易試圖支付UTXO時,要解鎖支付指令碼,它必須含有與雜湊相匹配的指令碼。P2SH的含義是,向與該雜湊匹配的指令碼支付,當輸出被支付時,該指令碼將在後續呈現。

在P2SH交易中,鎖定指令碼由雜湊取代,雜湊指代的是贖回指令碼。因為它在系統中是在贖回時出現而不是以鎖定指令碼模式出現。表5-4列示了非P2SH指令碼,表5-5列示了P2SH指令碼。

表5-4 不含P2SH的複雜指令碼

Locking Script2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 OP_CHECKMULTISIG
Unlocking ScriptSig1 Sig2

表5-5 P2SH複雜指令碼

RedeemScript 2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 OP_CHECKMULTISIG
Locking ScriptOP_HASH160 <20-byte hash of redeem script> OP_EQUAL
Unlocking ScriptSig1 Sig2 redeem script

正如你在表中所看到的,在P2SH中,出現了花費該筆支出(贖回指令碼)條件的複雜指令碼,而這在鎖定指令碼中並未出現。取而代之,在鎖定指令碼中,只出現了雜湊,而贖回指令碼則在稍後輸出被支付時才作為解鎖指令碼的一部分而出現。

讓我們再看下Mohammed公司的例子,複雜的多重簽名指令碼和相應的P2SH指令碼。

首先,Mohammed公司對所有顧客訂單採用多重簽名指令碼:

2 <Mohammed's Public Key> <Partner1 Public Key> <Partner2 Public Key> <Partner3 Public Key> <Attorney Public Key> 5 OP_CHECKMULTISIG

如果佔位符由實際的公鑰(以04開頭的520位元組)替代,你將會看到的指令碼會非常地長:

2
04C16B8698A9ABF84250A7C3EA7EE-
DEF9897D1C8C6ADF47F06CF73370D74DCCA01CDCA79DCC5C395D7EEC6984D83F1F50C900A24DD47F569FD4193AF5DE762C58704A2192968D8655D6A935BEAF2CA23E3FB87A3495E7AF308EDF08DAC3C1FCBFC2C75B4B0F4D0B1B70CD2423657738C0C2B1D5CE65C97D78D0E34224858008E8B49047E63248B75DB7379BE9CDA8CE5751D16485F431E46117B9D0C1837C9D5737812F393DA7D4420D7E1A9162F0279CFC10F1E8E8F3020DECDBC3C0DD389D99779650421D65CBD7149B255382ED7F78E946580657EE6FDA162A187543A9D85BAAA93A4AB3A8F044DA-
DA618D087227440645ABE8A35DA8C5B73997AD343BE5C2AFD94A5043752580AFA1EC-
ED3C68D446BCAB69AC0A7DF50D56231BE0AABF1FDEEC78A6A45E394BA29A1EDF518C022DD618DA774D207D137AAB59E0B000EB7ED238F4D800 5 OP_CHECKMULTISIG

整個指令碼都可由僅為20個位元組的密碼雜湊所取代,首先採用SH256雜湊演算法,隨後對其運用RIPEMD160演算法。20位元組的指令碼為:

54c557e07dde5bb6cb791c7a540e0a4796f5e97

一筆P2SH交易運用鎖定指令碼將輸出與雜湊關聯,而不是與前面特別長的指令碼所關聯。使用的鎖定指令碼為:

OP_HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e OP_EQUAL

正如你所看到的,這個指令碼比前面的長指令碼簡短多了。取代“向該5個多重簽名指令碼支付”,這個P2SH等同於“向含該雜湊的指令碼支付”。顧客在向Mohammed公司支付時,只需在其支付指令中納入這個非常簡短的鎖定指令碼即可。當Mohammed想要花費這筆UTXO時,附上原始贖回指令碼(與UTXO鎖定的雜湊)和必要的解鎖簽名即可,如:

<Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG>

兩個指令碼經由兩步實現組合。首先,將贖回指令碼與鎖定指令碼比對以確認其與雜湊是否匹配:

<2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG> OP_HASH160 <redeem scriptHash> OP_EQUAL

假如贖回指令碼與雜湊匹配,解鎖指令碼會被執行以釋放贖回指令碼:

<Sig1> <Sig2> 2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG

5.7.5.1 P2SH地址

P2SH的另一重要特徵是它能將指令碼雜湊編譯為一個地址(其定義請見BIP0013)。P2SH地址是基於Base58編碼的一個含有20個位元組雜湊的指令碼,就像比特幣地址是基於Base58編碼的一個含有20個位元組的公鑰。由於P2SH地址採用5作為字首,這導致基於Base58編碼的地址以“3”開頭。例如,Mohammed的指令碼,基於Base58編碼下的P2SH地址變為“39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw”。此時,Mohammed可以將該地址傳送給他的客戶,這些客戶可以採用任何的比特幣錢包實現簡單支付,就像這是一個比特幣地址一樣。以“3”為字首給予客戶這是一種特殊型別的地址的暗示,該地址與一個指令碼相對應而非與一個公鑰相對應,但是它的效果與比特幣地址支付別無二致。

P2SH地址隱藏了所有的複雜性,因此,運用其進行支付的人將不會看到指令碼。

5.7.5.2 P2SH的優點

與直接使用複雜指令碼以鎖定輸出的方式相比,P2SH具有以下特點:

▷ 在交易輸出中,複雜指令碼由簡短電子指紋取代,使得交易程式碼變短。 
▷ 指令碼能被編譯為地址,支付指令的發出者和支付者的比特幣錢包不需要複雜工序就可以執行P2SH。
▷ P2SH將構建指令碼的重擔轉移至接收方,而非傳送方。 
▷ P2SH將長指令碼資料儲存的負擔從輸出方(儲存於UTXO集,影響記憶體)轉移至輸入方(僅儲存於區塊鏈)。 
▷ P2SH將長指令碼資料儲存的重擔從當前(支付時)轉移至未來(花費時)。 
▷ P2SH將長指令碼的交易費成本從傳送方轉移至接收方,接收方在使用該筆資金時必須含有贖回指令碼。

5.7.5.3 贖回指令碼和標準確認

在0.9.2版比特幣核心客戶端之前,P2SH僅限於標準比特幣交易指令碼型別(即通過標準函式檢驗的指令碼)。這也意味著使用該筆資金的交易中的贖回指令碼只能是標準化的P2PK、P2PKH或者多重簽名,而非OP_RETURN和P2SH。

作為0.9.2版的比特幣核心客戶端,P2SH交易能包含任意有效的指令碼,這使得P2SH標準更為靈活,也可以用於多種新的或複雜型別的交易。

請記住不能將P2SH植入P2SH贖回指令碼,因為P2SH不能自迴圈。也不能在贖回指令碼中使用OP_RETURN,因為OP_RETURN的定義即顯示不能贖回。

需要注意的是,因為贖回指令碼只有在你試圖傳送一個P2SH輸出時才會在比特幣網路中出現,假如你將輸出與一個無效的交易雜湊鎖定,則它將會被忽略。你將不能使用該筆資金,因為交易中含有贖回指令碼,該指令碼因是一個無效的指令碼而不能被接受。這樣的處理機制也衍生出一個風險,你能將比特幣鎖定在一個未來不能被花費的P2SH中。因為比特幣網路本身會接受這一P2SH,即便它與無效的贖回指令碼所對應(因為該贖回指令碼雜湊沒有對其所表徵的指令碼給出指令)。

P2SH鎖定指令碼包含一個贖回指令碼雜湊,該指令碼對於贖回指令碼本身未提供任何描述。P2SH交易即便在贖回指令碼無效的情況下也會被認為有效。你可能會偶然地將比特幣以這樣一種未來不能被花費的方式予以鎖定。

相關文章