通过代码、实践和调试经验解锁Taproot的强大功能

Taproot到底重要在哪

比特币的Taproot升级于2021年激活,是一个改变游戏规则的里程碑。它引入了Schnorr签名、脚本树和增强的隐私性。如果你是一个区块链爱好者,可能听说过Taproot的灵活性——但你亲手试过吗?今天,我们将深入探讨一个经典场景:Alice通过一个中间地址发送测试网比特币(tBTC),可以用两种方式解锁——Key Path和Script Path。Key Path是说Alice可以用自己的私钥花费这笔钱,Script Path是说Bob可以提供一个秘密(preimage)来花费这笔钱。这不仅仅是理论;我们将分享完整的Python代码(使用bitcoinutils)、真实的测试网交易,以及我们调试过程中的经验教训。从本文你将感受比特币智能合约的魅力,以及典型的Commit-Reveal交易结构,这将是理解Ordinals\BRC20\ARC20\RUNES 等比特币协议的基础。

场景设计

想象一下,Alice想安全地发送一些测试网比特币(tBTC),并内置灵活性。她使用一个Taproot地址作为中间人——我们称之为“中间地址”——这个地址可以用两种方式解锁:

  • Key Path(密钥路径): Alice随时用她的私钥取回资金,无需任何条件。

  • Script Path(脚本路径): 任何知道秘密前像(preimage)"helloworld"的人(如Bob)可以通过脚本条件领取资金。

计划如下,由两部分或两笔交易组成:

  1. Commit Phase(提交阶段): Alice将资金发送到中间地址。

  2. Reveal Phase(揭示阶段):

    • Alice可以用她的私钥解锁(Key Path)。

    • Bob可以提供"helloworld"解锁(Script Path)。

为何叫Path?因为这两种路径,需要组成一个树的数据结构,有叶子、有默克尔根。让我们深入细节吧。

Alice的地址和提交交易(Commit Transaction)

在我们的实验中,我们使用比特币测试网和bitcoinutils库。Alice的密钥对是:

  • 私钥: cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT

  • 公钥: 0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3

  • Alice的Taproot地址: tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne

很快复习一下?私钥、公钥、地址的关系?

私钥、公钥和地址是比特币世界的“三兄弟”,它们层层递进,关系紧密又各司其职:

  1. 私钥(Private Key):

    • 是什么:一个随机的 32 字节(256 位)数字,比如 Alice 的 cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT 是 WIF 格式(Wallet Import Format),解码后是纯 256 位数字。

    • 作用:像你的“银行卡密码”,用来签名交易,证明你有权动用资金。

    • 特点:绝密,只能 Alice 自己知道,泄露就等于丢钱。

  2. 公钥(Public Key):

    • 是怎么来的:从私钥通过椭圆曲线加密(ECDSA,secp256k1 曲线)计算得出,公式是 P = d × G,其中 d 是私钥,G 是生成点。

    • 是什么:一个 33 字节(压缩格式)的数字,Alice 的公钥是 0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3(前缀 02 表示 y 坐标的奇偶性)。

    • 作用:像“银行卡号”,公开给别人,证明资金归属。

  3. 地址(Address):

    • 是怎么来的:从公钥进一步加工生成。

      • 对于简单 Taproot 地址(如 tb1p060z...):

        • 取公钥的 x 坐标(32 字节),记为 Q。

        • 添加版本号 1(Taproot 用),组成 33 字节数据。

        • 用 Bech32m 编码,加上校验码,生成 tb1p 开头的字符串。

    • 是什么:Alice 的 Taproot 地址 tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne 是 62 字符的字符串,对应的 ScriptPubKey 是 51207e9e22f81c870d9f3b57389ff2dbbba5a7ed4b8352b38cffd474bfb9d8265cff(34 字节)。

    • 作用:像“收款二维码”,用来接收资金,网络用它锁定交易输出。

