比特币 - BIP-0341.mediawiki,位于bip-anyprevout · ajtowns/bips

  • ajtowns
  • 发布于 2025-01-20 22:32
  • 阅读 18

该文档提出了一个新的SegWit版本1输出类型,其花费规则基于Taproot、Schnorr签名和Merkle分支。Taproot旨在提高比特币脚本功能的隐私性、效率和灵活性,而无需添加新的安全假设。文档详细介绍了Taproot的设计、规范、构造以及花费Taproot输出的方法,并讨论了安全性、测试向量、基本原理、部署和向后兼容性。

跳至内容

ajtowns/bips 公开

forked from bitcoin/bips

折叠文件树

文件

bip-anyprevout

搜索此仓库

/

bip-0341.mediawiki

复制路径

BlameMore 文件操作

BlameMore 文件操作

最近提交

luke-jrluke-jr

合并 pull request bitcoin#1104 来自 ajtowns/202103-bip341-speedy-tri…

打开提交详情

Apr 25, 2021

40b10c8 · Apr 25, 2021

历史

历史

打开提交详情

查看此文件的提交历史。

345 行 (270 loc) · 40.7 KB

/

bip-0341.mediawiki

顶部

文件元数据和控件

  • 预览

  • 代码

  • Blame

345 行 (270 loc) · 40.7 KB

Raw

复制原始文件

下载原始文件

大纲

编辑和原始操作

  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>- 致谢

简介

Permalink: 简介

摘要

Permalink: 摘要

本文档提出了一种新的 SegWit 版本 1 输出类型,其花费规则基于 Taproot、Schnorr 签名和 Merkle 分支。

版权

Permalink: 版权

本文档基于 3-clause BSD 许可。

动机

Permalink: 动机

本提案旨在提高比特币脚本功能的隐私性、效率和灵活性,而无需添加新的安全假设[ 1]。 具体来说,它试图最大限度地减少在创建或花费时在链上揭示的关于交易输出的可花费性条件的信息量,并添加一些升级机制,同时修复一些小的但长期存在的问题。

设计

Permalink: 设计

之前已经提出了许多相关的想法来改进比特币的脚本功能:Schnorr 签名 (BIP340)、Merkle 分支 ("MAST", BIP114, BIP117)、新的 sighash 模式 (BIP118)、像 CHECKSIGFROMSTACK 这样的新操作码,Taproot, Graftroot, G'root, 和 跨输入聚合

将所有这些想法结合到一个提案中将是一个广泛的改变,难以审查,并且可能会错过原本可以在此过程中做出的新发现。 并非所有都同样成熟。 例如,跨输入聚合 interacts 以复杂的方式与升级机制,并且解决方案仍在 in flux。 另一方面,将它们全部分离为独立的升级会减少可以获得的效率和隐私收益,并且钱包和服务提供商可能不愿意经历许多增量更新。 因此,我们面临着功能和范围蔓延之间的权衡。 在此设计中,我们通过专注于 Taproot 和 Merkle 分支提供的结构性脚本改进,以及使它们可用和高效所需的更改来寻求平衡。 对于像 sighash 和操作码这样的东西,我们包括对已知问题的修复,但不包括可以独立添加而没有缺点的的新功能。

