这篇文章介绍下我所理解的 Segregated Witness(隔离见证),隔离见证比较复杂,看了源码和很多介绍的文章,也只能算是初步了解了隔离见证。所以这篇文章陆续会不断更新。
有错误的地方,欢迎留言指出。
什么是隔离见证及解决什么问题?
比特币区块容量限制为 1MB,每笔交易按照 250 字节计算,10 分钟一个区块的话,每秒钟能进行 6 笔交易,这个是非常少的,所以也就陆续出现了很多“扩容”方案。
扩容方案总体有三个方向(不算 Segwit2MB 这种折衷方案):
- 更改核心代码,增加区块容量,增加到 2MB、8MB等。但是不兼容之前版本,会导致硬分叉。
- 第二层的方案,即采用“闪电网络”,“侧链”等,将高频交易移出主网,可以不用改变核心代码,不会导致硬或软分叉。
- Segwit,这是比特币核心团队提出的方案,在不改变 1MB 上限的前提下,将验证数据(脚本、签名)移出交易体,放到专门的数据结构里。这样由于交易体内存储的数据变少了,虽然上限没变,但是实际可以容纳的交易变多了,也就提高了交易速率,实际上相当于将 1MB 增加到了 3.7MB。 关键的是这个虽然改变了核心代码,但是兼容之前版本,不会导致硬分叉的产生。
支持增加区块容量的人(其实也曾是比特币核心团队成员,包括中本聪隐退后的负责人 Gavin Andresen)出来成立了 BitcoinClassic 、BitcoinUnlimited,后来硬分叉成为了 BCH ,而 BTC 这边,比特币核心团队也激活了 Segwit 作为扩容方案。
哪种更好呢?这个真是争论不休。
单从技术来看,我觉得 Segwit 更好一些,一是因为它将“交易状态”和“见证”(签名)分离开了,这种分层思想可以解决很多以后扩展和管理方面的麻烦;二是向前兼容,这是很多产品设计的核心,这也是架构设计最难的地方,也是最体现设计的地方。
不过好于不好也不只是技术层面决定的,我是觉得哪种方案支持人数最多,那么它就是大家应该遵守的,这也是区块链这种去中心化的技术的核心,不是么?
隔离见证的一些其他优点
- 杜绝了“无意的可塑性”。 这个怎么理解呢?在之前的一篇介绍交易体的文章里,我们知道,交易体是由很多部分组成的,其中任意一部分的变化,都会影响到交易体的 id(hash),其中最有可能变化的是签名,因为在 P2SH 的模式里,如果是 n/m 这种多重签名,那么对同一份交易的有效签名可以有很多种,这就导致了交易的“可塑性”,这种当然不是刻意设计的,所以叫做“无意的可塑性”。这种变化当然是不希望的,因为这就是同一笔交易。 Segwit 因为将签名拆分出交易体,所以杜绝了这种情况的发生。
- 签名数据的传输变为了可选。 我们知道轻钱包中,只需要在本地保存区块头即可,验证交易的存在性,只需要下载特定的包含交易的交易体和默克尔树的中间值即可。在以往这样的交易体中包含签名数据,对我们是无用的,因为我们不关心交易的有效性(这是挖矿节点来验证的),我们只关心交易的存在性。Setwit 就进一步减少了轻钱包的存储负担。
实现细节
1. Transaction ID 及 Commitment
因为 witness 的加入,每个交易有了两个 ID:
txid,跟以往一样,通过如下数据的 DOUBLE SHA256 得到:
1
[nVersion][txins][txouts][nLockTime]
wtxid,新加入,通过如下数据的 DOUBLE SHA256 得到:
1
[nVersion][marker][flag][txins][txouts][witness][nLockTime]
marker: 0x00,flag: 0x01,所有 txin 的 witness 数据都存放在 [witness] 内。
所有交易的 wtxid 构成了一棵默克尔树,叶子就是 wtxid,计算出的 witness root hash 成为 commitment 的一部分,保存在 coinbase(挖矿交易)的 scriptPubKey(位于txout)内,commintment 结构如下(至少 38 字节):
1
2
3
4
5
6
1-byte - OP_RETURN (0x6a)
1-byte - Push the following 36 bytes (0x24)
4-byte - Commitment header (0xaa21a9ed)
32-byte - Commitment hash: Double-SHA256(witness root hash|witness reserved value)
39th byte onwards: Optional data with no consensus meaning
2. Segwit 如何工作
分两种情况看下区别:
P2PKH(Pay-to-Public-Key-Hash) 和 P2WPKH(Pay-to-Witness-Public-Key-Hash)
1 2 3 4 5 6 7
# P2PKH: scriptSig: <signature> <pubkey> scriptPubKey: OP_DUP OP_HASH160 <20-byte hash of Pubkey> OP_EQUALVERIFY OP_CHECKSIG # P2WPKH: scriptSig: (empty) scriptPubKey: 0 <20-byte hash of Pubkey> witness: <signature> <pubkey>
scriptSig 变为空(或者其他无效的数据都可以),scriptPubKey 也简化了,前置 0 是 witness version,注意接着的是 20 byte 的数据,以往的 scriptSig 的内容移到了 witness 内。
P2SH(Pay-to-Script-Hash) 和 P2WSH(Pay-to-Witness-Script-Hash)
1 2 3 4 5 6 7 8
# P2SH scriptSig: 0 <SigA> <SigB> <2 PubkeyA PubkeyB PubkeyC PubkeyD PubkeyE 5 CHECKMULTISIG> scriptPubKey: HASH160 <20-byte hash of redeem script> EQUAL # P2WSH scriptSig: redeemScript / validation fails scriptPubKey: 0 <32-byte hash of redeem script> witness: 0 <SigA> <SigB> <2 PubkeyA PubkeyB PubkeyC PubkeyD PubkeyE 5 CHECKMULTISIG>
scriptSig 可以是赎回脚本或其他无效的数据,scriptPubKey 跟上面一样,第一位是 version,
后面是 32 byte 的赎回脚本的 hash,这个是跟上面(20 byte)不同的,是为了区分,witness 里是原来的 scriptSig 的内容。
隔离见证采用了新的 bech32 编码,所以地址有了明显的变化,bc 开头:
3. 最后看下源码里 segwit 相关代码
先看下 coinbase 中 commitment 的创建,在 GenerateCoinbaseCommitment (validation.cpp):
其中 BlockWitnessMerkleRoot (merkle.cpp):
下面这个函数是 segwit 地址解析,DecodeDestination(key_io.cpp)
这几个函数里介绍的结构,前面都已经介绍过。
segwit 的想法是很赞,应用在现在的比特币里,总是感觉有些不优雅。比如 witness 的默克尔树的实现,根没有像交易体一样放到区块头里,而是放到了第一个交易 coinbase 内,这样是为了不硬分叉做的妥协,但是也确实使结构不清晰。
参考
https://bbs.huaweicloud.com/blogs/710256bf476611e89fc57ca23e93a89f
https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
http://www.bcfans.com/xueyuan/baike/15137.html
https://zhuanlan.zhihu.com/p/30930715