关系总结:

  • 私钥 → 公钥:数学计算(单向,不可逆)。

  • 公钥 → 地址:哈希和编码(单向,保护隐私)。

  • Alice 用私钥签名,证明她能动用 tb1p060z... 的资金;中间地址 tb1p53ncq... 则额外加了脚本条件,增加了解锁方式。

中间地址怎么产生呢?是由Alice的公钥和解锁脚本产生的:

  • 脚本: OP_SHA256 <"helloworld"的哈希> OP_EQUALVERIFY OP_TRUE(大概的意思就是提供了"helloworld"的哈希,就可以解锁)

  • 公钥: 0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3

由最后产生的地址将会是:

  • 地址: tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h

  • 对应的ScriptPubKey: 5120a46780148be98aaa861ad0b5dfc5c9b935d515c7be8c9e2bc6cedfa594e2b6d9

很快复习一下,地址和ScriptPubKey的关系?

地址和 ScriptPubKey 是一对好搭档,它们本质上是同一个东西的不同表示形式:

  • 地址(tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h.):

    • 是给人类看的,方便抄写和分享。

    • 它是公钥 Q(32 字节)加上版本号(Taproot 用 1)和校验码,用 Bech32m 编码生成的字符串。

    • 长度不固定,但通常是 62 字符(测试网)。

  • ScriptPubKey(5120a46780148be98aaa861ad0b5dfc5c9b935d515c7be8c9e2bc6cedfa594e2b6d9):

    • 是给比特币网络看的,交易里的实际“锁”脚本。

    • 格式是 51(OP_1,表示 Taproot) + 20(32 字节长度) + a46780148be98aaa861ad0b5dfc5c9b935d515c7be8c9e2bc6cedfa594e2b6d9(公钥 Q 的 x-only 格式)。

    • 总共 34 字节(1 字节版本 + 1 字节长度 + 32 字节数据)。

关系:

  • 地址是 ScriptPubKey 的“便携版”,通过 Bech32m 编码生成。

  • 网络收到地址后,能反向解码成 ScriptPubKey,用来锁定资金。

  • 在我们的例子中,tb1p53ncq... 解码后正好是 5120a4678014...,它们是一回事,只是表现形式不同。

提交交易(Commit): Alice将资金发送到这个中间地址。这个交易英文叫Commit Transaction,直译为承诺交易,也就是把资金发送的一个地址(更严谨的说,是发到这个地址对应的锁定脚本,也就是ScriptPubKey里面)。这个地址看起来很简单,跟普通地址很像,但是其实他里面有很多玄机。我们使用的一个例子是:

  • TxId: 9e193d8c5b4ff4ad7cb13d196c2ecc210d9b0ec144bb919ac4314c1240629886

  • 输入: tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne (6000 聪)

  • 输出: tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h (5000 聪)

  • 手续费: 1000 聪 这为我们的揭示阶段奠定了基础。

大家可以上https://mempool.space/testnet 去查看。

相关的代码如下


from bitcoinutils.setup import setup
from bitcoinutils.utils import to_satoshis
from bitcoinutils.script import Script
from bitcoinutils.transactions import Transaction, TxInput, TxOutput
from bitcoinutils.keys import PrivateKey
import hashlib

def main():
    # 设置测试网
    setup('testnet')
    
    # Alice 的密钥
    alice_private = PrivateKey('cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT')
    alice_public = alice_private.get_public_key()
    
    # 创建 preimage 的哈希
    preimage = "helloworld"
    preimage_bytes = preimage.encode('utf-8')
    preimage_hash = hashlib.sha256(preimage_bytes).hexdigest()
    
    # 创建脚本路径 - 验证 preimage
    tr_script = Script([
        'OP_SHA256',
        preimage_hash,
        'OP_EQUALVERIFY',
        'OP_TRUE'
    ])
    
    # 创建 Taproot 地址(结合密钥路径和脚本路径)
    taproot_address = alice_public.get_taproot_address([[tr_script]])
    
    print("\n=== HTLC Taproot 地址信息 ===")
    print(f"Alice 私钥: {alice_private.to_wif()}")
    print(f"Alice 公钥: {alice_public.to_hex()}")
    print(f"Preimage: {preimage}")
    print(f"Preimage Hash: {preimage_hash}")
    print(f"Taproot 地址: {taproot_address.to_string()}")
    
    print("\n脚本路径信息:")
    print(f"Script: {tr_script}")
    
    print("\n使用说明:")
    print("1. 向这个 Taproot 地址发送比特币")
    print("2. 可以通过以下方式花费:")
    print("   - Alice 使用她的私钥(密钥路径)")
    print("   - 任何人提供正确的 preimage 'helloworld'(脚本路径)")