因此,我们选择以下技术的组合:

  • Merkle 分支 让我们只向区块链揭示脚本的实际执行部分,而不是脚本可以执行的所有可能方式。 在已知的用于实现此目的的各种机制中,一种其中 Merkle 树直接成为脚本结构一部分的机制最大程度地节省了空间,因此选择了该方法。
  • Taproot 在此基础上,让我们合并传统上独立的 pay-to-pubkey 和 pay-to-scripthash 策略,使所有输出都可以通过密钥或(可选地)脚本来花费,并且彼此无法区分。 只要基于密钥的花费路径用于花费,就不会显示是否也允许脚本路径,从而在花费时节省空间并提高脚本的隐私性。
  • 假设大多数应用程序都涉及可以由所有各方同意花费的输出,Taproot 的优势就变得显而易见。 这就是 Schnorr 签名发挥作用的地方,因为它们允许 key aggregation:公钥可以从多个参与者公钥构造,并且需要所有参与者之间的合作才能签名。 这种多方公钥和签名与其单方公钥和签名无法区分。 这意味着,通过 taproot,大多数应用程序都可以使用基于密钥的花费路径,该路径既高效又私密。 这可以推广到任意 M-of-N 策略,因为 Schnorr 签名支持阈值签名,但代价是更复杂的设置协议。
  • 由于 Schnorr 签名还允许 批量验证,从而可以比单独验证每个签名更有效地一起验证多个签名,因此我们确保设计的各个部分都与此兼容。
  • 如果由于上述更改而出现未使用的位,则这些位将保留用于 未来扩展 的机制。 因此,Merkle 树中的每个脚本都有一个关联的版本,这样就可以通过软分叉引入新的脚本版本,同时保持与 BIP 341 的兼容性。 此外,将来的软分叉可以利用见证中当前未使用的 annex(请参阅 BIP341)。
  • 虽然 签名哈希算法 的核心语义没有改变,但本提案中包含了一些改进。 新的签名哈希算法通过在签名消息中包含金额和 scriptPubKey 来修复离线签名设备的验证功能,避免了不必要的哈希,使用标记哈希并定义了默认的 sighash 字节。
  • 与典型的早期构造(将公钥或脚本的哈希存储在输出中)相比,公钥直接包含在输出中。 这对发送者来说具有相同的成本,并且如果采用基于密钥的花费路径,则总体上更节省空间。 [ 2]

非正式地,由此产生的设计如下:添加了一个新的见证版本(版本 1),其程序由点 Q 的 32 字节编码组成。 Q 计算为 P + hash(P||m)G,对于公钥 P,以及 Merkle 树的根 m,其叶子由版本号和脚本组成. 这些输出可以通过为 Q 提供签名直接花费,或者通过揭示 P、脚本和叶子版本、满足脚本的输入以及证明 Q 提交给该叶子的 Merkle 路径间接花费。 此构造中的所有哈希(用于从 P 计算 Q 的哈希、Merkle 树内部节点中的哈希以及使用的签名哈希)都已标记以保证域分离。

规范

Permalink: 规范

本节指定 Taproot 共识规则。 有效性由排除定义:如果不存在标记为失败的条件,则块或交易有效。

下面的符号遵循 BIP340 的符号。 这包括 hashtag(x) 符号来指代 SHA256(SHA256(tag) || SHA256(tag) || x). 据作者所知,比特币中目前没有 SHA256 的使用会向其提供以两个 SHA256 输出开头的消息,这使得 hashtag 与其他哈希之间的冲突极不可能发生。

脚本验证规则

Permalink: 脚本验证规则

Taproot 输出是版本号为 1 的原生 SegWit 输出(参见 BIP141)和一个 32 字节的见证程序。 以下规则仅适用于花费此类输出时。 任何其他输出,包括长度不是 32 字节的版本 1 输出,或 P2SH 包装的版本 1 输出[ 3],不受影响。

  • q 为包含见证程序的 32 字节数组(scriptPubKey 中的第二个 push),它表示根据 BIP340 的公钥。
  • 如果见证堆栈有 0 个元素,则失败。
  • 如果至少有两个见证元素,并且最后一个元素的第一个字节是 0x50[ 4],则最后一个元素称为 annex a[ 5] 并从见证堆栈中删除。 annex(或缺少 annex)始终包含在签名中并有助于交易权重,但在 taproot 验证期间会被忽略。
  • 如果见证堆栈中只剩下一个元素,则使用密钥路径花费:
    • 单个见证堆栈元素被解释为签名,并且对于公钥 q 必须有效(参见下一节)(参见下一小节)。
  • 如果至少剩下两个见证元素,则使用脚本路径花费:
    • 将倒数第二个堆栈元素称为 s,即脚本。
    • 最后一个堆栈元素称为控制块 c,并且长度必须为 33 + 32m,对于 m 的值,m 是一个介于 0 和 128 之间的整数[ 6],包括 0 和 128。 如果不具备这样的长度,则失败。
    • p = c[1:33] 并令 P = lift_x(int(p)),其中 lift_x[:] 的定义与 BIP340 中的定义相同。 如果此点不在曲线上,则失败。
    • v = c[0] & 0xfe 并将其称为 leaf version (叶版本)[ 7]。
    • k0 = hashTapLeaf(v || compact_size(size of s) || s); 也将其称为 tapleaf hash (tapleaf 哈希)。
    • 对于 [0,1,...,m-1] 中的 j
    • ej = c[33+32j:65+32j]
    • kj+1 取决于 kj < ej(按字典顺序)[ 8]:
      • 如果 kj < ej: kj+1 = hashTapBranch(kj || ej)[ 9]。
      • 如果 kj ≥ ej: kj+1 = hashTapBranch(ej || kj).
    • t = hashTapTweak(p || km).
    • 如果 t ≥ 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141(secp256k1 的阶数),则失败。
    • Q = P + int(t)G
    • 如果 q ≠ x(Q)c[0] & 1 ≠ y(Q) mod 2,则失败[ 10]。
    • 根据适用的脚本规则[ 11],执行脚本,使用不包括脚本 s、控制块 c 和 annex a(如果存在)的见证堆栈元素作为初始堆栈。

