深入了解 segregated witness (segwit)

前言

對 Bitcoin 有基本研究的人,或多或少都有聽過 segwit 這個名詞,可能有些人知道他可以省手續費,可能有些人知道他能讓礦工賺更多的錢,又可能有些人知道他的 address 似乎長的不太一樣。究竟這個早在 2015 年 BIP141 就已經提出來的概念,真正代表什麼意義?另外,這些 segwit transaction 在 Bitcoin 的網路裡也已經來到了 50% 左右的比例,在這樣 segwit 越來越重要的前提下,了解這樣東西我認為也是 Bitcoin 社群一份子需要具備的,因此,這篇文就是希望能帶給讀者一些 segwit 的基本知識,包含他當初的動機、witness program 解析、address 和 transaction 的格式。

這篇文章並不會深入到程式碼的細節,但畢竟這仍然算是一篇技術文章,因此希望讀者必須先知道 Bitcoin transaction 他的基本知識,和了解 Bitcoin 的 script 是如何驗證的。如果不清楚的可以看看我之前寫的文章。Mastering Bitcoin — CH5 TransactionsOutlinemedium.com

簡單來說…

我們都知道 Bitcoin transaction 是由數個 input 以及 output 所組成的,而 input 含有 scriptSig,即 unlocking script 用來解鎖對應到的資產,而 output 含有 scriptPubKey,即 locking script 用來保護該資產,這些 script 是用來確認真正的擁有者才能夠花費對應的資產,但他們本身並不影響也不代表 transaction 的真實含義。

因此,BIP141 就提議,在 block 裡面創造一個新的資料結構叫 witness program 來存放 transaction 的 input script。如此一來會把簽名的部分分離 (segregate) 出 transaction 了,所以才叫做 segregated witness,簡稱 segwit。

動機

最初,BIP141 的設計是有兩個用途的,第一個是為了修正 transaction malleability 的問題,而第二個則是為了能“變相的”增加 Bitcoin block 的 capacity。

fix transaction malleability

首先 Bitcoin 定義了一個 transaction 的 txID 是整個 transaction 的資料做兩次 sha256 出來的結果,這其實給了節點一些動手腳的機會,節點可以改變 transaction signature,例如在 unlocking script 加上一個 push 跟一個 drop,造成整個 transaction 在驗證 signature 時的效果不變,但 txID 卻改變的情形,這會造成原先算出來的 txID 變得不可信。惡意的節點可以因此欺騙使用者讓他以為他的交易並沒有上鏈,但其實是以另一個 txID 的結果上了鏈,使用者在不知情的情況下就可能導致重複送款。

而 BIP141 的出現,因為把 transaction 的 input script 給移出 transaction 的架構中了,而 txID 的算法仍然保持不變,因此上述的問題就巧妙地被解決了。

另外因為 witness 的出現,產生了另一種 transaction ID 去涵蓋 witness program,稱做 wtxID,又或者是我們常看到的 tx hash。因此如果是一個 non-segwit transaction 因為根本沒有 witness data 所以他的 txID 會和 tx hash 一樣,但如果是 segwit transaction 的話,兩者的值則會不同,等等下面會有範例。需要注意的是,不管 transaction 是不是 segwit,input 去 reference 到前一個 output 的時候都是用 txID 而不是 tx hash。

txID:         [nVersion][txins][txouts][nLockTime]wtxID / hash: [nVersion][marker][flag][txins][txouts][witness][nLockTime]

這邊再補充一點,儘管 segwit 巧妙地解決了 transaction malleability 的問題,但事實上,經過了多次的 patch,現行的 Bitcoin 系統在沒有使用 segwit 的 transaction 仍然不會遭受到 transaction malleability 的攻擊了,詳細的解說這邊就先不介紹了。

increase block capacity

原本的 block 大小上限為 1MB,而 transaction 的大小也是用 bytes 來計算,BIP141 則提出一個不一樣的計算單位,叫做 weight。因此新的 block 大小上限被定義為 4M weight,在 block 的資料裡面,只要是 witness program 則 1 byte 就對應到 1 weight,如果是 non-witness program 則 1 byte 就佔了 4 weight。

Image for post
before BIP141
Image for post
after BIP141

由上面兩張圖可以看到,假設一個 transaction 為 250 bytes,且他有兩個 inputs。在原本的設計下,一個 block 可以容納 4000 筆這種 transaction。而 BIP141 的架構下,如果這筆 transaction 採用 segwit 將其中的 100 bytes (兩個 input script) 分離出來變成 witness program,這些 witness program 每 1 byte 就是 1 weight,因此每筆 transaction 就可以節省 300 weight 的空間,這個 block 就可以因此容納 5714 筆 transaction 了,比原本的多!