if __name__ == "__main__":
    main() 
```

两种花费路径揭示(Reveal Transaction)

Taproot的魅力在于它的双重解锁路径。我们用Python实现了两种方式:

1 . Script Path 揭示

Bob(或任何人)使用前像"helloworld"满足脚本条件,把钱花了。前像的英文是preimage,就是提供解锁的秘密的意思。

结果:

  • TxId: 68f7c8f0ab6b3c6f7eb037e36051ea3893b668c26ea6e52094ba01a7722e604f

  • 输出: tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne (4000 聪)。这里稍微有点歧义,大家看花回给Alice了?其实在代码里,在输出部分,Bob完全可以填一个别的地址。在我们的例子里,为了支付方便,Bob提供preimage以后,又支付回去了。反正就是把前面前面所在tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h的钱,5000聪给花了。

  • 手续费: 1000 聪

  • 状态: 成功

代码如下:

```
本脚本实现了一个 Taproot 地址的 Script Path 花费:
- 使用 preimage "helloworld" 通过脚本条件(OP_SHA256 <hash> OP_EQUALVERIFY OP_TRUE)解锁资金。
- 从一个 Taproot 输入(带脚本树)花费,输出到 Alice 的简单 Taproot 地址。
脚本验证了 preimage 的 SHA256 哈希是否匹配预定义值,成功后转移资金。

运行记录:
- 输入: 9e193d8c5b4ff4ad7cb13d196c2ecc210d9b0ec144bb919ac4314c1240629886:0
  - 金额: 0.00005 tBTC (5000 聪)
  - 地址: tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h
- 输出: tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne
  - 金额: 0.00004 tBTC (4000 聪)
- TxId: 68f7c8f0ab6b3c6f7eb037e36051ea3893b668c26ea6e52094ba01a7722e604f
- 结果: 成功 (2025-02-26)

