區塊鏈入門 | 一文讀懂P2SH和P2WSH
P2SH和P2WSH是比特幣交易的高階指令碼,能夠構建複雜條件的智慧合約交易。
首先,我們將看看多重簽名指令碼。接下來,我們介紹最常見的交易指令碼P2SH,即Pay-to-Script-Hash支付給指令碼雜湊, 它打開了一個複雜指令碼的整個世界。最後我們介紹P2WSH,即Pay-to-Witness-Script-Hash支付給見證指令碼雜湊, P2WSH的結構性調整對比特幣交易產生了多方面的影響。
1 、多重簽名
多重簽名指令碼設定了一個條件,其中N 個公鑰被記錄在指令碼中,並且至少有M 個必須提供簽名來解鎖資金。這也稱為M-N 方案,其中N 是金鑰的總數,M 是驗證所需的簽名的數量。例如,2/3 的多重簽名是三個公鑰被列為潛在簽名人,至少有2 個有效的簽名才能花費資金。此時,標準多重簽名指令碼限制在最多15 個列出的公鑰,這意味著您可以從1 到15 之間的多重簽名或該範圍內的任何組合執行任何操作。在本書釋出之前,限制15 個已列出d 的金鑰可能會被解除,因此請檢查isStandard()函式以檢視當前網路接受的內容。
設定M-N 多重簽名條件的鎖定指令碼的一般形式是:
M <Public Key 1> <Public Key 2> ... <Public Key N> N CHECKMULTISIG
M 是花費輸出所需的簽名的數量,N 是列出的公鑰的總數。設定2 到3 多重簽名條件的鎖定指令碼如下所示:
2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
上述鎖定指令碼可由含有簽名和公鑰的指令碼予以解鎖: 或者由3 個存檔公鑰中的任意2 個相一致的私鑰簽名組合予以解鎖。兩個指令碼組合將形成一個驗證指令碼:
<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
當執行時,只有當未解鎖版指令碼與解鎖指令碼設定條件相匹配時,組合指令碼才顯示得到結果為真(Ture)。
上述例子中相應的設定條件即為:未解鎖指令碼是否含有3 個公鑰中的任意2 個相對應的私鑰的有效簽名。
l 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 個私鑰簽名對應3 個公鑰用於檢查多重簽名,從而保證指令碼不產生bug。
2 、 P2SH(Pay-to-Script-Hash)
P2SH在2012 年被作為一種新型、強大、且能大大簡化複雜交易指令碼的交易型別而引入。為進一步解釋P2SH 的必要性,讓我們先看一個實際的例子。
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 位元組的雜湊值取代,被稱為贖回指令碼。因為它在系統中是在贖回時出現而不是以鎖定指令碼模式出現。
下表列示了P2SH 指令碼。 
從表中可以看出,對於P2SH,詳細描述了輸出(贖回指令碼)的條件的複雜指令碼不會在鎖定指令碼中顯示。
相反,只有它的雜湊值在鎖定指令碼中呈現,並且兌換指令碼本身稍後呈現,作為解鎖指令碼在輸出花費時的一部分。這使得給礦工的交易費用從傳送方轉移到收款方,複雜的計算工作也從從傳送方轉移到收款方。
一筆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
###3、 P2WSH (Pay-to-Witness-Script-Hash)
P2WSH是支援隔離見證型別的P2SH, 我們先介紹隔離見證的概念。
隔離見證是比特幣的一種結構性調整,旨在將見證資料部分從一筆交易的scriptSig(解鎖指令碼)欄位移出至一個伴隨交易的單獨的見證資料結構。客戶端請求交易資料時可以選擇要或不要該部分伴隨的見證資料。
隔離見證由以下BIPs 定義:
BIP-141 隔離見證的主要定義
BIP-143 版本0 見證程式的交易簽名驗證
BIP-144 對等服務——新的網路訊息和序列化格式
BIP-145 隔離見證(對於礦工)的getblocktemplate 升級
l 為什麼需要隔離見證?
隔離見證是一個將在多方面產生影響的結構性調整——可擴充套件性、安全性、經濟刺激以及比特幣整體效能:
l 交易延展性
將見證移出交易後,用作識別符號的交易雜湊不在包含見證資料。因為見證資料是交易中唯一可被第三方修改(參見交易識別符章節)的部分,移除它的同時也移除了交易延展性攻擊的機會。通過隔離見證,交易變得對任何人(建立者本人除外)都不可變,這極大地提高了許多其它依賴於高階比特幣交易架構的協議的
可執行性。比如支付通道、跨連交易和閃電網路。
l 指令碼版本管理
在引入隔離見證指令碼後,類似於交易和區塊都有其版本號,每一個鎖定指令碼前也都有了一個指令碼版本號。指令碼版本號的條件允許指令碼語言用一種向後相容的方式(也就是軟分叉升級)升級,以引入新的指令碼運算元、語法或語義。非破壞性升級指令碼語言的能力將極大地加快比特幣的創新速度。
l 網路和儲存擴充套件
見證資料通常是交易總體積的重要貢獻者。更復雜的指令碼通常非常大,比如那些用於多重簽名或支付通道的指令碼。有時候這些指令碼佔據了一筆交易的大部分(超過75%)空間。通過將見證資料移出交易,隔離見證提升了比特幣的可擴充套件性。節點能夠在驗證簽名後去除見證資料,或在作簡單支付驗證時整個忽略它。見證資料不需要被髮送至所有節點,也不需要被所有節點儲存在硬碟中。
l 簽名驗證優化
隔離見證升級簽名函式(CHECKSIG, CHECKMULTISIG, 等)減少了演算法的計算複雜性。引入隔離見證前,用於生成簽名的演算法需要大量的雜湊操作,這些操作與交易的大小成正比。在O(n2)中關於簽名運算元量方面,資料雜湊計算增加,在所有節點驗證簽名上引入了大量計算負擔。引入隔離見證後,演算法更改減少了O(n2)的複雜性。
l 離線簽名改進
隔離見證簽名包含了在被簽名的雜湊雜湊中,每個輸入所引用的值(數量)。在此之前,一個離線簽名裝置,比如硬體錢包,必須在簽署交易前驗證每一個輸入的數量。這通常是通過大量的資料流來完成的,這些資料是關於以前的交易被引用作為輸入的。由於該數量現在是已簽名的承諾雜湊雜湊的一部分,因此離線裝置不需要以前的交易。如果數量不匹配(被一個折中的線上系統誤報),則簽名無效。
在P2PSH例子中,穆罕默德的公司使用了P2SH 來表達一個多重簽名指令碼。對穆罕默德的公司的支付被編碼成一個這樣的鎖定指令碼:
P2SH 輸出指令碼示例:
HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e EQUAL
這個P2SH 指令碼引用了一個贖回指令碼( redeem script )的雜湊值,該指令碼定義了一個“2 of 3”的多重簽名需求來使用資金。為了使用該輸出,穆罕默德的公司將提供這個贖回指令碼(其雜湊值與P2SH 輸出中的指令碼雜湊值匹配),以及滿足該贖回指令碼所需的簽名,所有這些都在交易輸出中:
顯示一個P2SH 輸出被使用的解碼交易:
[...]“Vin” : ["txid": "abcdef12345...","vout": 0, "scriptSig": “<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 CHECKMULTISIG>”,]
現在,讓我們看看整個示例如何升級成為隔離見證。如果穆罕默德的客戶使用的是一個隔離見證相容的錢包,他們就會付款,建立一個“支付給指令碼雜湊”(P2WSH)輸出,看起來就像這樣:
P2WSH 輸出指令碼示例:
0 9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73
再一次,就像P2WPKH 的例子一樣,你可以看到,隔離見證等同指令碼要簡單得多,省略了各種你在P2SH 指令碼中見到的指令碼操作符。相反,隔離見證程式僅包含兩個推送到堆疊的值:一個見證版本(0)和一個32 位元組的贖回指令碼的雜湊值。
提示當P2SH 使用20 位元組的RIPEMD160(SHA256(script)) 雜湊值時,P2WSH 見證程式使用了一個32 位元組的SHA256(指令碼)雜湊值。在選擇雜湊演算法時,這一差異是有意為之,被用於通過雜湊值長度來區分兩種型別的見證程式(P2WPKH andP2WSH),併為P2WSH(128 位而不是80 位P2SH)提供更強的安全性。
穆罕默德的公司可以通過提供正確的贖回指令碼和足夠的簽名滿足並花出P2WSH輸出。作為見證資料的一部分,贖回指令碼和簽名被隔離在此支出交易之外。在交易輸入內部,穆罕默德的錢包會放置一個空的scriptSig:
顯示了一個P2WSH 輸出被用隔離見證資料花出的解碼交易:
[...]“Vin” : ["txid": "abcdef12345...","vout": 0, "scriptSig":“”,][...]“witness”: “<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 CHECKMULTISIG>”[…]
我們可以看到見證資料被放置到Witness中,而scriptSig為空。
節點能夠在驗證簽名後去除該見證資料,或在作簡單支付驗證時整個忽略它。見證資料不需要被髮送至所有節點,也不需要被所有節點儲存在硬碟中。
4、總結
**多重簽名**十分強大,但是複雜的鎖定指令碼資料量很大,但其使用起來還是多有不便。
**P2SH**正是為了解決這一實際難題而被引入的,它旨在使複雜指令碼的運用能與直接向比特幣地址支付一樣簡單。在P2SH 支付中,複雜的鎖定指令碼被雜湊值所取代。
**P2SH**的解鎖指令碼通常是交易總體積的重要貢獻者,更復雜的指令碼通常非常大,比如那些用於多重簽名或支付通道的指令碼。
**P2WSH**通過將解鎖指令碼(見證資料)移出交易提升了比特幣的可擴充套件性,同時提高了區塊容量並降低了交易手續費。
5、 參考文獻 《精通比特幣(第二版)》
--- 作者:BitTribeLab 孫海濤