Taproot
Table of Contents
1. Taproot 简介
Taproot 是 Bitcoin 的一次大升级,在区块 709632 处(2021 年 11 月 12 日)被激活。
2. Pay-to-Taproot (P2TR)
Taproot Output 是版本为 1 的隔离见证 Output,这类 Output 称为 Pay-to-Taproot (P2TR)。
2.1. 和版本 0 隔离见证 Output 的对比
我们知道,版本为 0 的隔离见证 Output 有两类(在 BIP141 中定义):
- Pay-to-Witness-Public-Key-Hash (P2WPKH)
- Pay-to-Witness-Script-Hash (P2WSH)
这两类版本为 0 的隔离见证 Output 其 scriptPubKey 字段是不同的(不同点参见表 1),所以我们很容易区分某个 Output 到底是 P2WPKH 还是 P2WSH。
但是在版本为 1 的隔离见证 Output(即 Taproot Output)中,统一了这两种形式。也就是说,P2TR 的 Output 的 scriptPubKey 字段是一样的,我们无法从 Output 的格式来得知这个 Output 是由 Schnorr 签名锁定(即 Key Path)还是由脚本锁定(即 Script Path),这样有更好的隐私。
表 1 是 P2WPKH/P2WSH/P2TR 的 scriptPubKey 字段及花费它们时的 Witness 字段的总结。
Type | scriptPubKey(锁定时使用) | Witness(花费时使用) |
---|---|---|
P2WPKH | 0x0014{20-byte-key-hash} | <signature> <pubkey> |
P2WSH | 0x0020{32-byte-hash} | ...... |
P2TR (Key Path) | 0x5120{32-byte-tweaked-public-key} | <schnorr-signature> |
P2TR (Script Path) | 0x5120{32-byte-tweaked-public-key} | ...... <script> <control-blok> |
2.1.1. 费用比对
下面分析一下 P2TR (Key Path) 和 P2WPKH 的交易大小的变化(越小就越省费用)。
从表 1 中,可以看到创建一个 P2TR (Key Path) Output 时,要比创建 P2WPKH Output 占用更多的空间,因为 P2TR (Key Path) 的 scriptPubKey 直接含有 tweaked public key(32 字节),而 P2WPKH 则是公钥哈希(20 字节)。也就是说, 往 P2TR 转账比往 P2WPKH 地址转账要贵一点点。
不过, 花费 P2TR (Key Path) 比花费 P2WPKH 要省更多的费用, 原因是:花费 P2TR (Key Path) 的 Witness 中不再包含公钥了;而且 P2TR (Key Path) 采用的 Schnorr 签名比 P2WPKH 采用的 DER 格式的 ECDSA 签名要更小。这导致花费 P2TR (Key Path) 需要提供的 Witness 小很多。
综合考虑“创建 Output”和“花费 Output”两个方面,P2TR (Key Path) 比 P2WPKH 更省费用,如表 2 所示。
P2WPKH | P2TR (Key Path) | |
---|---|---|
创建 Output | 手续费少一点点(因为 scriptPubKey 小一点点) | 手续费多 |
花费 Output | 手续费多 | 手续费少更多(因为 Witness 更小) |
2.1.2. scriptPubKey
对于隔离见证 Output,其 scriptPubKey 的首个字节为 OP_n,表示隔离见证版本,OP_n 可以是:
OP_0: 0x00 OP_1: 0x51 OP_2: 0x52 ... See: https://github.com/bitcoin/bitcoin/blob/v22.0/src/script/script.h#L68
即 版本 0 隔离见证 Output 的 scriptPubKey 的首个字节是 0x00,而版本 1 隔离见证 Output 的 scriptPubKey 的首个字节是 0x51。
2.2. Witness
Witness 的格式是什么?直接看 BIP141 对其的定义:
The witness is a serialization of all witness data of the transaction. Each txin is associated with a witness field. A witness field starts with a var_int to indicate the number of stack items for the txin. It is followed by stack items, with each item starts with a var_int to indicate the length. Witness data is NOT script.
Witness 的首个字节是 var_int 类型的数,它表示 Witness 元素的个数。
2.2.1. P2WPKH 的 Witness
对于 P2WPKH 来说,Witness 由“signature”和“pubkey”两个元素组成。
Tx 9d86b83297aaf232446e5ab41b603027fb37ad85f5259b78d6ff6fefe7cada9d 是花费 P2WPKH 的例子,它的 Witness 为:
02483045022100f1622d4147f1856cadc312df066e5a19b382b20a45d6a16efbf912b94eac5ebb022036cfe4f3a1e2342c185aa0168dbe0e388c3199d6d0d74ded414bc3787e41669f012102cdcfe7b2facd6a068fa0fb0ac5aa02fee40995ef6a7bd06a54d6c758988ad8fa
下面解释下它每个字节的含义:
0070: 02 .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. vin0 Witness Count: 2 0070: .. 48 .. .. .. .. .. .. .. .. .. .. .. .. .. .. vin0 Witness 0 Length: 0x48(72) 0070: .. .. 30 .. .. .. .. .. .. .. .. .. .. .. .. .. DER start 0070: .. .. .. 45 .. .. .. .. .. .. .. .. .. .. .. .. DER length: 69 0070: .. .. .. .. 02 .. .. .. .. .. .. .. .. .. .. .. DER int 0070: .. .. .. .. .. 21 .. .. .. .. .. .. .. .. .. .. DER R length: 33 0070: .. .. .. .. .. .. 00 f1 62 2d 41 47 f1 85 6c ad 0080: c3 12 df 06 6e 5a 19 b3 82 b2 0a 45 d6 a1 6e fb 0090: f9 12 b9 4e ac 5e bb .. .. .. .. .. .. .. .. .. DER R: 00f1622d4147f1856cadc312df066e5a19b382b20a45d6a16efbf912b94eac5ebb 0090: .. .. .. .. .. .. .. 02 .. .. .. .. .. .. .. .. DER int 0090: .. .. .. .. .. .. .. .. 20 .. .. .. .. .. .. .. DER S length: 32 0090: .. .. .. .. .. .. .. .. .. 36 cf e4 f3 a1 e2 34 00a0: 2c 18 5a a0 16 8d be 0e 38 8c 31 99 d6 d0 d7 4d 00b0: ed 41 4b c3 78 7e 41 66 9f .. .. .. .. .. .. .. DER S: 36cfe4f3a1e2342c185aa0168dbe0e388c3199d6d0d74ded414bc3787e41669f 00b0: .. .. .. .. .. .. .. .. .. 01 .. .. .. .. .. .. DER sighash type byte: 1 00b0: .. .. .. .. .. .. .. .. .. .. 21 .. .. .. .. .. vin0 Witness 1 Length: 0x21(33) 00b0: .. .. .. .. .. .. .. .. .. .. .. 02 cd cf e7 b2 sender public key 00c0: fa cd 6a 06 8f a0 fb 0a c5 aa 02 fe e4 09 95 ef 00d0: 6a 7b d0 6a 54 d6 c7 58 98 8a d8 fa
2.2.2. P2WSH 的 Witness
Tx 90d6525d315dae372550f0d0bdee0081f5eff165173e4fe705003e21b0c3333d 是花费 P2WSH 的例子(它是一个 2-of-3 多签),它的 Witness 为:
0400483045022100e671f305e2bfb42531e7a03f220e3f9be53ec58de318154866ae40fe73c3b1ca0220604ed76ff68afca4926b74f00841c1da9d4a5a993615d792e8e8f6e03c76c80401473044022004aaa8c83cf0a3ae69145bcf5ed596f191a2fa8affd78091220b187bf404e64e022060957cb42da6df3fbde0d7780d8e9ee67644b1760433424d6362cc86a8bc04e50169522103b1d7b531a4a9ca4a701cae7b1260bbe47b0b063ca40de3a3015f073d64ce614f21020166885dc4c7f6989e377eaff4bc87e0b4491efb61e28a4175f4c967a0d0917421023d60b07d04326b1277cf84a4a17f6683de8f798e0efb570be76223bbb63faef053ae
下面解释下它每个字节的含义:
0140: .. .. 04 .. .. .. .. .. .. .. .. .. .. .. .. .. vin0 Witness Count: 4 0140: .. .. .. 00 .. .. .. .. .. .. .. .. .. .. .. .. vin0 Witness 0 Length:0 0140: .. .. .. .. 48 .. .. .. .. .. .. .. .. .. .. .. vin0 Witness 1 Length:72 0140: .. .. .. .. .. 30 .. .. .. .. .. .. .. .. .. .. DER start 0140: .. .. .. .. .. .. 45 .. .. .. .. .. .. .. .. .. DER length: 69 0140: .. .. .. .. .. .. .. 02 .. .. .. .. .. .. .. .. DER int 0140: .. .. .. .. .. .. .. .. 21 .. .. .. .. .. .. .. DER R length: 33 0140: .. .. .. .. .. .. .. .. .. 00 e6 71 f3 05 e2 bf DER R: 00e671f305e2bfb42531e7a03f220e3f9be53ec58de318154866ae40fe73c3b1ca 0150: b4 25 31 e7 a0 3f 22 0e 3f 9b e5 3e c5 8d e3 18 0160: 15 48 66 ae 40 fe 73 c3 b1 ca .. .. .. .. .. .. 0160: .. .. .. .. .. .. .. .. .. .. 02 .. .. .. .. .. DER int 0160: .. .. .. .. .. .. .. .. .. .. .. 20 .. .. .. .. DER S length: 32 0160: .. .. .. .. .. .. .. .. .. .. .. .. 60 4e d7 6f DER S: 604ed76ff68afca4926b74f00841c1da9d4a5a993615d792e8e8f6e03c76c804 0170: f6 8a fc a4 92 6b 74 f0 08 41 c1 da 9d 4a 5a 99 0180: 36 15 d7 92 e8 e8 f6 e0 3c 76 c8 04 .. .. .. .. 0180: .. .. .. .. .. .. .. .. .. .. .. .. 01 .. .. .. DER sighash type byte: 1 0180: .. .. .. .. .. .. .. .. .. .. .. .. .. 47 .. .. vin0 Witness 2 Length:71 0180: .. .. .. .. .. .. .. .. .. .. .. .. .. .. 30 .. DER start 0180: .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 44 DER length: 68 0190: 02 .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. DER int 0190: .. 20 .. .. .. .. .. .. .. .. .. .. .. .. .. .. DER R length: 32 0190: .. .. 04 aa a8 c8 3c f0 a3 ae 69 14 5b cf 5e d5 DER R: 04aaa8c83cf0a3ae69145bcf5ed596f191a2fa8affd78091220b187bf404e64e 01a0: 96 f1 91 a2 fa 8a ff d7 80 91 22 0b 18 7b f4 04 01b0: e6 4e .. .. .. .. .. .. .. .. .. .. .. .. .. .. 01b0: .. .. 02 .. .. .. .. .. .. .. .. .. .. .. .. .. DER int 01b0: .. .. .. 20 .. .. .. .. .. .. .. .. .. .. .. .. DER S length: 32 01b0: .. .. .. .. 60 95 7c b4 2d a6 df 3f bd e0 d7 78 DER S: 60957cb42da6df3fbde0d7780d8e9ee67644b1760433424d6362cc86a8bc04e5 01c0: 0d 8e 9e e6 76 44 b1 76 04 33 42 4d 63 62 cc 86 01d0: a8 bc 04 e5 .. .. .. .. .. .. .. .. .. .. .. .. 01d0: .. .. .. .. 01 .. .. .. .. .. .. .. .. .. .. .. DER sighash type byte: 1 01d0: .. .. .. .. .. 69 .. .. .. .. .. .. .. .. .. .. vin0 Witness 3 script: 105 01d0: .. .. .. .. .. .. 52 .. .. .. .. .. .. .. .. .. ***OP_2 01d0: .. .. .. .. .. .. .. 21 03 b1 d7 b5 31 a4 a9 ca ***PUSHDATA 33 bytes: 03b1d7b531a4a9ca4a701cae7b1260bbe47b0b063ca40de3a3015f073d64ce614f 01e0: 4a 70 1c ae 7b 12 60 bb e4 7b 0b 06 3c a4 0d e3 01f0: a3 01 5f 07 3d 64 ce 61 4f .. .. .. .. .. .. .. 01f0: .. .. .. .. .. .. .. .. .. 21 02 01 66 88 5d c4 ***PUSHDATA 33 bytes: 020166885dc4c7f6989e377eaff4bc87e0b4491efb61e28a4175f4c967a0d09174 0200: c7 f6 98 9e 37 7e af f4 bc 87 e0 b4 49 1e fb 61 0210: e2 8a 41 75 f4 c9 67 a0 d0 91 74 .. .. .. .. .. 0210: .. .. .. .. .. .. .. .. .. .. .. 21 02 3d 60 b0 ***PUSHDATA 33 bytes: 023d60b07d04326b1277cf84a4a17f6683de8f798e0efb570be76223bbb63faef0 0220: 7d 04 32 6b 12 77 cf 84 a4 a1 7f 66 83 de 8f 79 0230: 8e 0e fb 57 0b e7 62 23 bb b6 3f ae f0 .. .. .. 0230: .. .. .. .. .. .. .. .. .. .. .. .. .. 53 .. .. ***OP_3 0230: .. .. .. .. .. .. .. .. .. .. .. .. .. .. ae .. ***OP_CHECKMULTISIG
2.2.3. P2TR (Key Path) 的 Witness
如果在花费 P2TR UTXO 时,Witness 只包含一个元素,则是 P2TR (Key Path)。
Tx dbef583962e13e365a2069d451937a6de3c2a86149dc6a4ac0d84ab450509c91 是花费 P2TR (Key Path) 的例子,它的 witness 为:
0141e6e1fe41524e65e3040bc3d080a136345c2c806eb7f336dd6a7a79e9054b0d1fc6a8d836667ef6e9f2188cd1270ab28e5e0eb642eac89f2ec50a32ca54aaf9d601
下面解释下它每个字节的含义:
0050: .. 01 .. .. .. .. .. .. .. .. .. .. .. .. .. .. vin0 Witness Count: 1 0050: .. .. 41 .. .. .. .. .. .. .. .. .. .. .. .. .. vin0 Witness 0 Length:65, schnorr_sig (64 bytes) + sig_hash (1 bytes) 0050: .. .. .. e6 e1 fe 41 52 4e 65 e3 04 0b c3 d0 80 schnorr_sig 0060: a1 36 34 5c 2c 80 6e b7 f3 36 dd 6a 7a 79 e9 05 0070: 4b 0d 1f c6 a8 d8 36 66 7e f6 e9 f2 18 8c d1 27 0080: 0a b2 8e 5e 0e b6 42 ea c8 9f 2e c5 0a 32 ca 54 0090: aa f9 d6 0090: .. .. .. 01 sig_hash: SIGHASH_ALL (0x01)
注:上面例子中 signature 占 65 字节。当 sig_hash 为 SIGHASH_DEFAULT(0x00)时,sig_hash 可以省略,这时 signature 只占 64 字节,比如 Tx 37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8 首个 Input 就是 signature 只占 64 字节的例子。
2.2.4. P2TR (Script Path) 的 Witness
如果在花费 P2TR UTXO 时,Witness 至少包含两个元素,则是 P2TR (Script Path)。
也就是说, 在花费一个 P2TR UTXO 时,是通过 Witness 中元素的个数来决定使用 Key Path(Witness 元素个数为 1)还是 Script Path(Witness 元素个数大于等于 2)。
Tx 905ecdf95a84804b192f4dc221cfed4d77959b81ed66013a7e41a6e61e7ed530 是花费 P2TR (Script Path) 的例子(它是一个 2-of-2 多签脚本),它的 Witness 为:
044123b1d4ff27b16af4b0fcb9672df671701a1a7f5a6bb7352b051f461edbc614aa6068b3e5313a174f90f3d95dc4e06f69bebd9cf5a3098fde034b01e69e8e788901400fd4a0d3f36a1f1074cb15838a48f572dc18d412d0f0f0fc1eeda9fa4820c942abb77e4d1a3c2b99ccf4ad29d9189e6e04a017fe611748464449f681bc38cf394420febe583fa77e49089f89b78fa8c116710715d6e40cc5f5a075ef1681550dd3c4ad20d0fa46cb883e940ac3dc5421f05b03859972639f51ed2eccbf3dc5a62e2e1b15ac41c02e44c9e47eaeb4bb313adecd11012dfad435cd72ce71f525329f24d75c5b9432774e148e9209baf3f1656a46986d5f38ddf4e20912c6ac28f48d6bf747469fb1
下面解释下它每个字节的含义:
0070: .. .. .. .. .. .. 04 .. .. .. .. .. .. .. .. .. vin0 Witness Count: 4 0070: .. .. .. .. .. .. .. 41 23 b1 d4 ff 27 b1 6a f4 vin0 Witness 0 Length:65 (0x41) 0080: b0 fc b9 67 2d f6 71 70 1a 1a 7f 5a 6b b7 35 2b 0090: 05 1f 46 1e db c6 14 aa 60 68 b3 e5 31 3a 17 4f 00a0: 90 f3 d9 5d c4 e0 6f 69 be bd 9c f5 a3 09 8f de 00b0: 03 4b 01 e6 9e 8e 78 89 01 .. .. .. .. .. .. .. 00b0: .. .. .. .. .. .. .. .. .. 40 0f d4 a0 d3 f3 6a vin0 Witness 1 Length:64 (0x40) 00c0: 1f 10 74 cb 15 83 8a 48 f5 72 dc 18 d4 12 d0 f0 00d0: f0 fc 1e ed a9 fa 48 20 c9 42 ab b7 7e 4d 1a 3c 00e0: 2b 99 cc f4 ad 29 d9 18 9e 6e 04 a0 17 fe 61 17 00f0: 48 46 44 49 f6 81 bc 38 cf 39 .. .. .. .. .. .. 00f0: .. .. .. .. .. .. .. .. .. .. 44 20 fe be 58 3f vin0 Witness 2 Length:68 (0x44) 0100: a7 7e 49 08 9f 89 b7 8f a8 c1 16 71 07 15 d6 e4 0110: 0c c5 f5 a0 75 ef 16 81 55 0d d3 c4 ad 20 d0 fa 0120: 46 cb 88 3e 94 0a c3 dc 54 21 f0 5b 03 85 99 72 0130: 63 9f 51 ed 2e cc bf 3d c5 a6 2e 2e 1b 15 ac .. 0130: .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 41 vin0 Witness 3 Length:65 (0x41) 0140: c0 2e 44 c9 e4 7e ae b4 bb 31 3a de cd 11 01 2d 0150: fa d4 35 cd 72 ce 71 f5 25 32 9f 24 d7 5c 5b 94 0160: 32 77 4e 14 8e 92 09 ba f3 f1 65 6a 46 98 6d 5f 0170: 38 dd f4 e2 09 12 c6 ac 28 f4 8d 6b f7 47 46 9f 0180: b1
这个 Witness 中一共有 4 个元素。
Witness 最后一个元素是 control block:
c02e44c9e47eaeb4bb313adecd11012dfad435cd72ce71f525329f24d75c5b9432774e148e9209baf3f1656a46986d5f38ddf4e20912c6ac28f48d6bf747469fb1
可以分解为:
c0 # leaf version and parity bit 2e44c9e47eaeb4bb313adecd11012dfad435cd72ce71f525329f24d75c5b9432 # internal key P 774e148e9209baf3f1656a46986d5f38ddf4e20912c6ac28f48d6bf747469fb1 # hash e
Witness 倒数第二个元素是 Script:
20febe583fa77e49089f89b78fa8c116710715d6e40cc5f5a075ef1681550dd3c4ad20d0fa46cb883e940ac3dc5421f05b03859972639f51ed2eccbf3dc5a62e2e1b15ac # 对应 Script: febe583fa77e49089f89b78fa8c116710715d6e40cc5f5a075ef1681550dd3c4 OP_CHECKSIGVERIFY d0fa46cb883e940ac3dc5421f05b03859972639f51ed2eccbf3dc5a62e2e1b15 OP_CHECKSIG
Witness 倒数第二个元素之前的所有元素都是“Script 的参数”,在这个 2-of-2 多签的例子中,它是两个 Schnorr 签名。
如何校验这个 Witness 是合法的呢?具体规则在 bip341 中,简单地总结有两点:
- 检查 Script 确实在 MAST 上;
- 脚本 Script 执行完成后,检查栈上留下 true。
这个 2-of-2 多签例子中,脚本 Script = <P1> OP_CHECKSIGVERIFY <P2> OP_CHECKSIG,输入参数是 [sig(P2), sig(P1)],只要签名是正确的,则执行完成后,栈上留下的就是 true。
2.2.5. Control Block 实例
节 2.2.4 中介绍了,花费 P2TR 时,如果 Witness 元素大于等于 2 个,则是 Script Path。这时,最后一个 Witness 是 Control Block。
下面再举例说明一下 Control Block。对于图 1(摘自 https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki )所示的 MAST,其中一共有 5 个叶子节点(即 5 个脚本),如果花费这个 P2TR 时,想使用脚本 D,除公开脚本 D 源码外,只用提供 C/E/AB 三个哈希值就可以证明脚本 D 确实在 MAST 上了。我们并不需要公开其它脚本(即 A/B/C/E)的源码。
Figure 1: Merkelized Alternative Script Tree (MAST)
完整的 Control Block 如下:
<control byte with leaf version and parity bit> <internal key P> <C> <E> <AB>
3. 创建 P2TR Output(Taproot 地址推导)
节 2.1 中提到了创建 P2TR Output 时,需要指定 scriptPubKey 为 0x5120{32-byte-tweaked-public-key},其中的 32-byte-tweaked-public-key 就是图 2 中的 Tweaked Public Key Q 的 X 坐标。我们对这个 Tweaked Public Key Q 的 X 坐标进行 Bech32m 编码就可以得到 Taproot 地址。
Figure 2: Tweaked Public Key \(Q=P+tG\) 。这仅是一个简化公式,严格来说它是错误的。正确的公式为 \(Q=\text{lift_x}(P) + tG\) ,其中 \(\text{lift_x}(P)\) 的功能是返回椭圆曲线上一个点,它的 \(Y\) 坐标一定是偶数,而 \(X\) 坐标和 \(P\) 的 \(X\) 坐标相同
有 4 种方式可以花费图 2 所指定的 Output:
- 使用 \(x + t\) 作为私钥(即 Tweaked Private Key,也可能是 \((\text{secp256k1 order}-x)+t\) ,参考节 3.1)进行 Schnorr-Bip340 签名;
- 公布 script A 和哈希 B 和哈希 C;
- 公布 script B 和哈希 A 和哈希 C;
- 公布 script C 和哈希 AB。
图 2 的例子中只给出了 3 个脚本,当然你可以根据需求删除脚本或者添加更多的脚本。
具体关于如何花费 P2TR Output,可以参考:https://github.com/bitcoin-core/btcdeb/blob/master/doc/tapscript-example.md
3.1. Tweak 公钥时为什么要 lift_x
BIP341 中有 Tweak 公钥和 Tweak 私钥的 Python 示例代码:
## 代码来源于 https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki def taproot_tweak_pubkey(pubkey, h): # 输入为 Internal Public Key,输出为 Tweaked Public Key t = int_from_bytes(tagged_hash("TapTweak", pubkey + h)) if t >= SECP256K1_ORDER: raise ValueError P = lift_x(int_from_bytes(pubkey)) # lift_x(pubkey) 的功能是返回椭圆曲线上一个点,它的 Y 坐标一定是偶数,而 X 坐标和 pubkey 的 X 坐标相同 if P is None: raise ValueError Q = point_add(P, point_mul(G, t)) return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q)) def taproot_tweak_seckey(seckey0, h): # 输入为 Internal Private Key,输出为 Tweaked Private Key seckey0 = int_from_bytes(seckey0) P = point_mul(G, 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 bytes_from_int((seckey + t) % SECP256K1_ORDER)
从上面代码中,我们可以知道:
- 在 Tweak 公钥时,对 Internal Public Key 进行了 lift_x 操作。
- 在 Tweak 私钥时,如果 Internal Private Key 对应的公钥的 \(Y\) 坐标为偶数,则对 Internal Private Key 进行了取逆操作。
为什么要进行上面的处理呢?这是因为在 BIP340 中提到 Schnorr 公钥会采用 XOnly 形式。也就是说上面函数 taproot_tweak_pubkey 接收的参数 pubkey 是 32 字节的 XOnly 公钥。 对于 Secp256k1 来说,只知道 \(X\) 坐标是无法唯一确定公钥的。 具体来说,当只知道 \(X\) 坐标时,存在两个密钥对,其公钥的 \(X\) 坐标是一样的,并且这两个公钥的 \(Y\) 坐标一定是一奇一偶的(这里不证明):
Private Key: Public Key: seckey (x1, y1) # y1 为奇数时 y2 一定为偶数;y1 为偶数时 y2 一定为奇数 n-seckey (x2=x1, y2=p-y1)
所以,只使用 XOnly 公钥时有歧义。我们需要消除歧义,采取的策略就是:总是选择公钥 \(Y\) 坐标为偶数的那个密钥对。
3.2. 只使用 Key Path 时
如果我们不需要 Script Path,则可以去掉 Script 相关的哈希,即去掉图 3 中黑线圈起来的部分。
Figure 3: 只使用 Key Path(这是个人钱包最常见的场景)
也就是说,计算没有 Script Path 的 Tweaked Public Key 的方法为: \[Q= P+tG = P+ \text{TaggedHash('TapTweak', P)} G\] 对 Tweaked Public Key Q 的 X 坐标进行 bech32m 编码就得到了个人钱包的 taproot 地址。
3.3. 只使用 Script Path 时
如果我们不需要 Key Path,则可以把 Internal Public Key 设置为一个没人知道对应私钥的公钥,如图 4 所示。
Figure 4: 只使用 Script Path(把 Internal Public Key 设置为一个没人知道对应私钥的公钥即可)
BIP 341 中推荐的 NUMS Point(一个不知道对应私钥的公钥)为:
x = 50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 y = 31d3c6863973926e049e637cb1b5f40a36dac28af1766968c30c2313f3a38904
它的构造方法是计算 secp256k1 曲线的 Base Point G 的 sha256 哈希,即:
50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 = sha256(0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)
3.4. Huffman Encoding
节 3 中提到了,花费图 2 所指定的 Output 有 4 种方式:
- 使用 \(x + t\) 作为私钥(即 Tweaked Private Key,也可能是 \((\text{secp256k1 order}-x)+t\) ,参考节 3.1)进行 Schnorr-Bip340 签名;
- 公布 script A 和哈希 B 和哈希 C;
- 公布 script B 和哈希 A 和哈希 C;
- 公布 script C 和哈希 AB。
显然,第 4 种方式要比第 2/3 种方式更加省手续费。因为除脚本本身外,第 4 种方式只需要再额外提供一个哈希值,而第 2/3 种方式需要额外提供两个哈希值。
这给我们一个启发:如果我们知道花费 Output 时最可能使用哪个脚本,则把这个脚本放到离 Merkle Root 比较近的位置,这样可以省手续费。这就是 Huffman Encoding 的思路。
3.5. Taproot 地址计算实例
下面是构造图 2 所示 Taproot Output(即计算 Taproot 地址)的实例:
#!/usr/bin/env python3 # test_framework functions can be got from: https://github.com/bitcoinops/taproot-workshop from test_framework.address import program_to_witness from test_framework.key import ECPubKey, generate_bip340_key_pair from test_framework.script import CScript, OP_CHECKSIG import struct import hashlib def ser_compact_size(l): r = b"" if l < 253: r = struct.pack("B", l) elif l < 0x10000: r = struct.pack("<BH", 253, l) elif l < 0x100000000: r = struct.pack("<BI", 254, l) else: r = struct.pack("<BQ", 255, l) return r def ser_string(s): return ser_compact_size(len(s)) + s def sha256(s): return hashlib.new('sha256', s).digest() def tagged_hash(tag, data): ss = sha256(tag.encode('utf-8')) ss += ss ss += data return sha256(ss) # Method: Returns tapbranch hash. Child hashes are lexographically sorted and then concatenated. # l: tagged hash of left child # r: tagged hash of right child def tapbranch_hash(l, r): return tagged_hash("TapBranch", b''.join(sorted([l,r]))) TAPSCRIPT_VER = bytes([0xc0]) # See tapscript chapter for more details. internalPrivkey, internalPubkey = generate_bip340_key_pair() print('Internal Private Key (x):', internalPrivkey.get_bytes().hex()) print('Internal Public Key (P):', internalPubkey.get_bytes(bip340=True).hex()) # Derive pay-to-pubkey scripts privkeyA, pubkeyA = generate_bip340_key_pair() print('Private Key A:', privkeyA.get_bytes().hex()) privkeyB, pubkeyB = generate_bip340_key_pair() print('Private Key B:', privkeyB.get_bytes().hex()) privkeyC, pubkeyC = generate_bip340_key_pair() print('Private Key C:', privkeyC.get_bytes().hex()) scriptA = CScript([pubkeyA.get_bytes(bip340=True), OP_CHECKSIG]) # 可根据需要修改为其它脚本 print('Script A:', scriptA.hex()) scriptB = CScript([pubkeyB.get_bytes(bip340=True), OP_CHECKSIG]) # 可根据需要修改为其它脚本 print('Script B:', scriptB.hex()) scriptC = CScript([pubkeyC.get_bytes(bip340=True), OP_CHECKSIG]) # 可根据需要修改为其它脚本 print('Script C:', scriptC.hex()) # 1) Compute TapLeaves A, B and C # Method: ser_string(data) is a function which adds compactsize to input data. hash_inputA = TAPSCRIPT_VER + ser_string(scriptA) hash_inputB = TAPSCRIPT_VER + ser_string(scriptB) hash_inputC = TAPSCRIPT_VER + ser_string(scriptC) taggedhash_leafA = tagged_hash("TapLeaf", hash_inputA) taggedhash_leafB = tagged_hash("TapLeaf", hash_inputB) taggedhash_leafC = tagged_hash("TapLeaf", hash_inputC) # 2) Compute Internal node TapBranch AB # Method: use tapbranch_hash() function internal_nodeAB = tapbranch_hash(taggedhash_leafA, taggedhash_leafB) # 3) Compute TapTweak rootABC = tapbranch_hash(internal_nodeAB, taggedhash_leafC) taptweak = tagged_hash("TapTweak", internalPubkey.get_bytes(bip340=True) + rootABC) print("Tweak Value (t):", taptweak.hex()) # 4) Derive the segwit output address taproot_pubkey_b = internalPubkey.tweak_add(taptweak).get_bytes(bip340=True) print("Tweaked Publib Key (Q):", taproot_pubkey_b.hex()) segwit_address = program_to_witness(1, taproot_pubkey_b, main=True) # For mainnet print('Segwit address:', segwit_address)
上面脚本执行的输出实例(注:由于私钥随机生成,每次运行的输出都会不一样):
Internal Private Key (x): 197f84101343f9e799aaa73b6b2d4732e6ac711855f618729c20b55e1b1c1cf0 Internal Public Key (P): 86aa78a95914cd36e45431693f1c1077b722a49a5074f4b22098d9bbefcf90c6 Private Key A: 6f33ada384b7e27ede03bf46d2d8fdd5757bb4dac2f7fa5813c9f6bb31814b2e Private Key B: a1d2069d9e4710bc3a52d8d4b7f9c434c0e4731d5e2a35f25c8b9c1d537ed6a0 Private Key C: 958e7e3cd3b6de611b6aba36d9cb6f32866b9ccecfaedf4a47c1ba518548ef23 Script A: 20769a05e97434e37e53f93b607906bd4c903004d38aca8e5c4398f393675963aeac Script B: 206d3d912e429b3322766a38e1b4edf5bd1c4fb425f50afa97eb99f7d603db8150ac Script C: 205174b0e4b603c49cbbe2d8294f34a5aa2bbd33c4c223c04e087d8c4823ee585eac Tweak Value (t): 866c46294e5812c6139e50e19bb3b19a0be6f31f897c651f2af1d78b6254f49b Tweaked Publib Key (Q): ce19f48f0bee9ac04c5cbfb3894886014fa5694c85781e4826cb1ea11ba80d1d Segwit address: bc1pecvlfrcta6dvqnzuh7ecjjyxq986262vs4upujpxev02zxagp5wsqfyv9c
4. Taproot 优点
5. 参考
- BIP340 (Schnorr Signatures for secp256k1)
- BIP341 (Taproot: SegWit version 1 spending rules)
- BIP342 (Validation of Taproot Scripts)
- Optech's Schnorr/Taproot workshops
- Tapscript example
- Taproot Output 生成和花费 的完整例子可参考:https://github.com/Eunovo/taproot-with-bitcoinjs
- 更多 Taproot 例子可参考:https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.spec.ts