4.3 P2TR到P2TR,taproot to taproot
P2TR到P2TR
本节主要是讲如何解锁Taproot地址。
from bitcoinutils.setup import setup
from bitcoinutils.utils import to_satoshis
from bitcoinutils.transactions import Transaction, TxInput, TxOutput, TxWitnessInput
from bitcoinutils.keys import PrivateKey, P2trAddress
def main():
# 设置测试网
setup('testnet')
# 发送方信息(使用正确的私钥)
from_private_key = PrivateKey('cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT')
from_pub = from_private_key.get_public_key()
from_address = from_pub.get_taproot_address()
# 接收方地址
to_address = P2trAddress('tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h')
# 创建交易输入
txin = TxInput(
'b0f49d2f30f80678c6053af09f0611420aacf20105598330cb3f0ccb8ac7d7f0',
0
)
# 输入金额(用于签名)
input_amount = 0.00029200
amounts = [to_satoshis(input_amount)]
# 输入脚本(用于签名)
input_script = from_address.to_script_pub_key()
scripts = [input_script]
# 创建交易输出
amount_to_send = 0.00029000
txout = TxOutput(
to_satoshis(amount_to_send),
to_address.to_script_pub_key()
)
# 创建交易(启用 segwit)
tx = Transaction([txin], [txout], has_segwit=True)
print("\n未签名的交易:")
print(tx.serialize())
print("\nTxId:", tx.get_txid())
# 签名交易
sig = from_private_key.sign_taproot_input(
tx,
0,
scripts,
amounts
)
# 添加见证数据
tx.witnesses.append(TxWitnessInput([sig]))
# 获取签名后的交易
signed_tx = tx.serialize()
print("\n已签名的交易:")
print(signed_tx)
print("\n交易信息:")
print(f"从地址 (P2TR): {from_address.to_string()}")
print(f"到地址 (P2TR): {to_address.to_string()}")
print(f"发送金额: {amount_to_send} BTC")
print(f"手续费: {input_amount - amount_to_send} BTC")
print(f"交易大小: {tx.get_size()} bytes")
print(f"虚拟大小: {tx.get_vsize()} vbytes")
print("\n您可以在这里广播交易:")
print("https://mempool.space/testnet/tx/push")
if __name__ == "__main__":
main()
结果是这样的:
https://mempool.space/testnet/tx/a3b4d0382efd189619d4f5bd598b6421e709649b87532d53aecdc76457a42cb6
让我分析这笔Taproot到Taproot的交易结构和对应关系。Taproot是比特币最新的脚本类型,采用了Schnorr签名技术。
Taproot交易解析
这里我们看到从一个Taproot地址 (tb1pjyjeruun8pc5ln3wtv2d6lsxqn55frpyc83kn473h7848d0kj73sxy3ku8) 发送到另一个Taproot地址 (tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h)。
输入分析
输入部分:
地址:tb1pjyjeruun8pc5ln3wtv2d6lsxqn55frpyc83kn473h7848d0kj73sxy3ku8
前序输出脚本:
OP_PUSHNUM_1 OP_PUSHBYTES_32 912591f39338714fce2e5b14dd7e0604e9448c24c1e369d7d1bf8f53b5f697a3
前序输出类型:V1_P2TR
见证数据分析
这是Taproot的关键部分:
7d25fbc9b98ee0eb09ed38c2afc19127465b33d6120f4db8d4fd46e532e30450d7d2a1f1dd7f03e8488c434d10f4051741921d695a44fb774897020f41da99f3
与SegWit交易不同,Taproot的见证数据只有一个组件:单个Schnorr签名。这是Taproot的"密钥路径花费"方式。
Schnorr签名具有64字节的固定长度,没有DER编码格式,这使得交易更紧凑高效。
对应关系详解
从SegWit到Taproot的演变:
前序交易来自SegWit地址,输出到了第一个Taproot地址
SegWit使用了传统的ECDSA签名:
30440220212d4a6f059e1f9...
和公钥:02898711e6bf63f5...
Taproot则使用了单一的Schnorr签名:
7d25fbc9b98ee0eb...
Taproot的内部密钥:
Taproot地址中的32字节值(
912591f39338714fce2e5b14dd7e0604e9448c24c1e369d7d1bf8f53b5f697a3
)不是公钥哈希,而是直接的"输出密钥"这个输出密钥是内部密钥(可能与调整因子结合)的修改版本
验证过程:
比特币节点验证Taproot交易时:
提取输出密钥 (
912591f39338714f...
)使用Schnorr签名验证算法验证签名 (
7d25fbc9b98ee0eb...
)验证成功后允许花费

总结一下Taproot交易相比SegWit的主要编程区别:
# 1. 地址生成方式不同
# SegWit: P2wpkhAddress
from_address = P2wpkhAddress(...)
# Taproot: 需要先获取公钥,再获取Taproot地址
from_pub = from_private_key.get_public_key()
from_address = from_pub.get_taproot_address()
# 2. 签名方法改变
# SegWit: sign_segwit_input()
sig = private_key.sign_segwit_input(tx, 0, script_code, amount)
# Taproot: sign_taproot_input()
sig = private_key.sign_taproot_input(
tx,
0, # 输入索引
scripts, # 所有输入的脚本列表
amounts # 所有输入的金额列表
)
# 3. witness数据结构更简单
# SegWit: [signature, public_key]
tx.set_witness(0, [sig, pubkey])
# Taproot: 只需要签名
tx.witnesses.append(TxWitnessInput([sig]))
# 4. 交易创建时同样需要segwit标志
tx = Transaction([txin], [txout], has_segwit=True)
# 5. script_sig同样为空
txin.script_sig = Script([]) # 这点与SegWit相同
地址格式更简洁(以 'tb1p' 开头)
签名数据更精简(不需要公钥)
整体结构更优化

Last updated