该文档提出了一个新的SegWit版本1输出类型,其花费规则基于Taproot、Schnorr签名和Merkle分支。Taproot旨在提高比特币脚本功能的隐私性、效率和灵活性,而无需添加新的安全假设。文档详细介绍了Taproot的设计、规范、构造以及花费Taproot输出的方法,并讨论了安全性、测试向量、基本原理、部署和向后兼容性。
forked from bitcoin/bips
bip-anyprevout
搜索此仓库
/
复制路径
BlameMore 文件操作
BlameMore 文件操作
合并 pull request bitcoin#1104 来自 ajtowns/202103-bip341-speedy-tri…
打开提交详情
Apr 25, 2021
40b10c8 · Apr 25, 2021
打开提交详情
345 行 (270 loc) · 40.7 KB
/
顶部
预览
代码
Blame
345 行 (270 loc) · 40.7 KB
复制原始文件
下载原始文件
大纲
编辑和原始操作
BIP: 341
Layer: Consensus (soft fork)
Title: Taproot: SegWit version 1 spending rules
Author: Pieter Wuille <pieter.wuille@gmail.com>
Jonas Nick <jonasd.nick@gmail.com>
Anthony Towns <aj@erisian.com.au>
Comments-Summary: No comments yet.
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0341
Status: Draft
Type: Standards Track
Created: 2020-01-19
License: BSD-3-Clause
Requires: 340
Post-History: 2019-05-06: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-May/016914.html [bitcoin-dev] Taproot proposal
2019-10-09: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-October/017378.html [bitcoin-dev] Taproot updates
## 目录<br>Permalink: 目录<br>- 简介 <br> - 摘要<br> - 版权<br> - 动机<br>- 设计<br>- 规范 <br> - 脚本验证规则<br> - 签名验证规则 <br> - 通用签名消息<br> - Taproot 密钥路径花费签名验证<br>- 构建和花费 Taproot 输出<br>- 安全性<br>- 测试向量<br>- 原理<br>- 部署<br>- 向后兼容性<br>- 致谢 |
本文档提出了一种新的 SegWit 版本 1 输出类型,其花费规则基于 Taproot、Schnorr 签名和 Merkle 分支。
本文档基于 3-clause BSD 许可。
本提案旨在提高比特币脚本功能的隐私性、效率和灵活性,而无需添加新的安全假设[ 1]。 具体来说,它试图最大限度地减少在创建或花费时在链上揭示的关于交易输出的可花费性条件的信息量,并添加一些升级机制,同时修复一些小的但长期存在的问题。
之前已经提出了许多相关的想法来改进比特币的脚本功能:Schnorr 签名 (BIP340)、Merkle 分支 ("MAST", BIP114, BIP117)、新的 sighash 模式 (BIP118)、像 CHECKSIGFROMSTACK 这样的新操作码,Taproot, Graftroot, G'root, 和 跨输入聚合。
将所有这些想法结合到一个提案中将是一个广泛的改变,难以审查,并且可能会错过原本可以在此过程中做出的新发现。 并非所有都同样成熟。 例如,跨输入聚合 interacts 以复杂的方式与升级机制,并且解决方案仍在 in flux。 另一方面,将它们全部分离为独立的升级会减少可以获得的效率和隐私收益,并且钱包和服务提供商可能不愿意经历许多增量更新。 因此,我们面临着功能和范围蔓延之间的权衡。 在此设计中,我们通过专注于 Taproot 和 Merkle 分支提供的结构性脚本改进,以及使它们可用和高效所需的更改来寻求平衡。 对于像 sighash 和操作码这样的东西,我们包括对已知问题的修复,但不包括可以独立添加而没有缺点的的新功能。
因此,我们选择以下技术的组合:
annex
(请参阅 BIP341)。非正式地,由此产生的设计如下:添加了一个新的见证版本(版本 1),其程序由点 Q 的 32 字节编码组成。 Q 计算为 P + hash(P||m)G,对于公钥 P,以及 Merkle 树的根 m,其叶子由版本号和脚本组成. 这些输出可以通过为 Q 提供签名直接花费,或者通过揭示 P、脚本和叶子版本、满足脚本的输入以及证明 Q 提交给该叶子的 Merkle 路径间接花费。 此构造中的所有哈希(用于从 P 计算 Q 的哈希、Merkle 树内部节点中的哈希以及使用的签名哈希)都已标记以保证域分离。
本节指定 Taproot 共识规则。 有效性由排除定义:如果不存在标记为失败的条件,则块或交易有效。
下面的符号遵循 BIP340 的符号。 这包括 hashtag(x) 符号来指代 SHA256(SHA256(tag) || SHA256(tag) || x). 据作者所知,比特币中目前没有 SHA256 的使用会向其提供以两个 SHA256 输出开头的消息,这使得 hashtag 与其他哈希之间的冲突极不可能发生。
Taproot 输出是版本号为 1 的原生 SegWit 输出(参见 BIP141)和一个 32 字节的见证程序。 以下规则仅适用于花费此类输出时。 任何其他输出,包括长度不是 32 字节的版本 1 输出,或 P2SH 包装的版本 1 输出[ 3],不受影响。
q 被称为 taproot output key (taproot 输出密钥),p 被称为 taproot internal key (taproot 内部密钥)。
我们首先定义一个可重用的通用签名消息计算函数,然后定义密钥路径花费中使用的实际签名验证。
函数 SigMsg(hash_type, ext_flag) 将被签名的消息计算为字节数组。 它也隐式地是花费交易和它花费的输出的函数,但这些都没有列出以保持符号简洁。
参数 hash_type 是一个 8 位无符号值。 重用了来自旧版脚本系统的 SIGHASH
编码,包括 SIGHASH_ALL
、SIGHASH_NONE
、SIGHASH_SINGLE
和 SIGHASH_ANYONECANPAY
,加上默认的 hash_type 值 0x00,它会导致对整个交易进行签名,就像 SIGHASH_ALL
一样。 以下限制适用,如果违反这些限制会导致验证失败:
SIGHASH_SINGLE
而没有“相应的输出”(与正在验证的输入具有相同索引的输出)。参数 ext_flag 是范围 0-127 中的整数,用于(在消息中)指示在消息末尾添加了扩展 [ 13]。
如果参数采用可接受的值,则消息是以下数据的串联,按顺序排列(每个项目的大小以字节为单位列出)。 2、4 或 8 字节的数值以小端编码。
SIGHASH_ANYONECANPAY
:SIGHASH_NONE
或 SIGHASH_SINGLE
:CTxOut
格式的所有输出的序列化的 SHA256。SIGHASH_ANYONECANPAY
:COutPoint
(32 字节哈希 + 4 字节小端)。CTxOut
中的脚本。 其大小始终为 35 字节。SIGHASH_ANYONECANPAY
:SIGHASH_SINGLE
:CTxOut
格式的相应输出的 SHA256。SigMsg() 的总长度最多为 206 字节[ 14]。 请注意,这不包括子哈希的大小,例如 sha_prevouts,它可能会在同一交易的签名中缓存。
总之,BIP143 sighash 类型的语义保持不变,除了以下几点:
SIGHASH_ANYONECANPAY
标志,则消息提交到交易花费的_所有_输出的 scriptPubKey。 [ 16]。SIGHASH_ANYONECANPAY
标志,则消息提交到_所有_交易输入的金额。[ 17]SIGHASH_NONE
或 SIGHASH_SINGLE
,则签名消息将提交到所有输入 nSequence(除非也设置了 SIGHASH_ANYONECANPAY
)。 [ 18]要使用公钥 q 验证签名 sig:
本节讨论如何构建和花费 Taproot 输出。 它仅影响选择实现接收和花费的钱包软件, 并且在任何方面都不是共识关键。
从概念上讲,每个 Taproot 输出都对应于单个公钥条件(内部密钥)以及以在树中组织的脚本编码的零个或多个通用条件的组合。 满足这些条件中的任何一个都足以花费输出。
初始步骤 第一步是确定内部密钥和其余脚本的组织方式应该是什么。 具体细节可能取决于应用程序,但以下是一些通用准则:
OP_IF
等)和将它们拆分为多个脚本(每个脚本对应于原始脚本的一条执行路径)之间进行选择时,通常最好选择后者。计算输出脚本 一旦将花费条件分成一个内部密钥 internal_pubkey
和一个叶子是 (leaf_version, script) 元组的首先,我们为32字节的 BIP340 公钥数组定义 taproot_tweak_pubkey
。
该函数返回一个bit,指示调整后的公钥的Y坐标以及公钥字节数组。
奇偶校验位是使用脚本路径花费输出所必需的。
为了允许使用密钥路径进行花费,我们定义 taproot_tweak_seckey
来计算调整后的公钥的私钥。
对于任何字节字符串 h
,它都满足 taproot_tweak_pubkey(pubkey_gen(seckey), h)[0] == pubkey_gen(taproot_tweak_seckey(seckey, h))
。
def taproot_tweak_pubkey(pubkey, h):
t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
if t >= SECP256K1_ORDER:
raise ValueError
Q = point_add(lift_x(int_from_bytes(pubkey)), point_mul(G, t))
return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q))
def taproot_tweak_seckey(seckey0, h):
P = point_mul(G, int_from_bytes(seckey0))
seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0
t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h))
if t >= SECP256K1_ORDER:
raise ValueError
return (seckey + t) % SECP256K1_ORDER
下面的函数 taproot_output_script
返回一个带有 scriptPubKey 的字节数组(参见 BIP141)。
ser_script
指的是在其输入前加上 CompactSize 编码长度的函数。
def taproot_tree_helper(script_tree):
if isinstance(script_tree, tuple):
leaf_version, script = script_tree
h = tagged_hash("TapLeaf", bytes([leaf_version]) + ser_script(script))
return ([((leaf_version, script), bytes())], h)
left, left_h = taproot_tree_helper(script_tree[0])
right, right_h = taproot_tree_helper(script_tree[1])
ret = [(l, c + right_h) for l, c in left] + [(l, c + left_h) for l, c in right]
if right_h < left_h:
left_h, right_h = right_h, left_h
return (ret, tagged_hash("TapBranch", left_h + right_h))
def taproot_output_script(internal_pubkey, script_tree):
"""Given a internal public key and a tree of scripts, compute the output script.
script_tree is either:
- a (leaf_version, script) tuple (leaf_version is 0xc0 for [[bip-0342.mediawiki|BIP342]] scripts)
- a list of two elements, each with the same structure as script_tree itself
- None
"""
if script_tree is None:
h = bytes()
else:
_, h = taproot_tree_helper(script_tree)
output_pubkey, _ = taproot_tweak_pubkey(internal_pubkey, h)
return bytes([0x51, 0x20]) + output_pubkey
此图显示了从内部密钥 P 和由 5 个脚本叶子组成的 Merkle 树中获取调整值的哈希结构。A、B、C 和 E 是类似于 D 的 TapLeaf 哈希,AB 是 TapBranch 哈希。请注意,当计算 CDE 时,首先对 E 进行哈希,因为 E 小于 CD。
要使用脚本 D 花费此输出,控制块将按以下顺序包含以下数据:
<带有叶子版本和奇偶校验位的控制字节> <内部密钥 p> <C> <E> <AB>
然后,将按照 above 所述计算 TapTweak,如下所示:
D = tagged_hash("TapLeaf", bytes([leaf_version]) + ser_script(script))
CD = tagged_hash("TapBranch", C + D)
CDE = tagged_hash("TapBranch", E + CD)
ABCDE = tagged_hash("TapBranch", AB + CDE)
TapTweak = tagged_hash("TapTweak", p + ABCDE)
使用密钥路径花费 可以使用与 internal_pubkey
对应的私钥花费 Taproot 输出。为此,见证堆栈包含一个元素:如上定义的签名哈希上的 BIP340 签名,其中私钥由与上述代码段中相同的 h
调整。参见下面的代码:
def taproot_sign_key(script_tree, internal_seckey, hash_type):
_, h = taproot_tree_helper(script_tree)
output_seckey = taproot_tweak_seckey(internal_seckey, h)
sig = schnorr_sign(sighash(hash_type), output_seckey)
if hash_type != 0:
sig += bytes([hash_type])
return [sig]
此函数返回必要的见证堆栈和一个 sighash
函数,用于计算如上定义的签名哈希(为简单起见,上面的代码段忽略了将交易、输入位置等信息传递给 sighashing 代码)。
使用其中一个脚本花费 可以通过满足其构造中使用的任何脚本来花费 Taproot 输出。为此,需要脚本的输入,加上脚本本身和控制块的见证堆栈。参见下面的代码:
def taproot_sign_script(internal_pubkey, script_tree, script_num, inputs):
info, h = taproot_tree_helper(script_tree)
(leaf_version, script), path = info[script_num]
output_pubkey_y_parity, _ = taproot_tweak_pubkey(internal_pubkey, h)
pubkey_data = bytes([output_pubkey_y_parity + leaf_version]) + internal_pubkey
return inputs + [script, pubkey_data + path]
Taproot 提高了 Bitcoin 的隐私性,因为不必显示花费输出的所有可能条件,而只需发布满足的花费条件。 理想情况下,输出使用密钥路径花费,这可以防止观察者了解 coin 的花费条件。 密钥路径花费可能是来自单签名或多签名钱包的 “正常” 付款,也可能是隐藏的多方合约的合作结算。
脚本路径花费会泄露存在脚本路径并且密钥路径不适用——例如,因为相关方未能达成协议。 此外,Merkle 根中脚本的深度会泄露信息,包括树的最小深度,这表明创建输出的特定钱包软件并有助于聚类。 因此,可以通过偏离由叶子上的概率分布确定的最佳树来提高脚本花费的隐私性。
与其他现有输出类型一样,出于隐私原因,taproot 输出不应重复使用密钥。 这不仅适用于用于花费输出的特定叶子,而且适用于输出中提交的所有叶子。 如果叶子被重用,则可能会发生花费不同的输出会重用 Merkle 证明中的相同 Merkle 分支。 使用新的密钥意味着 taproot 输出构造不需要采取特殊措施来随机化叶子位置,因为由于 taproot 中使用的分支排序 Merkle 树构造,它们已经被随机化。 这并不能避免通过叶子深度泄露信息,因此仅适用于平衡(子)树。 此外,每个叶子应具有一组与其他每个叶子不同的密钥。 这样做的原因是增加叶子熵并防止观察者使用暴力搜索来了解未公开的脚本。
Bitcoin Core 单元测试框架 中使用的测试向量可以在 here 找到。
0x50
? 选择 0x50
是因为它不会与有效的 P2WPKH 或 P2WSH 花费相混淆。由于控制块的初始字节的最低位用于指示公钥 Y 坐标的奇偶校验,因此每个叶子版本都需要一个偶数字节值和紧随其后的奇数字节值,它们都尚未在 P2WPKH 或 P2WSH 花费中使用。为了指示附件,只需要一个 “未配对” 的可用字节,比如 0x50
。此选择最大化了未来脚本版本的可用选项。annex
,否则可能会导致永久资金损失。SIGHASH_ANYONECANPAY
,则为 sha_prevouts
),而不是像在 BIP143 中那样将它们设置为零,然后再进行哈希处理。尽管如此,通过在可变长度数据之前提交数据的长度(隐式在 hash_type 和 spend_type 中),可以防止冲突。SIGHASH_SINGLE
或 SIGHASH_NONE
,为什么签名消息要提交到所有输入 nSequence? 因为设置它们已经使消息提交到所有交易输入的 prevouts
部分,所以以任何不同的方式处理 Sequence 没有用处。此外,此更改使 nSequence 与 SIGHASH_SINGLE
和 SIGHASH_NONE
仅修改签名消息(关于交易输出而不是输入)的观点保持一致。hash_type
不能是 0x00
? 允许这样做会使(包括矿工在内的第三方)将 64 字节的签名篡改为 65 字节的签名,从而导致与创建者预期的 `wtxid` 不同,费用率也不同hash_type
类型隐式存在,通常可以节省一个字节。^ 为什么输出密钥应该始终具有 taproot 承诺,即使没有脚本路径也是如此? 如果 taproot 输出密钥是密钥的聚合,则恶意方有可能在不被其他方注意的情况下添加脚本路径。 这允许绕过多方策略并窃取 coin。 MuSig 密钥聚合没有这个问题,因为它已经导致内部密钥被随机化。
攻击的工作方式如下:假设 Alice 和 Mallory 想要将他们的密钥聚合到没有脚本路径的 taproot 输出密钥中。 为了防止密钥取消和相关攻击,他们使用 MSDL-pop 而不是 MuSig。 MSDL-pop 协议要求所有参与方提供对其对应私钥的拥有权证明,并且聚合密钥只是各个密钥的总和。 在 Mallory 收到 Alice 的密钥 A 后,Mallory 创建 M = M0 + int(t)G,其中 M0 是 Mallory 的原始密钥,t 允许使用内部密钥 P = A + M0 和仅包含 Mallory 密钥的脚本进行脚本路径花费。 Mallory 将 M 的拥有权证明发送给 Alice,并且双方都计算输出密钥 Q = A + M = P + int(t)G。 Alice 将无法注意到脚本路径,但 Mallory 可以单方面花费任何带有输出密钥 Q 的 coin。
此 BIP 与 BIP342 同时部署。
对于 Bitcoin signet,这些 BIP 始终处于活动状态。
对于 Bitcoin mainnet 和 testnet3,这些 BIP 将通过名为 “taproot” 和 bit 2 的 “版本位” 进行部署,使用 BIP9 进行修改以使用较低的阈值,并带有一个额外的 min_activation_height 参数,并替换 DEFINED、STARTED 和 LOCKED_IN 状态的状态转换逻辑,如下所示:
case DEFINED:
if (GetMedianTimePast(block.parent) >= starttime) {
return STARTED;
}
return DEFINED;
case STARTED:
int count = 0;
walk = block;
for (i = 0; i < 2016; i++) {
walk = walk.parent;
if ((walk.nVersion & 0xE0000000) == 0x20000000 && ((walk.nVersion >> bit) & 1) == 1) {
count++;
}
}
if (count >= threshold) {
return LOCKED_IN;
} else if (GetMedianTimePast(block.parent) >= timeout) {
return FAILED;
}
return STARTED;
case LOCKED_IN:
if (block.nHeight < min_activation_height) {
return LOCKED_IN;
}
return ACTIVE;
对于 Bitcoin mainnet,starttime 是 epoch 时间戳 1619222400(UTC 2021 年 4 月 24 日午夜),timeout 是 epoch 时间戳 1628640000(UTC 2021 年 8 月 11 日午夜),阈值是 1815 个区块(90%)而不是 1916 个区块(95%),并且 min_activation_height 是区块 709632(预计大约 2021 年 11 月 12 日)。
对于 Bitcoin testnet3,starttime 是 epoch 时间戳 1619222400(UTC 2021 年 4 月 24 日午夜),timeout 是 epoch 时间戳 1628640000(UTC 2021 年 8 月 11 日午夜),阈值是 1512 个区块(75%),并且 min_activation_height 是区块 0。
作为软分叉,较旧的软件将继续运行而无需修改。 但是,未升级的节点会将所有 SegWit 版本 1 的见证程序视为任何人都可以花费的脚本。 强烈建议他们升级以完全验证新程序。
未升级的钱包可以使用 SegWit 版本 0 程序、传统的 pay-to-pubkey-hash 等从非升级和升级的钱包接收和发送比特币。 根据实现,如果未升级的钱包支持发送到 BIP173 Bech32 地址,则它们可能能够发送到 Segwit 版本 1 程序。
本文档是与许多人讨论脚本和签名改进的结果,并得到了 Greg Maxwell 和其他人的直接贡献。它进一步建立在早期发布的提案之上,例如 Greg Maxwell 的 Taproot,以及 Russell O'Connor、Johnson Lau 和 Mark Friedenbach 的 Merkle 分支构造。
作者要感谢 Arik Sosman 建议在哈希之前对 Merkle 节点子节点进行排序,从而无需传输树中的位置,以及所有提供宝贵反馈和评论的人,包括 structured reviews 的参与者。
- 原文链接: github.com/ajtowns/bips/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!