Script Path 原理:
Taproot 地址由内部公钥 P 和脚本树 T 生成输出公钥 Q = P + H(P || T) * G,其中:
- P: Alice 的公钥 (internal_pub)。
- T: 脚本树的 Merkle 根 (H(tr_script))。
- H: Taproot 的 tagged hash 函数。
- G: 椭圆曲线生成点。
Script Path 花费通过提供 preimage 和控制块(Control Block)解锁资金:
- preimage: "helloworld" 的十六进制 (68656c6c6f776f726c64)。
- 脚本: OP_SHA256 <hash> OP_EQUALVERIFY OP_TRUE,验证 preimage 的 SHA256 是否匹配。
- 控制块: 包含脚本树的 Merkle 路径,证明脚本属于 Q。
见证数据包含 [preimage_hex, script_hex, control_block_hex],无需私钥签名。
```

from bitcoinutils.setup import setup
from bitcoinutils.utils import to_satoshis, ControlBlock
from bitcoinutils.script import Script
from bitcoinutils.transactions import Transaction, TxInput, TxOutput, TxWitnessInput
from bitcoinutils.keys import PrivateKey, P2trAddress
import hashlib

def main():
    setup('testnet')
    
    # Alice 的密钥(仅用于生成地址)
    alice_private = PrivateKey('cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT')
    alice_public = alice_private.get_public_key()
    
    # Preimage 和哈希
    preimage = "helloworld"
    preimage_hex = preimage.encode('utf-8').hex()  # 转换为十六进制
    preimage_hash = hashlib.sha256(preimage.encode('utf-8')).hexdigest()
    
    # 脚本路径
    tr_script = Script(['OP_SHA256', preimage_hash, 'OP_EQUALVERIFY', 'OP_TRUE'])
    
    # Taproot 地址
    taproot_address = alice_public.get_taproot_address([[tr_script]])
    control_block = ControlBlock(alice_public, [[tr_script]], 0, is_odd=taproot_address.is_odd())
    
    # Commit 信息
    commit_txid = "9e193d8c5b4ff4ad7cb13d196c2ecc210d9b0ec144bb919ac4314c1240629886"
    input_amount = 0.00005  # 5000 satoshis
    
    # Script Path 花费
    txin = TxInput(commit_txid, 0)
    txout = TxOutput(to_satoshis(0.00001), alice_public.get_taproot_address().to_script_pub_key())
    tx = Transaction([txin], [txout], has_segwit=True)
    tx.witnesses.append(TxWitnessInput([preimage_hex, tr_script.to_hex(), control_block.to_hex()]))
    
    # 输出验证信息
    print(f"Taproot 地址: {taproot_address.to_string()}")
    print(f"ScriptPubKey: {taproot_address.to_script_pub_key().to_hex()}")
    print(f"Input Amount: {input_amount} tBTC ({to_satoshis(input_amount)} satoshis)")
    print(f"Output Amount: 0.00004 tBTC ({to_satoshis(0.00004)} satoshis)")
    print(f"Fee: {input_amount - 0.00001} tBTC ({to_satoshis(input_amount) - to_satoshis(0.00001)} satoshis)")
    print(f"Script Path TxId: {tx.get_txid()}")
    print(f"Raw Tx: {tx.serialize()}")

if __name__ == "__main__":
    main()

2、Key Path 揭示

Alice使用她的私钥直接花费资金,绕过脚本条件。在我们的例子里,我们让Alice tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne又支付了一笔钱到中间地址,这里交易ID是f4a0233be3dcdc3764646c3d2c7d02e29d38b3449aca98234a91e08a9c96a228。而就是中间地址现在有3000聪。

我们来看看代码:

```python
"""
功能概述:
本脚本实现了一个 Taproot 地址的双路径花费:
- Key Path: 使用 Alice 的私钥直接签名花费。
- Script Path: 使用 preimage "helloworld" 通过脚本条件花费。
脚本从一个 Taproot 输入(带脚本树)花费资金,输出到另一个 Taproot 地址。

运行记录:
1. Key Path:
   - 输入: f4a0233be3dcdc3764646c3d2c7d02e29d38b3449aca98234a91e08a9c96a228:0 (3000 聪)
   - 输出: tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne (2000 聪)
   - TxId: 2a13de71b3eb9c5845bc9aed56de0efd7d8f1e5e02debb0e9b3464a4ad940d05
   - 结果: 成功 (2025-02-26)


Key Path 原理:
Taproot 地址由内部公钥 P 和脚本树 T 生成输出公钥 Q = P + H(P || T) * G,其中:
- P: Alice 的公钥 (internal_pub)。
- T: 脚本树的 Merkle 根 (H(tr_script))。
- H: Taproot 的 tagged hash 函数。
- G: 椭圆曲线生成点。
Key Path 花费直接使用私钥 d (对应 P) 签名,但需调整为 d' = d + H(P || T),以匹配 Q。
签名需要:
- utxos_scriptPubkeys: 输入的 ScriptPubKey 列表。
- amounts: 输入金额列表。
- tapleaf_scripts: 脚本树(即使不用 Script Path,也需提供以计算 tweak)。

代码结构:
1. 导入模块: bitcoinutils 和 hashlib。
2. setup_network(): 设置 Testnet。
3. create_script(): 生成脚本路径 (preimage 条件)。
4. spend_key_path(): 通过 Key Path 花费。
5. spend_script_path(): 通过 Script Path 花费。
6. main(): 主函数,运行双路径测试。

使用说明:
- 替换 commit_txid 和 input_amount 为实际 UTXO。
- 确保 bitcoinutils 最新版本 (pip install bitcoin-utils --upgrade)。
- 清理缓存 (find . -name "*.pyc" -delete) 避免旧代码干扰。
- 广播 Raw Tx 到 https://mempool.space/testnet/tx/push。
"""
from bitcoinutils.setup import setup
from bitcoinutils.utils import to_satoshis, ControlBlock
from bitcoinutils.script import Script
from bitcoinutils.transactions import Transaction, TxInput, TxOutput, TxWitnessInput
from bitcoinutils.keys import PrivateKey, P2trAddress
import hashlib