但其實讀者仔細想一下就會知道,這種做法是變相的調大了 block 的 capacity,因為 BIP141 並沒有丟棄任何的 transaction data,只是改變了計算大小的方式,而 BIP141 讓礦工在組新的 block 時可以收更多的 transaction,也就賺取了更多的手續費,因此礦工也不太會有理由去拒絕這個 soft fork 升級。

Witness program

剛剛提到了整個 segwit 的關鍵,就是在於 witness program。而為了在一般 transaction 的架構下能支援 segwit,因此 BIP141 就選擇在前一筆 output 的 locking script 動點手腳,只要看到 scriptPubKey 是 0x00 開頭,他就被賦予了新的意義。

P2WPKH

witness:      <signature> <pubkey>
scriptSig: (empty)
scriptPubKey: 0 <20-byte-key-hash>
(0x0014{20-byte-key-hash})

全名是 pay to witness public key hash,和原本的 P2PKH 一樣,需要有一個長度為 20 bytes 的 public key hash,用來之後驗證 signature。而當花費這個 output 時,本來要放入 input scriptSig 的 signature 和 public key 被放入到了 witness program,因此 input 的 scriptSig 就可以是空的。簡單來說,scriptPubKey 的開頭為 0 讓 script engine 知道這是一個 segwit transaction,而接下來的 20 bytes 讓 script engine 更明確知道這是一個 P2WPKH output,因此 script engine 就會去 witness program 拿 signature 和 public key,最後的驗證就和普通 P2PKH 一樣了。

P2WPKH in P2SH

witness:      <signature> <pubkey>
scriptSig: <0 <20-byte-key-hash>>
(0x160014{20-byte-key-hash})
scriptPubKey: HASH160 <20-byte-script-hash> EQUAL
(0xA914{20-byte-script-hash}87)

上面的 P2WPKH 也可以包在 P2SH 裡面,如此一來雖然較原生的 segwit transaction 消耗多一點的空間,但卻享有向下兼容的好處,因為 P2SH 是從 Bitcoin 0.6.0 就出現了。

本質上來說這就是一個 P2SH 的 output,所以 scriptPubKey 是不能亂改動的,因此本來表明 segwit 特性的 0 和 20-byte-hash 就移到了 input 的 scriptSig 了,因為這邊 hash 的長度是 20 bytes,也就表明了這是一個 P2WPKH 形態的 segwit transaction,所以 witness program 的內容就和原生的 P2WPKH 一樣。

P2WSH

witness:      0 <signature1> <1 <pubkey1> <pubkey2> 2 CHECKMULTISIG>
scriptSig: (empty)
scriptPubKey: 0 <32-byte-hash>
(0x0020{32-byte-hash})

全名是 pay to witness script hash,和 P2SH 很類似,witness program 裝的內容基本上就是我們熟知的 redeem script。和上面一樣,scriptPubKey 的 0 讓 script engine 知道這是一個 segwit transaction,而接下來的 32 bytes 讓 script engine 更明確知道這是一個 P2WSH output,先透過驗證 witness program 的最後一個東西做 sha256,要等於 scriptPubKey 的 32-byte-hash,再來單獨驗證 witness program 即可。

P2WSH in P2SH

witness:      0 <signature1> <1 <pubkey1> <pubkey2> 2 CHECKMULTISIG>
scriptSig: <0 <32-byte-hash>>
(0x220020{32-byte-hash})
scriptPubKey: HASH160 <20-byte-hash> EQUAL
(0xA914{20-byte-hash}87)

和上面一樣,P2WSH 也可以包在 P2SH 裡面。因為這本質是一個 P2SH output,所以 segwit 的識別就移到了 input scriptSig 去,而 witness 的內容也和原生的 P2WSH 相同。


小結

Bitcoin transaction 的 input 和 output 一直都有雞生蛋、蛋生雞的感覺,而這邊也是類似。你一樣也是要先產生一個有啟用 segwit 的 output 這種感覺 (例如讓 output script 為 0 開頭,也就是我們上面介紹的那四種 output),然後將來你花費他的時候,才可以真正使用 segwit 的功能,把 signature 移出去 transaction input 放到 witness program 裡去。

另外,到這邊應該來說明一下到底怎麼定義一個 transaction 是 segwit 還是 non-segwit 的。

答案還是要回到 segwit 這個字本身,witness 代表著 transaction signature,所以能把它分離出去的 transaction 就是 segwit transaction,意思就是說當一個 transaction 的其中一個 input 的 scriptSig 裡面沒有 signature,該筆 transaction 就算是 segwit transaction 了。但 non-segwit transaction 仍然可以產生 P2WPKH 或 P2WSH 或他們包在 P2SH 的版本,因為這四種 output 只是表明將來花他們的 input 是可以把 signature 給分離出去的 (啟用 segwit 的感覺),output 本身並沒有 signature 可以分離出去,這個差別要分清楚。

看個栗子🌰

我們用兩個例子來做個小總結。

Image for post
non-segwit transaction