q 被称为 taproot output key (taproot 输出密钥),p 被称为 taproot internal key (taproot 内部密钥)。

签名验证规则

Permalink: 签名验证规则

我们首先定义一个可重用的通用签名消息计算函数,然后定义密钥路径花费中使用的实际签名验证。

通用签名消息

Permalink: 通用签名消息

函数 SigMsg(hash_type, ext_flag) 将被签名的消息计算为字节数组。 它也隐式地是花费交易和它花费的输出的函数,但这些都没有列出以保持符号简洁。

参数 hash_type 是一个 8 位无符号值。 重用了来自旧版脚本系统的 SIGHASH 编码,包括 SIGHASH_ALLSIGHASH_NONESIGHASH_SINGLESIGHASH_ANYONECANPAY,加上默认的 hash_type0x00,它会导致对整个交易进行签名,就像 SIGHASH_ALL 一样。 以下限制适用,如果违反这些限制会导致验证失败:

  • 使用任何未定义的 hash_type(不是 0x000x010x020x030x810x820x83[ 12])。
  • 使用 SIGHASH_SINGLE 而没有“相应的输出”(与正在验证的输入具有相同索引的输出)。

参数 ext_flag 是范围 0-127 中的整数,用于(在消息中)指示在消息末尾添加了扩展 [ 13]。