def main():
    setup('testnet')
    
    # Alice 的密钥
    alice_private = PrivateKey('cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT')
    alice_public = alice_private.get_public_key()
    
    # Preimage 和哈希
    preimage = "helloworld"
    preimage_hash = hashlib.sha256(preimage.encode('utf-8')).hexdigest()
    
    # 脚本路径
    tr_script = Script(['OP_SHA256', preimage_hash, 'OP_EQUALVERIFY', 'OP_TRUE'])
    
    # Taproot 地址
    taproot_address = alice_public.get_taproot_address([[tr_script]])
    
    # Commit 信息
    commit_txid = "f4a0233be3dcdc3764646c3d2c7d02e29d38b3449aca98234a91e08a9c96a228"
    input_amount = 0.00003  # 3000 satoshis
    
    # Key Path 花费
    txin = TxInput(commit_txid, 0)
    txout = TxOutput(to_satoshis(0.00002), alice_public.get_taproot_address().to_script_pub_key())
    tx = Transaction([txin], [txout], has_segwit=True)
    sig = alice_private.sign_taproot_input(
        tx,
        0,
        [taproot_address.to_script_pub_key()],
        [to_satoshis(input_amount)],
        script_path=False,
        tapleaf_scripts=[tr_script]  # 添加脚本树
    )
    tx.witnesses.append(TxWitnessInput([sig]))
    
    # 输出验证信息
    print(f"Taproot 地址: {taproot_address.to_string()}")
    print(f"ScriptPubKey: {taproot_address.to_script_pub_key().to_hex()}")
    print(f"Input Amount: {input_amount} tBTC ({to_satoshis(input_amount)} satoshis)")
    print(f"Output Amount: 0.00002 tBTC ({to_satoshis(0.00002)} satoshis)")
    print(f"Fee: {input_amount - 0.00002} tBTC ({to_satoshis(input_amount) - to_satoshis(0.00002)} satoshis)")
    print(f"Unsigned Tx: {tx.serialize()}")
    print(f"Schnorr Signature: {sig}")
    print(f"Key Path TxId: {tx.get_txid()}")
    print(f"Raw Tx: {tx.serialize()}")

if __name__ == "__main__":
    main()
