Home 比特币源码学习-隔离见证
Post
Cancel

比特币源码学习-隔离见证

这篇文章介绍下我所理解的 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

This post is licensed under CC BY 4.0 by the author.
Contents