那些对Bitcoin有一定了解的人,多少都听闻过segwit这一名词。或许有人知晓它有助于降低手续费,或许有人了解它能提高矿工的收入,又或许有人注意到它的地址看起来与众不同。然而,这一自2015年BIP141提出的概念,究竟有何深层含义?此外,segwit交易在Bitcoin网络中的比例已达到约50%,在segwit日益重要的背景下,我认为对这一概念的理解也是Bitcoin社区成员应具备的。因此,本文旨在为读者提供关于segwit的基本知识,包括其初衷、见证程序解析、地址和交易格式。
本文不会深入探讨程序代码的细节,但毕竟这是一篇技术文章,因此建议读者先了解Bitcoin交易的基本知识以及Bitcoin脚本是如何验证的。如对此不清楚,可以参考我之前撰写的相关文章。
简单来说...我们都知道Bitcoin交易由多个input和output组成,其中input包含scriptSig,即解锁脚本,用于解锁对应的资产;output包含scriptPubKey,即锁定脚本,用于保护资产。这些脚本确保只有真正的所有者才能花费对应的资产,但它们本身并不影响也不代表交易的真实含义。
BIP141提议在区块中创建一个新的数据结构——见证程序,用于存放交易input脚本。这样一来,签名部分就被从交易中分离出来,因此得名“分离见证”,简称segwit。
动机BIP141的设计初衷有两个,首先是解决transaction malleability问题,其次是“变相”地增加Bitcoin区块的容量。
修复transaction malleability问题首先,Bitcoin定义了一个交易txID,是整个交易数据经过两次sha256处理的结果。这给节点留下了一些操作空间,节点可以更改transaction签名,例如在解锁脚本中添加一个push和一个drop操作,使得签名验证时效果不变,但txID却发生改变。这可能导致恶意节点欺骗用户,使其误以为交易未上链,实则已以另一个txID的结果上链,从而使用户在不知情的情况下重复发送资金。
BIP141的出现,由于将transaction的input脚本从交易架构中移出,而txID的算法保持不变,因此上述问题得到了巧妙地解决。
另外,由于witness的出现,产生了另一种交易ID——wtxID,或者我们常说的tx hash。因此,如果一个non-segwit交易因为没有witness数据,其txID和tx hash相同,但如果是segwit交易,两者的值则会不同。下面会有示例。需要注意的是,无论transaction是否为segwit,input引用前一个output时都使用txID而不是tx hash。
txID: [nVersion][txins][txouts][nLockTime]wtxID / hash: [nVersion][marker][flag][txins][txouts][witness][nLockTime]这里再补充一点,尽管segwit巧妙地解决了transaction malleability问题,但实际上,经过多次补丁,现行的Bitcoin系统在没有使用segwit的交易中也不再容易受到transaction malleability攻击。详细的解说此处就不再赘述。
增加区块容量原本的区块大小上限为1MB,而transaction的大小也以bytes计算。BIP141提出了一种新的计算单位——weight。因此,新的区块大小上限被定义为4M weight。在区块的数据中,只要是witness program,则1 byte对应1 weight;如果是non-witness program,则1 byte对应4 weight。
从上面的两张图可以看出,假设一个transaction大小为250 bytes,且包含两个inputs。在原始设计中,一个区块可以容纳4000笔这种transaction。而BIP141架构下,如果这笔transaction采用segwit,将其中的100 bytes(两个input脚本)分离出来变成witness program,这些witness program每1 byte对应1 weight,因此每笔transaction可以节省300 weight的空间,从而使区块可以容纳5714笔transaction,比原来多。
然而,读者仔细思考一下就会发现,这种做法实际上是变相地增加了区块容量。因为BIP141并没有丢弃任何transaction数据,只是改变了计算大小的方式。而BIP141让矿工在组装新的区块时可以接收更多的transaction,从而赚取更多的手续费,因此矿工也没有理由拒绝这个软分叉升级。
见证程序刚刚提到了segwit的关键——见证程序。为了在一般transaction架构下支持segwit,BIP141选择在前一笔output的锁定脚本上动手脚,只要看到scriptPubKey以0x00开头,就赋予其新的含义。
P2WPKH见证:全名是pay to witness public key hash,与原生的P2PKH类似,需要有一个长度为20 bytes的public key hash,用于之后的验证。当花费这个output时,原本要放入input scriptSig的signature和public key被放入见证程序中,因此input的scriptSig可以是空的。简单来说,scriptPubKey的开头为0让script engine知道这是一个segwit交易,而接下来的20 bytes让script engine更明确知道这是一个P2WPKH output,因此script engine会从见证程序中获取signature和public key,最后的验证与普通P2PKH一样。
P2WPKH in P2SH见证:上面的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,所以见证程序的内容与原生的P2WPKH相同。
P2WSH见证:0全名是pay to witness script hash,与P2SH类似,见证程序包含的内容基本上就是我们熟知的redeem script。与上面一样,scriptPubKey的0让script engine知道这是一个segwit交易,而接下来的32 bytes让script engine更明确知道这是一个P2WSH output,先通过验证见证程序中的最后一个内容进行sha256,要等于scriptPubKey的32-byte-hash,再单独验证见证程序即可。
P2WSH in P2SH见证:0与上面一样,P2WSH也可以包含在P2SH中。因为这本质上是一个P2SH output,所以segwit的识别就移到了input scriptSig中,而见证的内容也与原生的P2WSH相同。
总结Bitcoin交易input和output之间存在着“鸡生蛋、蛋生鸡”的关系,这里也是类似的。你需要先产生一个启用segwit的output(例如让output script以0开头,也就是我们上面介绍的那四种output),然后将来花费它时,才能真正使用segwit的功能,将signature移出transaction input放入见证程序中。
此外,到此处应该说明一下如何定义一个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,以下是一些重点:
这是一个non-segwit transaction(尽管我把input删掉了,因为篇幅较长,暂且相信我)所以这笔transaction的tx id和hash相同。而且因为没有witness program,所以每byte都是4 weight,这笔transaction的size和weight正好是四倍的关系。虽然这是一笔non-segwit transaction,但这个transaction产生了一个P2WPKH output,因为它的scriptPubKey为一个0加上20 bytes的hash,所以接下来花费这个output的transaction一定会是segwit transaction。上图是一个简化过的segwit transaction,其实仔细观察vin的txid可以发现这笔transaction就是去花费上一个我们产生的output,以下是一些重点:
因为是segwit transaction,所以这笔transaction的txid和hash就不同了。scriptSig是空的,还多了一个txwitness的东西,里面有两样东西,就是signature和public key。因为有了witness program,所以size乘以四倍也不会等于weight了(事实上四倍的size一定会大于weight)。地址格式address表示着一个资产(output)的所有者,从某种程度上说,它就是output locking script的一种编码,因为locking script就是该output的保护锁。以P2PKH为例,P2PKH address实际上就是将20 bytes的public key hash加上一个前缀和校验和,然后进行base58编码得到的结果。
而原生的P2WPKH和P2WSH也是类似的,早在BIP141之后,BIP142就描述了segwit transaction的address应该是什么样子,但后来的BIP173却提出了一种新的编码方式(Bech32)取代了BIP142,理由主要是为了一些效率上的考虑,此处就不再详细介绍了。
Bech32首先定义一下几个名词。
human readable part (hrp): bc (mainnet) 或 tb (testnet),用于区分给人看的。separator: 数值只能是1。data: 将资料通过下表encode出的结果。可以看到上面这个对照表只有32种可能,因此要encode的资料,以P2WPKH为例,就是那20 bytes的public key hash,必须先转成binary,然后再从左至右(MSB开始)五个bits一组进行转换,最后如果有不足5个bits就补上0。
最后,上述三样东西加上checksum一起就是它的address了。
这里可以总结一下,在Bitcoin的世界里,目前主要有两种编码方式,一种就是原来的base58编码,也就是大家常见的以1开头或3开头的address,另一种就是我们这里介绍的,在segwit世界里使用的bech32编码,也就是以bc1开头的address。
交易格式上面我们介绍了四种output的类型,包括两种原生的P2WPKH和P2WSH,以及它们的address格式,还有另外两种隐藏在P2SH中的output。最后让我们回到transaction层面,看看一个segwit transaction究竟是什么样子。
与一般的non-segwit transaction相比,它们的架构基本相同,但多了三个栏位。
marker: 这个只能是0。在non-segwit transaction的情况下,这里应该是txin_count的值,然而所有Bitcoin transaction都一定会有input(甚至coinbase transaction都会有一个不存在的input),因此这个0可以让节点区分现在是在处理一个segwit transaction。flag: 这个目前就是1。是保留弹性用的。script_witnesses: 这里就是放置witness program的地方。总结segwit就是想方设法地将scriptSig移出
segwit最初的目的是为了解决一个bug,也就是我们最初提到的transaction malleability,虽然后来通过各种补丁,未使用segwit功能的transaction也已经安全了,但segwit仍然是真正一劳永逸的做法。
而Bitcoin在0.16版本就默认设置为内建的wallet采用P2WPKH in P2SH,而在未来的0.20版本中,也会将native的segwit address(Bech32)作为默认设置,因此可以预期,将来Bitcoin网络中segwit的使用将会越来越普及。
希望这篇文章对读者们有所帮助,如果有不清楚或错误的地方,请不吝告知,谢谢!
标签: 数字货币