```

结果:

  • TxId: 2a13de71b3eb9c5845bc9aed56de0efd7d8f1e5e02debb0e9b3464a4ad940d05

  • 输出: tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne(2000 聪)

  • 手续费: 1000 聪

  • 状态: 成功

解释一下工作的原理以及浏览器的数据解读

那这两个路径是怎么“合二为一”,变成一个普通地址的呢?

简单来说,Taproot把Alice的公钥(一个32字节的数字)和脚本(比如"helloworld"的验证条件)通过一种数学魔法混合在一起,生成了一个新的公钥。这个新公钥看起来就像一个普通的比特币地址,长度是32字节(256位),用Bech32m编码(自己去查是什么意思)后变成tb1p开头的字符串,比如tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h。

这个“魔法”叫作“调整”(tweak):

- Alice的公钥是P,脚本树是T(用哈希算出来的一个数)。

- Taproot用一个公式:Q = P + H(P || T) × G(这里的H是哈希,G是比特币的数学基础点),算出一个新公钥Q。

- 这个Q就是中间地址的核心(Q通过Bech32m编码变成了中间地址tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h),外人看不出里面藏了脚本,只有Alice或Bob解锁时才会暴露。

在Script Path解锁时(比如交易68f7c8f0ab6b3c6f7eb037e36051ea3893b668c26ea6e52094ba01a7722e604f),原理是这样的

- Bob提供了"helloworld"(十六进制68656c6c6f776f726c64),比特币网络会用OP_SHA256算它的哈希,得到936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af

- 然后和脚本里的哈希比对(OP_EQUALVERIFY),如果一样,就通过验证,资金解锁。

- 同时,Bob还得提供一个“控制块”(Control Block,c150be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3),目的是证明这个脚本是中间地址的一部分。

- 浏览器里看到的Witness数据(68656c6c6f776f726c64, a820936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af8851, c150be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3),就是Bob的解锁证据,网络检查后确认合法,资金就转到tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne了。 这就像一个带密码锁的保险箱,Alice用钥匙(私钥)开,Bob用密码(helloworld)开,但外面的人只看到一个普通的箱子。

问:在 Witness 里面a820936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af8851 是什么?

简单解释a820936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af8851 是 Taproot 交易中 Script Path 解锁的一部分,具体来说,它是脚本的十六进制表示。这个脚本定义了解锁条件,要求提供的前像(preimage)"helloworld" 通过 SHA256 哈希后,与预设的哈希值匹配。组成与含义让我们拆解这个十六进制字符串:

  • 完整数据: a820936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af8851

  • 分解:

    • a8: 操作码 OP_SHA256,表示计算 SHA256 哈希。

    • 20: 操作码 OP_PUSHBYTES_32,表示后面跟着 32 字节的数据。

    • 936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af: 32 字节的哈希值,即 "helloworld" 的 SHA256 哈希。

    • 88: 操作码 OP_EQUALVERIFY,验证栈顶两个值是否相等,不相等则交易失败。

    • 51: 操作码 OP_PUSHNUM_1(或 OP_TRUE),表示返回成功(1)。

通俗解读想象这个脚本是一个“密码验证器”:

  • a8(OP_SHA256): “把你给我的密码(前像)用SHA256算一下。”

  • 20(OP_PUSHBYTES_32) + 936a...(哈希值): “我这里有个标准答案,是32字节的哈希值。”

  • 88(OP_EQUALVERIFY): “比对一下,你算出来的和我的答案一样吗?不一样就滚蛋。”

  • 51(OP_TRUE): “一样的话,恭喜你,密码正确,门开了!”

问:Control Block 的作用到底是什么

在交易 68f7c8f0ab6b3c6f7eb037e36051ea3893b668c26ea6e52094ba01a7722e604f的Witness 数据中,我们看到了 Control Block: c150be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3 它的目的是“证明这个脚本是中间地址的一部分”,但这句话听起来有点抽象。让我们拆开来看,Control Block 到底是什么,为什么需要它。Control Block 是什么?Control Block 是 Taproot 交易中 Script Path 解锁的一个关键组件,它是一串十六进制数据,包含以下信息:

  • 版本字节:通常是 c1(表示 Taproot 的版本和奇偶性)。

  • 内部公钥:Alice 的公钥(去掉前缀的 x-only 格式,32 字节)。

  • Merkle 路径(可选):如果有多个脚本,会有额外的哈希值,证明当前脚本在脚本树中的位置。

在我们的例子中:

  • 完整数据:c150be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3

  • 分解:

    • c1: 版本字节,表示 Taproot 版本(0x01)和公钥 Q 的 y 坐标奇偶性(这里是奇数)。

    • 50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3: 32 字节的内部公钥(Alice 的公钥去掉 02 前缀)。

    • 无 Merkle 路径:因为我们只有一个脚本(OP_SHA256 <hash> OP_EQUALVERIFY OP_TRUE),没有多分支,所以路径为空。

它的作用:证明脚本合法性Taproot 地址(比如 tb1p53ncq...)是由 Alice 的内部公钥(P)和脚本树(T)混合生成的,公式是 Q = P + H(P || T) × G。这个 Q 是地址的核心,但外面的人看不到 P 和 T 的细节。当 Bob 用 Script Path 解锁时(提供 "helloworld" 和脚本 a820936a...),网络需要确认:

  • 这个脚本真的是中间地址的一部分吗?

  • Bob 不是随便拿个脚本糊弄人吧?

Control Block 就像一个“身份证”,告诉网络:

  • “我有 Alice 的内部公钥(50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3),这是地址的基础。”

  • “这个脚本(a820936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af8851)是和公钥一起生成地址的,我可以证明。”

网络拿到 Control Block 和脚本,重新计算 Q = P + H(P || T) × G,如果结果匹配地址的公钥(a46780148be9...),就说明脚本合法,解锁通过。结合例子解读在交易 68f7c8f0ab6b3c6f7eb037e36051ea3893b668c26ea6e52094ba01a7722e604f中:

  • 输入地址:tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h,ScriptPubKey 是 5120a46780148be98aaa861ad0b5dfc5c9b935d515c7be8c9e2bc6cedfa594e2b6d9,对应的公钥 Q 是 a46780148be98aaa861ad0b5dfc5c9b935d515c7be8c9e2bc6cedfa594e2b6d9。

  • Control Block:c150be5fc4... 提供了 Alice 的内部公钥 50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3。

  • 脚本:a820936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af8851是解锁条件。

  • 验证过程:

    1. 网络用 50be5fc4...(P)和脚本的哈希(T)计算 H(P || T)。

    2. 算出 Q = P + H(P || T) × G,结果是 a46780148be98aaa861ad0b5dfc5c9b935d515c7be8c9e2bc6cedfa594e2b6d9。

    3. 和地址的 ScriptPubKey 比对,匹配!证明脚本属于 tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h(中间地址),解锁合法。

结论:释放Taproot的潜力

通过本例,我们不仅解锁了 Taproot 的双重身份——Key Path 的迅捷私密和 Script Path 的灵活条件,更发现了它隐藏的超能力:链上的“隐身术”和比特币的智能合约。 隐私性在哪里体现? 看看我们的交易 2a13de71b3eb9c5845bc9aed56de0efd7d8f1e5e02debb0e9b3464a4ad940d05(Key Path)和 68f7c8f0ab6b3c6f7eb037e36051ea3893b668c26ea6e52094ba01a7722e604f(Script Path):在区块链浏览器上,它们的外表几乎一模一样,都是普通的 tb1p 开头地址转账,输出到 tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne,手续费 1000 聪。没人能从链上数据一眼看出,tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h藏着一个复杂的脚本条件("helloworld" 的哈希验证),还是 Alice 的私钥直接解锁。这种“隐身术”让交易细节只在解锁时显露,保护了 Alice 和 Bob 的隐私。相比传统比特币地址,Taproot 把复杂逻辑藏在幕后,提升了链上的匿名性——这不正是比特币精神的精髓吗。

比特币智能合约的本质又是什么? Taproot 的 Script Path 就像一个迷你智能合约。我们的脚本 OP_SHA256 OP_EQUALVERIFY OP_TRUE 设定了一个条件:只有知道 "helloworld" 的人才能拿走资金。这就像一个“密码锁”,Bob 输入正确密码(前像),柜子自动打开,资金转出。这种灵活性让 Alice 可以设计更多玩法——超时退款、多人签名、甚至复杂的条件逻辑——而这一切都压缩在一个 32 字节的公钥里,效率高得惊人。 Witness 数据(前像、脚本、控制块)正是这个“合同”的执行证明,简单却强大。

后续步骤

尝试你把本文的代码粘贴到自己的IDE测试和把玩一下。建立自己的前像或脚本条件。然后在测试网上用mempool.space测试。

祝你编程愉快!

Last updated