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编码格式,这使得交易更紧凑高效。

对应关系详解

  1. 从SegWit到Taproot的演变

    • 前序交易来自SegWit地址,输出到了第一个Taproot地址

    • SegWit使用了传统的ECDSA签名:30440220212d4a6f059e1f9...和公钥:02898711e6bf63f5...

    • Taproot则使用了单一的Schnorr签名:7d25fbc9b98ee0eb...

  2. Taproot的内部密钥

    • Taproot地址中的32字节值(912591f39338714fce2e5b14dd7e0604e9448c24c1e369d7d1bf8f53b5f697a3)不是公钥哈希,而是直接的"输出密钥"

    • 这个输出密钥是内部密钥(可能与调整因子结合)的修改版本

  3. 验证过程

    • 比特币节点验证Taproot交易时:

      1. 提取输出密钥 (912591f39338714f...)

      2. 使用Schnorr签名验证算法验证签名 (7d25fbc9b98ee0eb...)

      3. 验证成功后允许花费

总结一下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