官網原文:本章原文 建議打開原版對照著英文版同時閱讀。
官網原文:在git上閱讀 建議打開原版對照著英文版同時閱讀。
互動留言: 上報修改建議 當然你也可以直接在github修改。
回到目錄
中文共享閱讀:https://github.com/gdwwl/MasterBitcoin2CN
7.1介紹
在上一章中,我們介紹了比特幣交易的基本元素,并且查看了最常見的交易腳本類型,即P2PKH腳本。在本章中,我們將介紹更高級的腳本,以及如何使用它來構建具有復雜條件的交易。
首先,我們將看看多重簽名腳本。接下來,我們將檢查第二個最常見的交易腳本Pay-to-Script-Hash,它打開了一個復雜腳本的整個世界。然后,我們將檢查新的腳本操作符,通過時間鎖定將比特幣添加時間維度。
7.2多重簽名
多重簽名腳本設置了一個條件,其中N個公鑰被記錄在腳本中,并且至少有M個必須提供簽名來解鎖資金。這也稱為M-N方案,其中N是密鑰的總數,M是驗證所需的簽名的數量。例如,2/3的多重簽名是三個公鑰被列為潛在簽名人,/其中至少有兩個必須用于為有效的交易創建簽名才能花費資金/改為:至少有2個有效的簽名才能花費資金/。此時,標準多重簽名腳本限制在最多15個列出的公鑰,這意味著您可以從1到15之間的多重簽名或該范圍內的任何組合執行任何操作。在本書發布之前,限制15個已列出的/鍵/可能會被解除,因此請檢查isStandard()函數以查看當前網絡接受的內容。
設置M-N多重簽名條件的鎖定腳本的一般形式是:
M <Public Key 1> <Public Key 2> ... <Public Key N> N CHECKMULTISIG
/其中N是列出的公鑰的總數,M是花費輸出所需的簽名的數量。改為:
M是花費輸出所需的簽名的數量,N是列出的公鑰的總數。/
設置2到3多重簽名條件的鎖定腳本如下所示:
2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
上述鎖定腳本可由含有簽名和公鑰的腳本予以解鎖:
<Signature B> <Signature C>
或者由3個存檔公鑰中的任意2個相一致的私鑰簽名組合予以解鎖。
兩個腳本組合將形成一個驗證腳本:
<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
當執行時,只有當未解鎖版腳本與解鎖腳本設置條件相匹配時,組合腳本才顯示得到結果為真(Ture)。上述例子中相應的設置條件即為:未解鎖腳本是否含有3個公鑰中的任意2個/相一致的 改為相對應/私鑰的有效簽名。
CHECKMULTISIG執行中的bug
CHECKMULTISIG的執行中有一個bug,需要一些輕微的解決方法。 當CHECKMULTISIG執行時,它應該消耗 堆棧(stack) 上的M + N + 2個項目作為參數。 然而,由于該錯誤,CHECKMULTISIG將彈出(pop)超出預期的額外值或一個值。
我們來看看這個更詳細的/使用以前的/驗證示例:
<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
首先,CHECKMULTISIG彈出最上面的項目,這是N(在這個例子中N是“3”)。然后它彈出N個項目,這是可以簽名的公鑰。在這個例子中,公鑰A,B和C.然后,它彈出一個項目,即M,仲裁(需要多少個簽名)。這里M = 2。此時,CHECKMULTISIG應彈出最終的M個項目,這些是簽名,并查看它們是否有效。然而,不幸的是,實施中的錯誤導致CHECKMULTISIG再彈出一個項目(總共M + 1個)。檢查簽名時,不考慮額外的項目,因此它對CHECKMULTISIG本身沒有直接影響。但是,必須存在額外的值,因為如果不存在,則當CHECKMULTISIG嘗試彈出空堆棧時,會導致堆棧錯誤和腳本失敗(將交易標記為無效)。因為額外的項目被忽略,它可以是任何東西,但通常使用0。
因為這個bug成為共識規則的一部分,所以現在它必須永遠被復制。因此,正確的腳本驗證將如下所示:
0 <Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
這樣解鎖腳本就不是下面的:
<Signature B> <Signature C>
而是:
0 <Signature B> <Signature C>
從現在開始,如果你看到一個multisig解鎖腳本,你應該期望看到一個額外的0開始,其唯一的目的是解決一個bug,意外地成為一個共識規則的解決方法。
//增加解釋 譯者注:即保證例子中有3個私鑰簽名(其中2有效簽名,其中1個為0的無效簽名)對應3個公鑰用于檢查多重簽名,從而保證腳本不產生bug。/
7.3 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_C HECKMULTISIG
雖然多重簽名十分強大,但其使用起來還是多有不便。基于之前的腳本,Mohammed必須在客戶付款前將該腳本發送給每一位客戶,而每一位顧客也必須使用特制的能產生客戶交易腳本的比特幣錢包軟件,每位顧客還得學會如何利用腳本來完成交易。此外,由于腳本可能包含特別長的公鑰,最終的交易腳本可能是最初交易腳本長度的5倍之多。額外長度的腳本將給客戶造成費用負擔。最后,一個長的交易腳本將一直記錄在所有節點的隨機存儲器的UTXO集中,直到該筆資金被使用。/所有這些都使得在實際交易中采用復雜輸出腳本從而顯得困難重重。/改為:采用這種復雜輸出腳本使得在實際交易中變得困難重重。/
P2SH正是為了解決這一實際難題而被引入的,它旨在使復雜腳本的運用能與直接向比特幣地址支付一樣簡單。在P2SH 支付中,復雜的鎖定腳本被電子指紋所取代,/電子指紋為密碼學哈希 改為電子指紋是指密碼學中的哈希值/。當一筆交易試圖支付UTXO時,要解鎖支付腳本,它必須含有與哈希相匹配的腳本。P2SH的含義是,向與該哈希匹配的腳本支付,當輸出被支付時,該腳本將在后續呈現。
在P2SH交易中,鎖定腳本由哈希/改為:哈希運算后的20字節的散列值/ 取代,哈希指代的是贖回腳本/ 建議對照原文再譯/。因為它在系統中是在贖回時出現而不是以鎖定腳本模式出現。表7-1列示了非P2SH腳本,表7-2列示了P2SH腳本。
表7-1不含P2SH的復雜腳本
從表中可以看出,對于P2SH,詳細描述了輸出(贖回腳本)的條件的復雜腳本不會在鎖定腳本中顯示。 相反,只有它的散列 /散列值 在鎖定腳本中 /增加:呈現/,并且兌換腳本本身稍后呈現 /去掉這/,作為解鎖腳本在輸出花費時的一部分。 這會將收費和復雜性的負擔從發送方轉移到/收款人(花費) / 改為:這使得給礦工的交易費用從發送方轉移到收款方,復雜的計算工作也從從發送方轉移到收款方。
//整段用中國人能讀懂的方法重譯:
輸出腳本(Redeem Script)中的“2 Pubkey1 Pubkey2 Pubkey3 Pubkey4 Pubkey5 5 CHECKMULTISIG”的內容,沒有出現在鎖定腳本(Locking Script 表中第二行內容)中,但對實現上很長的一大串的“2 Pubkey1 Pubkey2 Pubkey3 Pubkey4 Pubkey5 5 CHECKMULTISIG”(有520字節)進行哈希運算后的20字節的散列值取代之,然后將之(“2 Pubkey1 Pubkey2 Pubkey3 Pubkey4 Pubkey5 5 CHECKMULTISIG”)放到解鎖腳本中(Unlocking Script)。這使得給礦工的交易費用從發送方轉移到收款方,并且令復雜的計算工作也從從發送方轉移到收款方。
【譯者注:本段原文描述如下:As you can see from the tables, with P2SH the complex script that details the conditions for spending the output (redeem script) is not presented in the locking script. Instead, only a hash of it is in the locking script and the redeem script itself is presented later, as part of the unlocking script when the output is spent. This shifts the burden in fees and complexity from the sender to the recipient (spender) of the transaction.】
讓我們再看下Mohammed公司的例子,復雜的多重簽名腳本和相應的P2SH腳本。
首先,Mohammed公司對所有顧客訂單采用多重簽名腳本:
2 <Mohammed's Public Key> <Partner1 Public Key> <Partner2 Public Key> <Partner3 Public Key> <Attorney Public Key> 5 CHECKMULTISIG
如果占位符由實際的公鑰(以04開頭的520字節)替代,你將會看到的腳本會非常地長:
2
04C16B8698A9ABF84250A7C3EA7EEDEF9897D1C8C6ADF47F06CF73370D74DCCA01CDCA79DCC5C395D7EEC6984D83F1F50C900A24DD47F569FD4193AF5DE762C58704A2192968D8655D6A935BEAF2CA23E3FB87A3495E7AF308EDF08DAC3C1FCBFC2C75B4B0F4D0B1B70CD2423657738C0C2B1D5CE65C97D78D0E34224858008E8B49047E63248B75DB7379BE9CDA8CE5751D16485F431E46117B9D0C1837C9D5737812F393DA7D4420D7E1A9162F0279CFC10F1E8E8F3020DECDBC3C0DD389D99779650421D65CBD7149B255382ED7F78E946580657EE6FDA162A187543A9D85BAAA93A4AB3A8F044DADA618D087227440645ABE8A35DA8C5B73997AD343BE5C2AFD94A5043752580AFA1ECED3C68D446BCAB69AC0BA7DF50D56231BE0AABF1FDEEC78A6A45E394BA29A1EDF518C022DD618DA774D207D137AAB59E0B000EB7ED238F4D800 5 CHECKMULTISIG
整個腳本都可由僅為20個字節的密碼哈希所取代,首先采用SH256哈希算法,隨后對其運用RIPEMD160算法。20字節 的腳本為:
54c557e07dde5bb6cb791c7a540e0a4796f5e97
一筆P2SH交易運用鎖定腳本將輸出與哈希關聯,而不是與前面特別長的腳本所關聯。使用的鎖定腳本為:
HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e EQUAL
正如你所看到的,這個腳本比前面的長腳本簡短多了。取代“向該5個多重簽名腳本支付”,這個P2SH等同于“向含該哈希的腳本支付”。顧客在向Mohammed公司支付時,只需在其支付指令中納入這個非常簡短的鎖定腳本即可。當 Mohammed想要花費這筆UTXO時,附上原始贖回腳本(與UTXO鎖定的哈希)和必要的解鎖簽名即可,如:
<Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG>
兩個腳本經由兩步實現組合。
首先,將贖回腳本與鎖定腳本比對以確認其與哈希是否匹配:
<2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG> HASH160 <redeem scriptHash> EQUAL
假如贖回腳本與哈希匹配,解鎖腳本會被執行以釋放贖回腳本:
<Sig1> <Sig2> 2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG
本章中描述的幾乎所有腳本只能以P2SH腳本來實現。 它們不能直接用在UTXO的鎖定腳本中。
7.3.1 P2SH地址
P2SH的另一重要特征是它能將腳本哈希編譯為一個地址(其定義請見BIP0013 /BIP-13)。P2SH地址是基于Base58編碼的一 個含有20個字節哈希的腳本,就像比特幣地址是基于Base58編碼的一個含有20個字節的公鑰。由于P2SH地址采用5作為前綴,這導致基于Base58編碼的地址以“3”開頭。例如,Mohammed的腳本,基于Base58編碼下的P2SH地址變 為“39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw”。此時,Mohammed可以將該地址發送給他的客戶,這些客戶可以 采用任何的比特幣錢包實現簡單支付,就像這是一個比特幣地址一樣。以“3”為前綴給予客戶這是一種特殊類型的地址的暗示,該地址與一個腳本相對應而非與一個公鑰相對應,但是它的效果與比特幣地址支付別無二致。
P2SH地址隱藏了所有的復雜性,因此,運用其進行支付的人將不會看到腳本。
7.3.2 P2SH的優點
與直接使用復雜腳本以鎖定輸出的方式相比,P2SH具有以下特點:
- 在交易輸出中,復雜腳本由簡短電子指紋取代,使得交易代碼變短。
- 腳本能被編譯為地址,支付指令的發出者和支付者的比特幣錢包不需要復雜工序就可以執行P2SH。
- P2SH將構建腳本的重擔轉移至接收方,而非發送方。
- P2SH將長腳本數據存儲的負擔從輸出方(存儲于UTXO集,影響內存)轉移至輸入方(存儲在區塊鏈里面)。
- P2SH將長腳本數據存儲的重擔從當前(支付時)轉移至未來(花費時)。
- P2SH將長腳本的交易費成本從發送方轉移至接收方,接收方在使用該筆資金時必須含有贖回腳本。
7.3.3贖回腳本和標準確認
在0.9.2版比特幣核心客戶端之前,P2SH僅限于標準比特幣交易腳本類型(即通過標準函數檢驗的腳本)。這也意味著使用該筆資金的交易中的贖回腳本只能是標準化的P2PK、P2PKH或者多重簽名,而非RETURN 和P2SH。
作為0.9.2版的比特幣核心客戶端,P2SH交易能包含任意有效的腳本,這使得P2SH標準更為靈活,也可以用于多種新的或復雜類型的交易。
請記住不能將P2SH植入P2SH贖回腳本,因為P2SH不能自循環。雖然在技術上可以將RETURN包含在贖回腳本中,但由于規則中沒有策略阻止您執行此操作,因此在驗證期間執行RETURN將導致交易被標記為無效,因此這是不實際的。
需要注意的是,因為贖回腳本只有在你試圖發送一個P2SH輸出時才會在比特幣網絡中出現,假如你將輸出與一個無效的交易哈希鎖定,則它將會被忽略。該UTXO將會被成功鎖定,但是你將不能使用該筆資金,因為交易中含有贖回腳本,該腳本因是一個無效的腳本而不能被接受。這樣的處理機制也衍生出一個風險,你可能將比特幣鎖定在一個未來不能被花費的P2SH中。因為比特幣網絡本身會接受這一P2SH,即便它與無效的贖回腳本所對應(因為該贖回腳本哈希沒有對其所表征的腳本給出指令)。
注意 | P2SH鎖定腳本包含一個贖回腳本哈希,該腳本對于贖回腳本本身未提供任何描述。P2SH交易即便在贖回腳本無效的情況下也會被認為有效。//你可能會偶然地將以鎖定比特幣以這樣一種未來不能被花費的方式予。改為:如果處理不當,有可能會出現一個事故,即你的比特幣可能會被鎖死在P2SH這個交易中,導致你以后再也不能花費這筆比特幣了。 |
---|
7.4 數據記錄輸出(RETURN操作符)
比特幣的/分發和時間戳賬戶機制(也即區塊鏈) 改為:比特幣的去中心特點和時間戳賬本機制,即區塊鏈技術/,其潛在運用將大大超越支付領域。許多開發者試圖充分發揮交易腳本語言的安全性和可恢復性優勢,將其運用于電子公證服務、證券認證和智能協議 /合約 等領域。/比特幣腳本語言的早期運用主 要包括在區塊鏈上創造出交易輸出。改為:很多早期的開發者利用比特幣這種能將交易數據放到區塊鏈上的技術進行了很多嘗試 /,例如,為文件記錄電子指紋,則任何人都可以通過該機制在特定的日期建立關于文檔存在性的證明。
運用比特幣的區塊鏈技術存儲與比特幣支付不相關數據的做法是一個有爭議的話題。許多開發者認為其有濫用的嫌疑,因而試圖予以阻止。另一些開發者則將之視為區塊鏈技術強大功能的有力證明,從而試圖給予大力支持。那些反對非支付相關應用的開發者認為這樣做將引致“區塊鏈膨脹”,因為所有的區塊鏈節點都將以消耗磁盤存儲空間為成本,負擔存儲此類 數據的任務。更為嚴重的是,此類交易僅將比特幣地址當作自由組合的20個字節而使用,進而會產生不能用于交易的UTXO。因為比特幣地址只是被當作數據使用,并不與私鑰相匹配,所以會導致UTXO不能被用于交易,因而是一種偽支付行為。因此,這些交易永遠不會被花費,所以永遠不會從UTXO集中刪除,并導致UTXO數據庫的大小永遠增加或“膨脹”。
在0.9版的比特幣核心客戶端上,通過采用Return操作符最終實現了妥協。Return允許開發者在交易輸出上增加80字節的非交易數據。然后,與偽交易型的UTXO不同,Return創造了一種明確的可復查的非交易型輸出,此類數據無需存儲于UTXO集。Return輸出被記錄在區塊鏈上,它們會消耗磁盤空間,也會導致區塊鏈規模的增加,但 它們不存儲在UTXO集中,因此也不會使得UTXO內存膨脹,更不會以消耗代價高昂的內存為代價使全節點都不堪重負。
RETURN 腳本的樣式:
RETURN <data>
“data”部分被限制為80字節,且多以哈希方式呈現,如32字節的SHA256算法輸出。許多應用都在其前面加上前綴以輔助認定。例如,電子公正服務的證明材料采用8個字節的前綴“DOCPROOF”,在十六進制算法中,相應的ASCII碼為 44 4f 43 50 52 4f 4f 46。
請記住 RETURN 不涉及可用于支付的解鎖腳本的特點, RETURN 不能使用其輸出中所鎖定的資金,因此它也就沒有必要記錄在蘊含潛在成本的UTXO集中,所以 RETURN 實際是沒有成本的。 RETURN 常為一個金額為0的比特幣輸出, 因為任何與該輸出相對應的比特幣都會永久消失。假如一筆 RETURN 被作為一筆交易的輸入,腳本驗證引擎將會阻止驗證腳本的執行,將標記交易為無效。如果你碰巧將 RETURN 的輸出作為另一筆交易的輸入,則該交易是無效的。
一筆標準交易(通過了 isStandard() 函數檢驗的)只能有一個 RETURN 輸出。但是單個RETURN 輸出能與任意類型的輸出交易進行組合。
Bitcoin Core中添加了兩個新版本的命令行選項。 選項datacarrier控制RETURN交易的中繼和挖掘,默認設置為“1”以允許它們。 選項datacarriersize采用一個數字參數,指定RETURN腳本的最大大小(以字節為單位),默認為83字節,允許最多80個字節的RETURN數據加上一個字節的RETURN操作碼和兩個字節的PUSHDATA操作碼。
注意: | 最初提出了RETURN,限制為80字節,但是當功能被釋放時,限制被減少到40字節。 2015年2月,在Bitcoin Core的0.10版本中,限制提高到80字節。 節點可以選擇不中繼或重新啟動RETURN,或者只能中繼和挖掘包含少于80字節數據的RETURN。 |
---|
7.5時間鎖(Timelocks)
時間鎖是只允許在一段時間后才允許支出的交易。比特幣從一開始就有一個交易級的時間鎖定功能。它由交易中的nLocktime字段實現。在2015年底和2016年中期推出了兩個新的時間鎖定功能,提供UTXO級別的時間鎖定功能。這些是CHECKLOCKTIMEVERIFY和CHECKSEQUENCEVERIFY。
時間鎖對于后期交易和將資金鎖定到將來的日期很有用。更重要的是,時間鎖將比特幣腳本擴展到時間的維度,為復雜的多級智能合同打開了大門。
7.5.1交易鎖定時間(nLocktime)
比特幣從一開始就有一個交易級的時間鎖功能。交易鎖定時間是交易級設置(交易數據結構中的一個字段),它定義交易有效的最早時間,并且可以在網絡上中繼或添加到區塊鏈中。鎖定時間也稱為nLocktime,是來自于Bitcoin Core代碼庫中使用的變量名稱。在大多數交易中將其設置為零,以指示即時傳播和執行。如果nLocktime不為零,低于5億,則將其解釋為塊高度,這意味著交易無效,并且在指定的塊高度之前未被中繼或包含在塊鏈中。如果超過5億,它被解釋為Unix紀元時間戳(自Jan-1-1970之后的秒數),并且交易在指定時間之前無效。指定未來塊或時間的nLocktime的交易必須由始發系統持有,并且只有在有效后才被發送到比特幣網絡。如果交易在指定的nLocktime之前傳輸到網絡,那么第一個節點就會拒絕該交易,并且不會被中繼到其他節點。使用nLocktime等同于一張延期支票。
7.5.1.1交易鎖定時間限制
nLocktime就是一個限制,雖然它可以在將來花費,但是到現在為止,它并不能使用它們。我們來解釋一下,下面的例子。
Alice簽署了一筆交易,支付給Bob的地址,并將交易nLocktime設定為3個月。Alice把這筆交易發送給Bob。有了這個交易,Alice和Bob知道:
- 在3個月過去之前,Bob不能完成交易進行變現。
- Bob可以在3個月后接受交易。
然而: - Alice可以創建另一個交易,雙重花費相同的輸入,而不需要鎖定時間。 因此,Alice可以在3個月過去之前花費相同的UTXO。
- Bob不能保證Alice不會這樣做。
了解交易nLocktime的限制很重要。 唯一的保證是Bob在3個月過去之前無法兌換它。 不能保證Bob得到資金。 為了實現這樣的保證,時間限制必須放在UTXO本身上,并成為鎖定腳本的一部分,而不是交易。 這是通過下一種形式的時間鎖定來實現的,稱為檢查鎖定時間驗證(CLTV)。
7.5.2檢查鎖定時間驗證Check Lock Time Verify (CLTV)
2015年12月,引入了一種新形式的時間鎖進行比特幣軟分叉升級。根據BIP-65中的規范,腳本語言添加了一個名為CHECKLOCKTIMEVERIFY(CLTV)的新腳本操作符。 CLTV是每個輸出的時間鎖定,而不是每個交易的時間鎖定,與nLocktime的情況一樣。這允許在應用時間鎖的方式上具有更大的靈活性。
簡單來說,通過在輸出的贖回腳本中添加CLTV操作碼來限制輸出,從而只能在指定的時間過后使用。
提示 | 當nLocktime是交易級時間鎖定時,CLTV是基于輸出的時間鎖。 |
---|
CLTV不會取代nLocktime,而是限制特定的UTXO,這樣他們只能在未來的交易中花費nLocktime設置為更大或相等的值 /改為:CLTV不會取代nLocktime,而是限制特定的UTXO,并通過將nLocktim設置為更大或相等的值,從而達到在未來才能花費這筆錢的目的。
CLTV操作碼采用一個參數作為輸入,表示為與nLocktime(塊高度或Unix紀元時間)相同格式的數字 。如VERIFY后綴所示,CLTV如果結果為FALSE,則停止執行腳本的操作碼類型。如果結果為TRUE,則繼續執行。
為了使用CLTV鎖定輸出,將其插入到創建輸出的交易中的輸出的贖回腳本中。例如,如果Alice支付Bob的地址,輸出通常會包含一個這樣的P2PKH腳本:
DUP HASH160 <Bob's Public Key Hash> EQUALVERIFY CHECKSIG
要鎖定一段時間,比如說3個月以后,交易將是一個P2SH交易,其中包含一個贖回腳本:
<now + 3 months> CHECKLOCKTIMEVERIFY DROP DUP HASH160 <Bob's Public Key Hash> EQUALVERIFY CHECKSIG
其中<now +3個月>是從交易開始被挖礦時間起計3個月的塊高度或時間值:當前塊高度+12,960(塊)或當前Unix紀元時間+7,760,000(秒)。現在,不要擔心CHECKLOCKTIMEVERIFY之后的DROP操作碼,下面很快就會解釋。
當Bob嘗試花費這個UTXO時,他構建了一個引用UTXO作為輸入的交易。他使用他的簽名和公鑰在該輸入的解鎖腳本,并將交易nLocktime設置為等于或更大于Alice設置的CHECKLOCKTIMEVERIFY 時間鎖。然后,Bob在比特幣網絡上廣播交易。
Bob的交易評估如下。如果Alice設置的CHECKLOCKTIMEVERIFY參數小于或等于支出交易的nLocktime,腳本執行將繼續(就好像執行“無操作”或NOP操作碼一樣)。否則,腳本執行停止,并且該交易被視為無效。
更確切地說,CHECKLOCKTIMEVERIFY失敗并停止執行,標記交易無效(來源:BIP-65):
- 堆棧是空的要么
- 堆棧中的頂部項小于0;要么
- 頂層堆棧項和nLocktime字段的鎖定時間類型(高度或者時間戳)不相同;要么
- 頂層堆棧項大于交易的nLocktime字段;要么
- 輸入的nSequence字段為0xffffffff。
注意 | CLTV和nLocktime使用相同的格式來描述時間鎖定,無論是塊高度還是自Unix紀元以秒鐘以來所經過的時間。 最重要的是,在一起使用時,nLocktime的格式必須與輸入中的CLTV格式相匹配,它們必須以秒為單位引用塊高度或時間。 |
---|
執行后,如果滿足CLTV,則其之前的時間參數仍然作為堆棧中的頂級項,并且可能需要使用DROP進行刪除,才能正確執行后續腳本操作碼。 為此,您將經常在腳本中看到CHECKLOCKTIMEVERIFY+DROP在一起使用。
通過將nLocktime與CLTV結合使用,交易鎖定時間限制中描述的情況發生變化。 因為Alice鎖定了UTXO本身,所以現在Bob或Alice在3個月的鎖定時間到期之前不可能花費它。
通過將時間鎖定功能直接引入到腳本語言中,CLTV允許我們開發一些非常有趣的復雜腳本。
該標準在BIP-65(CHECKLOCKTIMEVERIFY)中定義(附錄部分)。
7.5.3相對時間鎖
nLocktime和CLTV都是絕對時間鎖定,它們指定絕對時間點。接下來的兩個時間鎖定功能,我們將要考察的是相對時間鎖定,因為它們將消耗輸出的條件指定為從塊鏈接中的輸出確認起的經過時間。
相對時間鎖是有用的,因為它們允許將兩個或多個相互依賴的交易鏈接在一起,同時對依賴于從先前交易的確認所經過的時間的一個交易施加時間約束。換句話說,在UTXO被記錄在塊狀塊之前,時鐘不開始計數。這個功能在雙向狀態通道和閃電網絡中特別有用,我們將在后面章節[state_channels]中看到。
相對時間鎖,如絕對時間鎖定,同時具有交易級功能和腳本級操作碼。交易級相對時間鎖定是作為對每個交易輸入中設置的交易字段nSequence的值的共識規則實現的。腳本級相對時間鎖定使用CHECKSEQUENCEVERIFY(CSV)操作碼實現。
相對時間鎖是根據BIP-68的規范實現的,相對鎖定時間使用共同執行的序列號和BIP-112,CHECKSEQUENCEVERIFY。
/改為:相對時間鎖是根據BIP-68與BIP-112的規范共同實現的,其中BIP-68通過與相對時間鎖運用一致性增強的數字序列實現,BIP-112中是運用到了CHECKSEQUENCEVERIFY這個操作碼實現。
BIP-68和BIP-112在2016年5月被激活,作為軟叉升級到共識規則。
/改為 BIP-68和BIP-112是在2016年5月作為軟分叉升級時被激活的一個共識規則。
7.5.4 nSequence相對時間鎖
可以通過在每個輸入中設置nSequence字段來在交易的每個輸入上設置相對時間鎖定。
/上句改為: 相對時間鎖定可以在每個輸入中設置好,其方法是在每個輸入中加多一個nSequence字段。
7.5.4.1 nSequence的本義
nSequence字段最初是(但從未被正確實現的)允許在內存池中修改交易。/上句改為:nSequence字段的設計初心是想讓交易能在在內存中修改,可惜后面從未運用過,使用時,包含nSequence值低于2^32 (0xFFFFFFFF)的輸入的交易表示尚未“確定”的交易。
/原文:In that use, a transaction containing inputs with nSequence value below 232 (0xFFFFFFFF) indicated a transaction that was not yet "finalized."
/改為:使用nSequence這個字段時,如果輸入的交易的序列值小于2^32 (0xFFFFFFFF),就表示尚未“確定”的交易。
這樣的交易將在內存池中保存,直到被另一個交易消耗相同輸入并具有較大nSequence值的代替。一旦收到一個交易,其投入的nSequence值為2^32,那么它將被視為“最終確定”并開采。
nSequence的原始含義從未被正確實現,并且在不利用時間鎖定的交易中nSequence的值通常設置為232。對于具有nLocktime或CHECKLOCKTIMEVERIFY的交易,nSequence值必須設置為小于232,以使時間鎖定器有效。通常設置為2^32 - 1(0xFFFFFFFE)。
7.5.4.2 nSequence作為一個共同執行的相對時間鎖定
由于BIP-68的激活,新的共識規則適用于任何包含nSequence值小于2^31的輸入的交易(bit 1<<31 is not set)。以編程方式,這意味著如果沒有設置最高有效(bit 1<<31),它是一個表示“相對鎖定時間”的標志。否則(bit 1<<31set),nSequence值被保留用于其他用途,例如啟用CHECKLOCKTIMEVERIFY,nLocktime,Opt-In-Replace-By-Fee以及其他未來的開發 /新產品。
nSequence值小于2^31的交易輸入,就被解釋為具有相對時間鎖定。
/原文Transaction inputs with nSequence values less than 2^31 are interpreted as having a relative timelock.
/改為 一筆輸入交易,當輸入腳本中的nSequence值小于2^31時,就是相對時間鎖定的輸入交易。就是這種交易只有在輸入已經老化了相對時間鎖定量后才有效。/原文:Such a transaction is only valid once the input has aged by the relative timelock amount. 改為:這種交易只有到了相對鎖定時間后才生效。例如,具有30個區塊的nSequence相對時間鎖的一個輸入的交易只有在從輸入中引用的UTXO開始的時間起至少有30個塊時才有效。由于nSequence是每個輸入字段,因此交易可能包含任何數量的時間鎖定輸入,所有這些都必須具有足夠的時間以使交易有效。交易可以包括時間鎖定輸入(nSequence <2^31)和沒有相對時間鎖定(nSequence> = 2^31)的輸入。
nSequence值以塊或秒為單位指定,但與nLocktime中使用的格式略有不同。類型標志用于區分計數塊和計數時間(以秒為單位)的值。類型標志設置在第23個最低有效位(即值1 << 22)。如果設置了類型標志,則nSequence值將被解釋為512秒的倍數。如果未設置類型標志,則nSequence值被解釋為塊數。
當將nSequence解釋為相對時間鎖定時,只考慮16個最低有效位。一旦評估了標志(位32和23),nSequence值通常用16位掩碼(例如nSequence&0x0000FFFF)“屏蔽”。
下圖顯示由BIP-68定義的nSequence值的二進制布局。
[圖片上傳失敗...(image-bae7eb-1512023362597)]
Figure 1. BIP-68 definition of nSequence encoding (Source: BIP-68)
基于nSequence值的一致執行的相對時間鎖定在BIP-68中。標準定義在BIP-68, Relative lock-time using consensus-enforced sequence numbers.
7.5.5 帶CSV的相對時間鎖
就像CLTV和nLocktime一樣,有一個腳本操作碼用于相對時間鎖定,它利用腳本中的nSequence值。該操作碼是CHECKSEQUENCEVERIFY,通常簡稱為CSV。
在UTXO的贖回腳本中評估時,CSV操作碼僅允許在輸入nSequence值大于或等于CSV參數的交易中進行消耗。實質上,這限制了UTXO的消耗,直到UTXO開采時間過了一定數量的塊或秒。
與CLTV一樣,CSV中的值必須與相應nSequence值中的格式相匹配。如果CSV是根據塊指定的,那么nSequence也是如此。如果以秒為單位指定CSV,那么nSequence也是如此。
當幾個(已經形成鏈)交易被保留為“脫鏈”時,創建和簽名這幾個(已經形成鏈)交易但不傳播時,CSV的相對時間鎖特別有用。在父交易已被傳播,/挖掘和老化到指定的相對時間鎖定的時間之前,才能使用子交易。/改為:直到消耗完相對鎖定時間,才能使用子交易。這個用例的一個應用可以在state_channels和lightning_network /請加上鏈接 章節中看到。
CSV 細節參見 BIP-112, CHECKSEQUENCEVERIFY.
7.5.6中位時間過去Median-Time-Past
作為激活相對時間鎖定的一部分,時間鎖定(絕對和相對)的“時間”方式也發生了變化。在比特幣中,墻上時間(wall time)和共識時間之間存在微妙但非常顯著的差異。比特幣是一個分散的網絡,這意味著每個參與者都有自己的時間觀。網絡上的事件不會隨時隨地發生。網絡延遲必須考慮到每個節點的角度。最終,所有內容都被同步,以創建一個共同的分類帳。比特幣在過去存在的分類賬狀態中每10分鐘達成一個新的共識。
區塊頭中設置的時間戳由礦工設定。共識規則允許一定的緯度 /改為誤差 來解決分散節點之間時鐘精度的差異 /問題。然而,這給礦工造成了一個不幸的誘惑,即礦工在一段時間內就這么說謊,/然而,這誘惑了礦工去說謊,這以便通過包括還不成熟 /不在范圍內的時間交易來賺取額外費用 /礦工費。有關詳細信息,請參閱以下部分。
為了消除謊言和加強時間安全性的動機 /為了杜絕礦工說謊,加強時間安全性,在相對時間的BIPs的同時提出并激活了1個新的BIP /在相對時間鎖的基礎上又新增了一個BIP。這是BIP-113,它定義了一個稱為“中位時間過去 /(Median-Time-Past)”的新的共識測量機制。
通過取最后11個塊的時間戳并找到中位數來計算“中位時間過去”的值。/通過取最后11個塊的時間戳并計算其中位數作為“中位時間過去”的值。那個 /這個 中間時間 /值就變成了共識時間,并被用于所有的時間計算。過去約兩個小時的中間點,任何一個塊的時間戳的影響減小了。通過合并11個塊 /通過這個方法,沒有一個礦工可以利用時間戳從具有尚未成熟的時間段的交易中獲取非法礦工費。
Median-Time-Past更改了nLocktime,CLTV,nSequence和CSV的時間計算的實現。由Median-Time-Past計算的共識時間總是大約在掛鐘時間后一個小時。如果創建時間鎖交易,那么要在nLocktime,nSequence,CLTV和CSV中進行編碼的估計所需值時,應該考慮它。
Median-Time-Past細節參見BIP-113.
7.5.7針對費用狙擊(Fee Sniping)的時間鎖定
費用狙擊是一種理論攻擊情形,礦工試圖從將來的塊(挑選手續費較高的交易)重寫過去的塊,實現“狙擊”更高費用的交易,以最大限度地提高盈利能力。
例如,假設存在的最高塊是塊#100,000。如果不是試圖把#100,001號的礦區擴大到區塊鏈,那么一些礦工們會試圖重新挖礦#100,000。這些礦工可以選擇在候選塊#100,000中包括任何有效的交易(尚未開采)。他們不必使用相同的交易來恢復塊。事實上,他們有動力選擇最有利可圖(最高每kBB)的交易來包含在其中。它們可以包括處于“舊”塊#100,000中的任何交易,以及來自當前內存池的任何交易。當他們重新創建塊#100,000時,他們本質上可以將交易從“現在”提取到重寫的“過去”中。
今天,這種襲擊并不是非常有利可圖,因為回報獎勵(因為包括一定數量的比特幣獎勵)遠遠高于每個區塊的總費用。但在未來的某個時候,交易費將是獎勵的大部分(甚至是獎勵的整體)。那時候這種情況變得不可避免了。
為了防止“費用狙擊”,當Bitcoin Core /錢包 創建交易時,默認情況下,它使用nLocktime將它們限制為“下一個塊”。在我們的環境中,Bitcoin Core /錢包將在任何創建的交易上將nLocktime設置為100,001。在正常情況下,這個nLocktime沒有任何效果 - 交易只能包含在#100,001塊中,這是下一個區塊。
但是在區塊鏈分叉攻擊的情況下,由于所有這些交易都將被時間鎖阻止在#100,001,所以礦工們無法從籌碼中提取高額交易。他們只能在當時有效的任何交易中重新挖礦#100,000,這導致實質上不會獲得新的費用。
為了實現這一點,Bitcoin Core/錢包將所有新交易的nLocktime設置為<current block #+ 1>,并將所有輸入上的nSequence設置為0xFFFFFFFE以啟用nLocktime。
7.6具有流量控制的腳本(條件條款 (Conditional Clauses))
比特幣腳本的一個更強大的功能是流量控制,也稱為條件條款。您可能熟悉使用構造IF ... THEN ... ELSE的各種編程語言中的流控制。比特幣條件條款看起來有點不同,但是基本上是相同的結構。
在基本層面上,比特幣條件操作碼允許我們構建一個具有兩種解鎖方式的贖回腳本,這取決于評估邏輯條件的TRUE / FALSE結果。例如,如果x為TRUE,則贖回腳本為A,ELSE贖回腳本為B.
此外,比特幣條件表達式可以無限期地“嵌套”,這意味著這個條件語句可以包含其中的另外一個條件,另外一個條件其中包含別的條件等等 。Bitcoin腳本流控制可用于構造非常復雜的腳本,具有數百甚至數千個可能的執行路徑。嵌套沒有限制,但協商一致的規則對腳本的最大大小(以字節為單位)施加限制。
比特幣使用IF,ELSE,ENDIF和NOTIF操作碼實現流量控制。此外,條件表達式可以包含布爾運算符,如BOOLAND,BOOLOR和NOT。
乍看之下,您可能會發現比特幣的流量控制腳本令人困惑。那是因為比特幣腳本是一種堆棧語言。同樣的方式,當1+1看起來“向后”當表示為1 1 ADD時,比特幣中的流控制條款也看起來“向后”(backward)。
在大多數傳統(程序)編程語言中,流控制如下所示:
大多數編程語言中的流控制偽代碼
if (condition):
code to run when condition is true
else:
code to run when condition is false
code to run in either case
在基于堆棧的語言中,比如比特幣腳本,邏輯條件出現在IF之前,這使得它看起來像“向后”,如下所示:
Bitcoin腳本流控制
condition
IF
code to run when condition is true
ELSE
code to run when condition is false
ENDIF
code to run in either case
閱讀Bitcoin腳本時,請記住,評估的條件是在IF操作碼之前。
7.6.1條件條款與VERIFY操作碼
Bitcoin Script /比特幣腳本中的另一種條件是任何以VERIFY結尾的操作碼。 VERIFY后綴表示如果評估的條件不為TRUE,腳本的執行將立即終止,并且該交易被視為無效。
與提供替代執行路徑的IF子句不同,VERIFY后綴充當保護子句,只有在滿足前提條件的情況下才會繼續。
例如,以下腳本需要Bob的簽名和產生特定哈希的前置映像(秘密地) /? 。 解鎖時必須滿足這兩個條件:
1)具有EQUALVERIFY保護條款的贖回腳本。
HASH160 <expected hash> EQUALVERIFY <Bob's Pubkey> CHECKSIG
為了兌現這一點,Bob必須構建一個解鎖腳本,提供有效的前圖像和簽名:
2)一個解鎖腳本以滿足上述贖回腳本。
<Bob's Sig> <hash pre-image>
沒有呈現前圖像 /?,Bob無法訪問檢查其簽名的腳本部分。
該腳本可以用IF編寫:
具有IF保護條款的兌換腳本
HASH160 <expected hash> EQUAL
IF
<Bob's Pubkey> CHECKSIG
ENDIF
Bob的解鎖腳本是一樣的:
解鎖腳本以滿足上述兌換腳本
<Bob's Sig> <hash pre-image>
使用IF的腳本與使用具有VERIFY后綴的操作碼相同; 他們都作為保護條款。 然而,VERIFY的構造更有效率,使用較少的操作碼。
那么,我們什么時候使用VERIFY,什么時候使用IF? 如果我們想要做的是附加一個前提條件(保護條款),那么驗證是更好的。 然而,如果我們想要有多個執行路徑(流控制),那么我們需要一個IF ... ELSE流控制子句。
提示 | 諸如EQUAL之類的操作碼會將結果(TRUE / FALSE)推送到堆棧上,留下它用于后續操作碼的評估。 相比之下,操作碼EQUALVERIFY后綴不會在堆棧上留下任何東西。 在VERIFY中結束的操作碼不會將結果留在堆棧上。 |
---|
7.6.2在腳本中使用流控制
比特幣腳本中流量控制的一個非常常見的用途是構建一個提供多個執行路徑的贖回腳本,每個腳本都有一種不同的贖回UTXO的方式。
我們來看一個簡單的例子,我們有兩個簽名人,Alice和Bob,還有一個可以兌換。 使用多重簽名,這將被表示為1-of-2 多重簽名腳本。 為了示范,我們將使用IF子句做同樣的事情:
IF
<Alice's Pubkey> CHECKSIG
ELSE
<Bob's Pubkey> CHECKSIG
ENDIF
看這個贖回腳本,你可能會想:“條件在哪里?”IF子句之前沒有什么!“
條件不是贖回腳本的一部分。 相反,該解鎖腳本將提供該條件,允許Alice和Bob“選擇”他們想要的執行路徑。
Alice用解鎖腳本兌換了這個:
<Alice's Sig> 1
最后的1作為條件(TRUE),將使IF子句執行Alice具有簽名的第一個兌換路徑。
為了兌換這個Bob,他必須通過給IF子句賦一個FALSE值來選擇第二個執行路徑:
<Bob's Sig> 0
Bob的解鎖腳本在堆棧中放置一個0,導致IF子句執行第二個(ELSE)腳本,這需要Bob的簽名。
由于可以嵌套IF子句,所以我們可以創建一個“迷宮”的執行路徑。 解鎖腳本可以提供一個選擇執行路徑實際執行的“地圖”:
IF
script A
ELSE
IF
script B
ELSE
script C
ENDIF
ENDIF
在這種情況下,有三個執行路徑(腳本A,腳本B和腳本C)。 解鎖腳本以TRUE或FALSE值的形式提供路徑。 要選擇路徑腳本B,例如,解鎖腳本必須以1 0(TRUE,FALSE)結束。 這些值將被推送到堆棧,以便第二個值(FALSE)結束于堆棧的頂部。 外部IF子句彈出FALSE值并執行第一個ELSE子句。 然后,TRUE值移動到堆棧的頂部,并通過內部(嵌套)IF來評估,選擇B執行路徑。
使用這個結構,我們可以用數十或數百個執行路徑構建贖回腳本,每個腳本提供了一種不同的方式來兌換UTXO。 要花費,我們構建一個解鎖腳本,通過在每個流量控制點的堆棧上放置相應的TRUE和FALSE值來導航執行路徑。
7.7復雜的腳本示例
在本節中,我們將本章中的許多概念合并成一個例子。
我們的例子使用了迪拜公司所有者穆罕默德(Mohammed)的故事,他們正在經營進出口業務。
在這個例子中,穆罕默德希望用靈活的規則建立公司資本賬戶。他創建的方案需要不同級別的授權,具體取決于時間鎖定。 Multisig計劃的參與者是穆罕默德,他的兩個合作伙伴賽義德和扎拉,以及他們的公司律師阿卜杜勒。三個合作伙伴根據多數規則作出決定,因此三者中的兩個必須同意。然而,如果他們的鑰匙有問題,他們希望他們的律師能夠用三個合作伙伴簽名之一收回資金。最后,如果所有的合作伙伴一段時間都不可用或無行為能力,他們希望律師能夠直接管理該帳戶。
這是Mohammed設計的腳本:
具有時間鎖定(Timelock)的變量多重簽名
IF
IF
2
ELSE
<30 days> CHECKSEQUENCEVERIFY DROP
<Abdul the Lawyer's Pubkey> CHECKSIGVERIFY
1
ENDIF
<Mohammed's Pubkey> <Saeed's Pubkey> <Zaira's Pubkey> 3 CHECKMULTISIG
ELSE
<90 days> CHECKSEQUENCEVERIFY DROP
<Abdul the Lawyer's Pubkey> CHECKSIG
ENDIF
Mohammed的腳本使用嵌套的IF ... ELSE流控制子句來實現三個執行路徑。
在第一個執行路徑中,該腳本作為三個合作伙伴的簡單的2-of-3 multisig操作。 該執行路徑由第3行和第9行組成。第3行將multisig的定額設置為2(2 - 3)。 該執行路徑可以通過在解鎖腳本的末尾設置TRUE TRUE來選擇:
解鎖第一個執行路徑的腳本(2-of-3 multisig)
0 <Mohammed's Sig> <Zaira's Sig> TRUE TRUE
提示 | 此解鎖腳本開頭的0是因為CHECKMULTISIG中的錯誤從堆棧中彈出一個額外的值。 額外的值被CHECKMULTISIG忽略,但它必須存在或腳本失敗 /否則腳本簽名將失敗。 推送0(通常)是錯誤的解決方法 / 推送0(通常)是解決bug的方法,如CHECKMULTISIG執行中的錯誤章節所述。 |
---|
第二個執行路徑只能在UTXO創建30天后才能使用。 那時候,它需要簽署阿卜杜勒(Abdul)的律師和三個合作伙伴之一(三分之一)。 這是通過第7行實現的,該行將多選的法定人數設置為1。要選擇此執行路徑,解鎖腳本將以FALSE TRUE結束:
解鎖第二個執行路徑的腳本(Lawyer + 1-of-3)
0 <Saeed's Sig> <Abdul's Sig> FALSE TRUE
提示 | 為什么先FALSE后TRUE? 反了嗎?這是因為這兩個值被推到堆棧,所以先push FALSE,然后push TRUE。 因此,第一個IF操作碼首先彈出的是TRUE。 |
---|
最后,第三個執行路徑允許阿卜杜勒律師單獨花費資金,但只能在90天之后。 要選擇此執行路徑,解鎖腳本必須以FALSE結束:
解鎖第三個執行路徑的腳本(僅適用于律師)
<Abdul's Sig> FALSE
在紙上運行腳本來查看它在堆棧(stack)上的行為。
閱讀這個例子還需要考慮幾件事情。 看看你能找到答案嗎?
- 為什么律師可以隨時通過在解鎖腳本中選擇FALSE來兌換第三個執行路徑?
- 在UTXO開采后分別有多少個執行路徑可以使用5,35與105天?
- 如果律師失去鑰匙,資金是否流失? 如果91天過去了,你的答案是否會改變?
- 合作伙伴如何每隔29天或89天“重置”一次,以防止律師獲得資金?
- 為什么這個腳本中的一些CHECKSIG操作碼有VERIFY后綴,而其他的沒有?