上圖是一個簡化過後的 non-segwit transaction,而這邊我條列幾個重點

  1. 這是一個 non-segwit transaction (儘管我把 input 刪掉了,因為太佔版面了,姑且相信我) 所以這筆 transaction 的 tx id 和 hash 是相同的。
  2. 而且因為沒有任何 witness program,所以每 byte 都是 4 weight,這筆 transaction 的 size 跟 weight 也就剛好是四倍的關係。
  3. 雖然這是一筆 non-segwit transaction,但這個 transaction 產生了一個 P2WPKH output,因為他的 scriptPubKey 為一個 0 加上 20 bytes 的 hash,因此接下來花費這個 output 的 transaction 就一定會是 segwit transaction 了。
Image for post
segwit transaction

上圖是簡化過後的 segwit transaction,其實仔細看 vin 的 txid 可以發現這筆 transaction 就是去花費上一個我們產生的 output,這邊一樣我條列幾個重點

  1. 因為是 segwit transaction,所以這筆 transaction 的 txid 和 hash 就是不同的了。
  2. scriptSig 是空的,還多了一個 txwitness 的東西,裡面有兩樣東西,就是 signature 和 public key。
  3. 因為有了 witness program,所以 size 乘以四倍也不會等於 weight 了 (事實上四倍的 size 一定會大於 weight)。

Address format

address 表示著一個資產 (output) 的擁有者,某種程度他就是 output locking script 的某種 encoding,因為 locking script 就是這個 output 的保護鎖。以 P2PKH 來說,P2PKH address 其實就是拿 20 bytes 的 public key hash 加上一個 prefix 跟 checksum 然後做 base58 encoding 所得到的結果。

而原生的 P2WPKH 和 P2WSH 也是類似,早在 BIP141 之後,BIP142 就描述了 segwit transaction 的 address 該長什麼樣子,但後來的 BIP173 卻提出了一個新的 encoding 方式 (Bech32) 取代了 BIP142,理由不外乎就是因為一些效率上的考量,這邊就不多做介紹了。

Bech32

首先定義一下幾個名詞。

  1. human readable part (hrp): bc (mainnet) 或是 tb (testnet),就是用來給人做區別的。
  2. separator: 數值只能是 1。
  3. data: 將資料透過下表 encode 出來的結果。
Image for post

可以看到上面這個對照表只有 32 種可能,因此要 encode 的資料,以 P2WPKH 為例,就是那 20 bytes 的 public key hash,必須先轉成 binary,然後再從左至右 (MSB 開始) 五個 bits 一組做上表的轉換,最後如果有不足 5 個 bits 就補上 0。

最後,上述三樣東西,加上 checksum 全部一起就是他的 address 了。

這邊可以整理一下,在 Bitcoin 的世界,目前就兩種 encoding 的方式,一個就是本來的 base58 encoding,也就是大家常看到的 1 開頭或是 3 開頭的 address,另一種就是這邊我們介紹的,在 segwit 的世界裡用的 bech32 encoding,也就是 bc1 開頭的 address。

Transaction format

上面我們介紹了四種 output 的型態,包含兩種原生的 P2WPKH 跟 P2WSH,跟他們的 address format,還有另外兩種隱藏在 P2SH 裡面的 output。而最後讓我們回到 transaction 的層級,看一下一個的 segwit transaction 究竟會長什麼樣子。

Image for post
transaction format

和一般的 non-segwit transaction 相比,基本上架構都一樣,但多了三個欄位。

  1. marker: 這個只能是 0。在 non-segwit transaction 的情形,這邊理當是 txin_count 的值,然而所有 Bitcoin transaction 都一定會有 input (連 coinbase transaction 都會有一個不存在的 input),因此這個 0 就可以讓節點區別現在是在處理一個 segwit transaction。
  2. flag: 這個目前就是 1。是保留彈性用的。
  3. script_witnesses: 這邊就是放 witness program 的地方。

總結

segwit 就是想方設法地要把 scriptSig 給移出去

segwit 最初的目的是為了解決一個 bug,也就是我們最初提到的 transaction malleability,雖然後來透過各種補丁,沒使用 segwit 功能的 transaction 也是安全的了,但仍然 segwit 才是真正一勞永逸的做法。

而 Bitcoin 在 0.16 版本就 default 設定了內建的 wallet 採用 P2WPKH in P2SH ,而也會在將來的 0.20 版本改成 native 的 segwit address (Bech32),因此可以預期,將來 Bitcoin 網路 segwit 的使用只會越來越普及。

希望這篇文章對讀者們有幫助,如果有哪邊寫得不清楚或有錯誤的,在不吝告知,謝謝!

Reference


本文由 AMIS 徐粲邦 提供

原文連結

分享在 facebook
Facebook
分享在 google
Google+
分享在 twitter
Twitter
分享在 linkedin
LinkedIn
分享在 pinterest
Pinterest

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料

%d 位部落客按了讚: