3.2 Segwit和Taproot
1. P2WPKH:数据结构的革新
到2017年,比特币面临两个重要问题:交易延展性(交易ID可被修改)和区块空间有限。这个我们晚点再讲。隔离见证(SegWit)升级通过改变交易数据结构解决了这些问题,引入了P2WPKH(Pay to Witness Public Key Hash)。
锁定脚本
OP_0 <公钥哈希>
这个简化的脚本是P2WPKH的标志。它不再包含完整的验证逻辑,而是告诉节点:"这是一个版本0的隔离见证输出,验证数据在witness字段中。"
解锁
在SegWit中,解锁脚本被移到一个全新的交易部分 - 见证(witness)数据结构中:
<签名> <公钥>
大家可以发现,Segwit把之前sigscript的部分放到了一个独立的空间中了。
验证过程
验证逻辑与P2PKH相同,但执行环境不同:
验证节点识别这是SegWit输出
从见证数据获取签名和公钥
验证公钥哈希与脚本中的哈希匹配
验证签名有效性
P2WPKH地址以"bc1q"开头,如:bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
P2WPKH的意义
P2WPKH代表了比特币的结构性创新:
数据隔离:将签名数据(见证数据)从交易主体移出
解决延展性:签名不再影响交易ID的计算
区块容量扩展:见证数据在计算区块大小时享有折扣
向后兼容:旧节点仍然可以验证交易(忽略见证数据)
这是比特币在保持核心原则不变的情况下,通过数据结构改变实现了重大升级。
P2TR:综合创新的巅峰
锁定脚本
OP_1 <32字节输出密钥>
这个简单脚本隐藏了极大的复杂性 - 输出密钥可能是一个简单公钥,也可能包含了一整棵脚本默克尔树的根哈希。
花费方式
P2TR提供了两种截然不同的花费路径:
1. 密钥路径花费(简单情况):
<Schnorr签名>
为何这么简单,某种签名+某种密钥就能开锁吗?这看起来很简单,但有一个重要细节:输出密钥本身是"调整后"的。它不是普通公钥,而是内部密钥(internal key)与脚本树根(merkle root)的组合。具体来说,输出密钥 = 内部密钥 + SHA256(内部密钥 || 脚本树根) * G
(其中G是椭圆曲线的生成点)。看不懂直接忽略。只需要理解为,解锁的第一种选择,用签名可以解锁。
2. 脚本路径花费(复杂条件):
<控制块> <脚本> <脚本输入...>
脚本路径的主要创新在于证明提供的脚本确实是承诺树的一部分。随后还需要像传统脚本一样执行该脚本并验证提供的输入数据(如签名)。
控制块包含完整的内部密钥和默克尔证明,脚本是要执行的具体脚本(如多签脚本),脚本输入是满足脚本条件的数据(如签名)。
验证过程
检查是否提供了签名(密钥路径)或控制块(脚本路径)
对于密钥路径:直接验证Schnorr签名
对于脚本路径:验证控制块,执行脚本
P2TR地址以"bc1p"开头,如:bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0
P2TR的综合创新
P2TR融合了前几代脚本的所有优点,同时添加了新的改进:
从P2PKH借鉴:基本的密钥验证概念
从P2SH借鉴:将复杂脚本编码为哈希(更高级的MAST结构)
从SegWit借鉴:隔离见证数据结构和效率
全新创新:
Schnorr签名(更小,支持密钥/签名聚合)
MAST(默克尔抽象语法树,只公开使用的脚本路径)
密钥和脚本路径的统一外观(隐私增强)
说说交易延展性
交易延展性是指在不改变交易基本内容(发送方、接收方、金额)的情况下,可以修改交易的ID。为什么会发生?
在比特币早期设计中,交易ID(txid)是通过对整个交易数据(包括签名)进行哈希计算得出的。而问题在于,签名本身可以有多种有效形式,就像你的签名每次都有细微差别一样。
想象你发送了1个比特币给朋友:
你创建并签名了一笔交易,交易ID是
abc123
你在区块浏览器上用
abc123
追踪这笔交易矿工(或任何人)在不改变交易有效性的情况下,稍微修改了你的签名格式
交易被确认,但现在交易ID变成了
xyz789
你无法在区块浏览器上找到
abc123
,可能误以为交易失败你可能会再次发送1个比特币,结果朋友收到了2个比特币
实际危害
交易延展性导致的主要问题:
交易追踪困难:如果你发送一笔交易后想查询其状态,却发现交易ID变了,你会误以为交易失败
二层网络风险:比如闪电网络这样的二层解决方案需要可靠地引用之前的交易,延展性问题会破坏这种依赖关系
双重支付攻击:攻击者可以修改自己发送的交易的ID,然后假装交易从未发生,尝试再次花费同一笔资金
一个真实案例
Mt. Gox(曾经最大的比特币交易所)部分归因于交易延展性问题导致破产。他们的系统在看不到修改后的交易ID时,误以为交易失败并重复发送比特币。
SegWit如何解决这个问题
隔离见证(SegWit)通过一个简单而优雅的方式解决了这个问题:
将签名数据("见证"数据)从用于计算交易ID的部分移除
交易ID现在只基于交易的"不可变"部分计算
即使有人修改了签名,交易ID也保持不变
这就像给信件一个基于内容而非信封的唯一标识符,无论信封如何变化,内容标识符保持不变。
效果
解决交易延展性带来了几个重要好处:
使闪电网络等二层解决方案成为可能
提高了交易确认的可靠性
简化了钱包开发和交易跟踪
允许更复杂的智能合约设计
交易延展性问题的解决是比特币发展历程中的一个重要里程碑,为更多创新功能铺平了道路。
传统比特币交易ID(txid)是通过对整个交易数据(包括签名)进行哈希计算得出的
使用Hal Finney的交易为例:
传统交易ID的计算过程交易ID
ea44e97271691990157559d0bdd9959e02790c34db6c006d779e82fa5aee708e
(Hal Finney的后续交易) 是通过对整个交易数据进行双SHA256哈希计算得出的。
1. 传统交易的数据结构包括:
版本号 (4字节)
输入计数 (varint)
输入列表:
前一个交易ID (32字节)
输出索引 (4字节)
脚本长度 (varint)
脚本 (包含签名数据) - 这是关键部分!
序列号 (4字节)
输出计数 (varint)
输出列表:
金额 (8字节)
脚本长度 (varint)
脚本
锁定时间 (4字节)
2. 计算过程:txid = SHA256(SHA256(完整交易数据))
然后对结果进行字节序反转。
3. 在Finney的交易中:这笔交易的解锁脚本(ScriptSig)包含:
30440220576497b7e6f9b553c0aba0d8929432550e092db9c130aae37b84b545e7f4a36c022066cb982ed80608372c139d7bb9af335423d5280350fe3e06bd510e695480914f01
这是一个DER编码的ECDSA签名,它是交易ID计算的一部分。
延展性问题在传统交易中,如果签名数据被修改(即使保持有效性),交易ID就会改变。以下是可能导致签名变化的几种方式:
1. DER编码的多样性ECDSA签名由(r,s)值组成,但s值有两种等效表示形式。如果将:
30440220576497b7e6f9b553c0aba0d8929432550e092db9c130aae37b84b545e7f4a36c022066cb982ed80608372c139d7bb9af335423d5280350fe3e06bd510e695480914f01
修改为使用等效的s值,签名仍然有效,但整个交易ID会改变。
2. 空字节填充在DER编码中,可以添加额外的空字节,签名仍然有效,但会导致交易ID变化。
3. 签名哈希类型修改签名末尾的哈希类型字节(01)在某些情况下可以修改,保持签名有效性但改变交易ID。
举例说明假设有人修改了Finney交易的签名,将最后的哈希类型标志从01改为81(同样有效),新的签名可能是:
30440220576497b7e6f9b553c0aba0d8929432550e092db9c130aae37b84b545e7f4a36c022066cb982ed80608372c139d7bb9af335423d5280350fe3e06bd510e695480914f81
这个变化会导致:
新签名仍然是有效的
交易的功能完全相同
但计算出的交易ID完全不同
这就是为什么传统交易容易受到交易延展性攻击的原因。在双方相互依赖交易ID的场景(如闪电网络)中,这种可延展性会导致严重问题。
而SegWit通过将签名数据从交易ID计算中移除,彻底解决了这个问题,使交易ID在签名修改的情况下保持不变。
Segwit将签名数据("见证"数据)从用于计算交易ID的部分移,交易ID现在只基于交易的"不可变"部分计算。
我们举个真实例子:交易ID 9f67ae2890f33dcd6c44b2026c9f924ec31d29f1365d1d5b9e7fd8746a3b6523。
对于SegWit交易,计算txid时会忽略所有见证数据。具体步骤如下:
收集交易的非见证部分数据:
版本号 (4字节): 通常是
01000000
输入计数 (varint): 此交易为
01
(1个输入)输入详情 (不包含见证数据):
前一个交易ID (32字节,字节序反转)
前一个输出索引 (4字节)
scriptSig长度 (varint): 由于是SegWit,这里为
00
(空)scriptSig: 空
序列号 (4字节):
fdffffff
(启用了RBF)输出计数 (varint):
02
(2个输出)输出详情:
输出1金额 (8字节): 138.75340531 BTC的satoshi值
输出1脚本长度 (varint)
输出1脚本:
00143df5e6917c0fa9ce0d059065156d7c68fbc1f52a
输出2金额 (8字节): 0.32100000 BTC的satoshi值
输出2脚本长度 (varint)
输出2脚本:
a914b3dded4c789ee4f10b8a0a726bb2b54e13a4fadf87
锁定时间 (4字节): 通常是
00000000
将这些数据连接成一个字节序列
对这个字节序列进行双SHA256哈希:
txid = SHA256(SHA256(非见证数据))
对结果进行字节序反转(比特币使用小端序表示哈希值)
关键是,这个计算过程完全排除了见证数据(此交易中的签名
3044...
和公钥0263...
)。这样,即使见证数据被修改(保持有效性),交易ID也保持不变。

