通过代码、实践和调试经验解锁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)可以通过脚本条件领取资金。
计划如下,由两部分或两笔交易组成:
Commit Phase(提交阶段): Alice将资金发送到中间地址。
Reveal Phase(揭示阶段):
Alice可以用她的私钥解锁(Key Path)。
Bob可以提供"helloworld"解锁(Script Path)。
为何叫Path?因为这两种路径,需要组成一个树的数据结构,有叶子、有默克尔根。让我们深入细节吧。
Alice的地址和提交交易(Commit Transaction)
在我们的实验中,我们使用比特币测试网和bitcoinutils
库。Alice的密钥对是:
私钥:
cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT
公钥:
0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3
Alice的Taproot地址:
tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne
很快复习一下?私钥、公钥、地址的关系?
私钥、公钥和地址是比特币世界的“三兄弟”,它们层层递进,关系紧密又各司其职:
私钥(Private Key):
是什么:一个随机的 32 字节(256 位)数字,比如 Alice 的 cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT 是 WIF 格式(Wallet Import Format),解码后是纯 256 位数字。
作用:像你的“银行卡密码”,用来签名交易,证明你有权动用资金。
特点:绝密,只能 Alice 自己知道,泄露就等于丢钱。
公钥(Public Key):
是怎么来的:从私钥通过椭圆曲线加密(ECDSA,secp256k1 曲线)计算得出,公式是 P = d × G,其中 d 是私钥,G 是生成点。
是什么:一个 33 字节(压缩格式)的数字,Alice 的公钥是 0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3(前缀 02 表示 y 坐标的奇偶性)。
作用:像“银行卡号”,公开给别人,证明资金归属。
地址(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是解锁条件。
验证过程:
网络用 50be5fc4...(P)和脚本的哈希(T)计算 H(P || T)。
算出 Q = P + H(P || T) × G,结果是 a46780148be98aaa861ad0b5dfc5c9b935d515c7be8c9e2bc6cedfa594e2b6d9。
和地址的 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