如果参数采用可接受的值,则消息是以下数据的串联,按顺序排列(每个项目的大小以字节为单位列出)。 2、4 或 8 字节的数值以小端编码。

  • 控制:
    • hash_type (1)。
  • 交易数据:
    • nVersion (4):交易的 nVersion
    • nLockTime (4):交易的 nLockTime
    • 如果 hash_type & 0x80 不等于 SIGHASH_ANYONECANPAY
    • sha_prevouts (32):所有输入 output point 的序列化的 SHA256。
    • sha_amounts (32):所有花费的输出金额的序列化的 SHA256。
    • sha_scriptpubkeys (32):所有花费的输出 scriptPubKey 的序列化的 SHA256。
    • sha_sequences (32):所有输入 nSequence 的序列化的 SHA256。
    • 如果 hash_type & 3 不等于 SIGHASH_NONESIGHASH_SINGLE
    • sha_outputs (32):CTxOut 格式的所有输出的序列化的 SHA256。
  • 关于此输入的数据:
    • spend_type (1):等于 (ext_flag * 2) + annex_present,其中 annex_present 是 0(如果没有 annex),否则为 1(原始见证堆栈有两个或更多见证元素,并且最后一个元素的第一个字节是 0x50
    • 如果 hash_type & 0x80 等于 SIGHASH_ANYONECANPAY
    • outpoint (36):此输入的 COutPoint(32 字节哈希 + 4 字节小端)。
    • amount (8):此输入花费的先前输出的值。
    • scriptPubKey (35):此输入花费的先前输出的 scriptPubKey,序列化为 CTxOut 中的脚本。 其大小始终为 35 字节。
    • nSequence (4):此输入的 nSequence
    • 如果 hash_type & 0x80 不等于 SIGHASH_ANYONECANPAY
    • input_index (4):此输入在交易输入向量中的索引。 第一个输入的索引为 0。
    • 如果存在 annex(设置了 spend_type 的最低位):
    • sha_annex (32):(compact_size(size of annex) || annex) 的 SHA256,其中 annex 包括强制性的 0x50 前缀。
  • 关于此输出的数据:
    • 如果 hash_type & 3 等于 SIGHASH_SINGLE
    • sha_single_output (32):CTxOut 格式的相应输出的 SHA256。

SigMsg() 的总长度最多为 206 字节[ 14]。 请注意,这不包括子哈希的大小,例如 sha_prevouts,它可能会在同一交易的签名中缓存。

总之,BIP143 sighash 类型的语义保持不变,除了以下几点:

  1. 序列化的方式和顺序已更改。[ 15]
  2. 签名消息提交到已花费输出的 scriptPubKey,并且如果未设置 SIGHASH_ANYONECANPAY 标志,则消息提交到交易花费的_所有_输出的 scriptPubKey。 [ 16]。
  3. 如果未设置 SIGHASH_ANYONECANPAY 标志,则消息提交到_所有_交易输入的金额。[ 17]
  4. 如果设置了 SIGHASH_NONESIGHASH_SINGLE,则签名消息将提交到所有输入 nSequence(除非也设置了 SIGHASH_ANYONECANPAY)。 [ 18]
  5. 签名消息包括对 taproot 特定数据 spend_typeannex(如果存在)的提交。
Taproot 密钥路径花费签名验证

Permalink: Taproot 密钥路径花费签名验证

要使用公钥 q 验证签名 sig

  • 如果 sig 的长度为 64 字节,则返回 Verify(q, hashTapSighash(0x00 || SigMsg(0x00, 0)), sig)[ 19],其中 VerifyBIP340 中定义。
  • 如果 sig 的长度为 65 字节,则返回 _sig[64] ≠ 0x00[ 20] 且 Verify(q, hashTapSighash(0x00 || SigMsg(sig[64], 0)), sig[0:64])_.
  • 否则,失败[ 21]。

构建和花费 Taproot 输出

Permalink: 构建和花费 Taproot 输出

本节讨论如何构建和花费 Taproot 输出。 它仅影响选择实现接收和花费的钱包软件, 并且在任何方面都不是共识关键。

从概念上讲,每个 Taproot 输出都对应于单个公钥条件(内部密钥)以及以在树中组织的脚本编码的零个或多个通用条件的组合。 满足这些条件中的任何一个都足以花费输出。

初始步骤 第一步是确定内部密钥和其余脚本的组织方式应该是什么。 具体细节可能取决于应用程序,但以下是一些通用准则:

  • 在具有条件语句的脚本(OP_IF 等)和将它们拆分为多个脚本(每个脚本对应于原始脚本的一条执行路径)之间进行选择时,通常最好选择后者。
  • 当单个条件需要使用多个密钥进行签名时,可以使用像 MuSig 这样的密钥聚合技术将它们组合成单个密钥。 细节超出了本文档的范围,但请注意,这可能会使签名过程复杂化。
  • 如果一个或多个花费条件仅由单个密钥组成(在聚合之后),则最有可能的那个应该作为内部密钥。 如果不存在这样的条件,则值得添加一个由参与所有脚本的所有密钥的聚合组成的条件; 有效地添加一个“每个人都同意”的分支。 如果这是不可接受的,则选择一个具有未知离散对数的点作为内部密钥。 这种点的一个例子是 H = lift_x(0x0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0),它是通过获取 构造 的标准未压缩编码的哈希 secp256k1 基点 G 作为 X 坐标。 为了避免泄露无法进行密钥路径花费的信息,建议在范围 0...n-1 中随机选择一个新的整数 r,并使用 H + rG 作为内部密钥。 可以通过向验证者显示 r 来证明此内部密钥相对于 G 没有已知的离散对数,验证者可以重建内部密钥的创建方式。
  • 如果花费条件不需要脚本路径,则输出密钥应提交到不可花费的脚本路径,而不是没有脚本路径。 这可以通过将输出密钥点计算为 Q = P + int(hashTapTweak(bytes(P)))G 来实现。 [ 22]
  • 剩余的脚本应该组织成二叉树的叶子。 如果这些脚本对应的每个条件的可能性相同,这可以是一棵平衡树。 如果已知每个条件的概率,请考虑将树构建为 Huffman 树。

计算输出脚本 一旦将花费条件分成一个内部密钥 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 &lt; 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*。

此图显示了从内部密钥 P 和由 5 个脚本叶子组成的 Merkle 树中获取调整值的哈希结构。ABCE 是类似于 DTapLeaf 哈希,ABTapBranch 哈希。请注意,当计算 CDE 时,首先对 E 进行哈希,因为 E 小于 CD

要使用脚本 D 花费此输出,控制块将按以下顺序包含以下数据:

     &lt;带有叶子版本和奇偶校验位的控制字节> &lt;内部密钥 p> &lt;C> &lt;E> &lt;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 找到。

基本原理

永久链接:基本原理

  1. ^ 不添加安全性假设意味着什么? 签名的不可伪造性是防止盗窃的必要要求。至少当将脚本执行本身视为数字签名方案时,可以在随机预言模型中假设离散对数问题很难来 proven 。当前脚本系统中 ECDSA 不可伪造性的 proof 需要在此基础上进行非标准假设。请注意,通常很难准确地建模脚本的安全性意味着什么,因为它取决于钱包软件使用的策略和协议。
  2. ^ 为什么公钥直接包含在输出中? 虽然典型的早期构造将脚本或公钥的哈希存储在输出中,但这在总是涉及公钥时相当浪费。为了保证批量可验证性,每个验证者都必须知道公钥,因此仅显示其哈希作为输出意味着向见证添加额外的 32 字节。此外,为了保持输出的 128-bit collision security ,无论如何都需要一个 256 位的哈希,这在大小(因此在发送者的成本中)方面与直接显示公钥相当。虽然通常说使用公钥哈希可以防止 ECDLP 或量子计算机的攻击,但这种保护充其量是非常弱的:交易在确认时不会受到保护,并且无论如何 large portion 的货币供应不受这种保护。可以通过依赖不同的加密假设来引入对这种系统的实际抵抗力,但该提案侧重于不改变安全模型的改进。
  3. ^ 为什么不支持 P2SH 包装? 由于使用了 160 位哈希,因此使用 P2SH 包装的输出仅提供 80 位的抗冲突安全性。这被认为是低的,并且当输出包含来自多个参与方(公钥、哈希……)的数据时,会成为安全风险。
  4. ^ 为什么附件的第一个字节是 0x50 选择 0x50 是因为它不会与有效的 P2WPKH 或 P2WSH 花费相混淆。由于控制块的初始字节的最低位用于指示公钥 Y 坐标的奇偶校验,因此每个叶子版本都需要一个偶数字节值和紧随其后的奇数字节值,它们都尚未在 P2WPKH 或 P2WSH 花费中使用。为了指示附件,只需要一个 “未配对” 的可用字节,比如 0x50。此选择最大化了未来脚本版本的可用选项。
  5. ^ 附件的目的是什么? 附件是为未来扩展保留的空间,例如以一种无需了解正在花费的输出的 scriptPubKey 即可识别的方式指示计算成本高昂的新操作码的验证成本。在另一个软分叉定义此字段的含义之前,用户不应在交易中包含 annex,否则可能会导致永久资金损失。
  6. ^ 为什么 Merkle 路径长度限制为 128? 最佳空间效率的 Merkle 树可以基于叶子中脚本的概率,使用 Huffman 算法构建。此算法将构建长度大约等于 log2(1/probability) 的分支,但要使分支长于 128,你需要执行几率低于 2128 分之一的脚本。由于这是我们的安全界限,因此可以完全删除真正具有如此低几率的脚本。
  7. ^ 对叶子版本有什么约束? 首先,叶子版本不能是奇数,因为 c[0] & 0xfe 将始终是偶数,并且不能是 0x50,因为这会导致与附件的歧义。此外,为了支持某些形式的静态分析,这些静态分析依赖于能够在没有访问正在花费的输出的情况下识别脚本花费,建议避免使用任何叶子版本,这些叶子版本会与有效 P2WPKH 公钥或有效 P2WSH 脚本的有效第一个字节冲突(即,vv | 1 都应该是一个未定义的、无效的或禁用的操作码,或者一个作为第一个操作码无效的操作码)。符合此规则的值是 0xc00xfe 之间的 32 个偶数值,以及 0x660x7e0x800x840x960x980xba0xbc0xbe。另请注意,此约束意味着叶子版本应在不同的见证版本之间共享,因为知道见证版本需要访问正在花费的输出。
  8. ^ 为什么在 Merkle 树中哈希之前对子元素进行排序? 通过这样做,不必连同公开的 Merkle 分支中的哈希一起显示左/右方向。这是可能的,因为我们实际上并不关心特定脚本在树中的位置;只关心它们是否实际上已提交。
  9. ^ 为什么不为内部 Merkle 节点使用更有效的哈希构造? 所选择的构造确实需要调用两次 SHA256 压缩函数,理论上可以避免其中一个压缩函数(参见 BIP98)。但是,似乎最好坚持使用可以使用标准加密原语实现的构造,这既是为了实现简单性,也是为了可分析性。如果需要,可以通过 specialization 针对 64 字节的输入优化掉第二个压缩函数的很大一部分。
  10. ^ 为什么需要在脚本路径花费中显示一个 bit,并检查它是否与 Q 的 Y 坐标的奇偶校验匹配? Y 坐标的奇偶校验是提升 X 坐标 q 到唯一点所必需的。虽然这对于验证如上所述的 taproot 承诺不是绝对必要的,但它对于允许批量验证是必要的。或者,可以强制 Q 具有偶数 Y 坐标,但这需要使用不同的内部公钥(或不同的消息)重试,直到 Q 具有该属性。添加奇偶校验位没有缺点,因为否则控制块位将不会被使用。
  11. ^ 脚本路径花费中适用的脚本规则是什么? BIP342 指定了适用于叶子版本 0xc0 的有效性规则,但未来的提案可以为其他叶子版本引入规则。
  12. ^ 为什么要拒绝未知的 hash_type 值? 通过这样做,更容易推理具有足够缓存的实现必须执行的签名哈希的最坏情况数量。
  13. ^ 哪些扩展使用 ext_flag 机制? BIP342 重用了相同的通用签名消息算法,但在最后添加了 BIP342 特有的数据,这使用 ext_flag = 1 指示。
  14. ^ SigMsg() 的输出长度是多少? SigMsg() 的总长度可以使用以下公式计算:174 - is_anyonecanpay 49 - is_none 32 + has_annex * 32
  15. ^ 为什么更改签名消息中的序列化? 进入签名消息的哈希和消息本身现在使用单个 SHA256 调用而不是双 SHA256 计算。通过加倍 SHA256,不会预期到安全性得到提升,因为这只会针对 SHA256 的长度扩展攻击进行保护,而这对于签名消息来说不是问题,因为没有秘密数据。因此,加倍 SHA256 是一种资源浪费。消息计算现在遵循逻辑顺序,首先是交易级别的数据,然后是输入数据和输出数据。这允许使用 SHA256 中间状态有效地缓存跨不同输入的交易部分的消息。此外,当计算消息时,可以跳过子哈希(例如,如果设置了 SIGHASH_ANYONECANPAY,则为 sha_prevouts),而不是像在 BIP143 中那样将它们设置为零,然后再进行哈希处理。尽管如此,通过在可变长度数据之前提交数据的长度(隐式在 hash_type 和 spend_type 中),可以防止冲突。
  16. ^ 为什么签名消息要提交到 scriptPubKey 这可以防止向离线签名设备谎报正在花费的输出,即使实际执行的脚本(BIP143 中的 scriptCode)是正确的。这意味着可以紧凑地向硬件钱包证明存在哪些(未使用的)执行路径。此外,提交到所有花费的 scriptPubKey 有助于离线签名设备确定属于其自身钱包的子集。这在 automated coinjoins 中很有用。
  17. ^ 为什么签名消息要提交到所有交易输入的金额? 这消除了向离线签名设备谎报交易费用的可能性。
  18. ^ 如果设置了 SIGHASH_SINGLESIGHASH_NONE,为什么签名消息要提交到所有输入 nSequence 因为设置它们已经使消息提交到所有交易输入的 prevouts 部分,所以以任何不同的方式处理 Sequence 没有用处。此外,此更改使 nSequence 与 SIGHASH_SINGLESIGHASH_NONE 仅修改签名消息(关于交易输出而不是输入)的观点保持一致。
  19. ^ 为什么 hashTapSighash 的输入以 0x00 开头? 此前缀称为 sighash epoch,允许在未来的签名算法中重用 hashTapSighash 标记的哈希,这些算法对执行哈希的方式进行了侵入性更改(与用于增量扩展的 ext_flag 机制相反)。另一种方法是让它们使用不同的标记,但支持越来越多的标记可能变得不理想。
  20. ^ 为什么在 65 字节的签名中 hash_type 不能是 0x00 允许这样做会使(包括矿工在内的第三方)将 64 字节的签名篡改为 65 字节的签名,从而导致与创建者预期的 `wtxid` 不同,费用率也不同
  21. ^ 为什么允许两种签名长度? 通过使最常见的 hash_type 类型隐式存在,通常可以节省一个字节。
  22. ^ 为什么输出密钥应该始终具有 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 &lt; 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 &lt; 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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ajtowns
ajtowns
江湖只有他的大名,没有他的介绍。