深入了解segregated witness (segwit)

Bitcoin 有基本研究的人,或多或少都有听过segwit 这个名词,可能有些人知道他可以省手续费,可能有些人知道他能让矿工赚更多的钱,又可能有些人知道他的address 似乎长的不太一样。究竟这个早在2015 年BIP141 就已经提出来的概念,真正代表什么意义?另外,这些segwit transaction 在Bitcoin 的网络里也已经来到了50% 左右的比例,在这样segwit 越来越重要的前提下,了解这样东西我认为也是Bitcoin 社群一份子需要具备的,因此,这篇文就是希望能带给读者一些segwit 的基本知识,包含他当初的动机、witness program 解析、address 和transaction 的格式。

这篇文章并不会深入到程序代码的细节,但毕竟这仍然算是一篇技术文章,因此希望读者必须先知道Bitcoin transaction 他的基本知识,和了解Bitcoin 的script 是如何验证的。如果不清楚的可以看看我之前写的文章。

简单来说…

我们都知道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。


由上面两张图可以看到,假设一个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可以分离出去,这个差别要分清楚。

看个例子

我们用两个例子来做个小总结。


上图是一个简化过后的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 了。


上图是简化过后的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 出来的结果。


可以看到上面这个对照表只有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 究竟会长什么样子。


和一般的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 的使用只会越来越普及。

希望这篇文章对读者们有帮助,如果有哪边写得不清楚或有错误的,在不吝告知,谢谢!

本文链接地址:https://www.wwsww.cn/jishu/5962.html
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。