说说Schnorr签名
Schnorr签名确实是比特币采用Taproot之前就应该实现的技术,这种延迟主要是出于专利和兼容性考虑。Schnorr签名和传统的ECDSA签名,最重要的是签名聚合能力。
多签场景比较
传统多签(类比一种物理反应):
假设Alice、Bob和Carol共同控制一个3-of-3多签钱包:
传统方式(ECDSA):
- Alice的签名:71字节
- Bob的签名:71字节
- Carol的签名:71字节
- 三个公钥:每个33字节
- 多签脚本结构:额外约22字节
总计:约280字节
可见每个签名和公钥都必须单独列出,就像物理混合物的物理反应,成分彼此独立。传统多签让数据量变得很大。
Schnorr多签(化学反应):
Schnorr方式:
- 聚合签名:64字节
- 聚合公钥:32字节
总计:96字节
三个参与者可以"离线"聚合他们的签名和公钥,产生一个单一的签名和公钥,看起来与单签交易完全相同!这就像化学反应,生成了全新的产物,外部无法分辨原始成分。
真实世界示例
在现实中,这种差异尤为明显:
Bitfinex在2020年移动了约5,000个比特币,使用了一个复杂的多签操作。这笔交易占用了区块的12%空间!如果使用Schnorr签名,同样的安全级别可能只需要原来的三分之一或更少的空间。
Bitcoin Core团队签名:每次发布都需要多位开发者签名验证。使用Schnorr,他们可以生成一个单一签名,节省空间并提高隐私。
Schnorr签名的数学魔力
Schnorr签名的"化学反应"效果来自于其线性特性:
Copy对于公钥P1、P2和对应的签名S1、S2:
聚合公钥 = P1 + P2
聚合签名 = S1 + S2
这种简单的加法关系使它具有出色的聚合特性,而ECDSA没有这种特性。
比特币在Taproot升级上使用了Schnorr签名,是一个重要的理论应用。Schnorr在各方面的性能上都完胜ECDSA。
Last updated