5.2 双叶子节点的Taproot编程

场景设计

Alice想安全地发送一些测试网比特币到中间地址,这个地址可以用三种方式解锁:

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

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

  • Script Path(脚本路径)2:Bob 使用他的私钥签名也可以解锁

提交交易(Commit)

提交交易(Commit): Alice将资金发送到这个中间地址。这次中间地址产生,所需要的是Alice的内部公钥和更复杂的双叶子脚本Tweak所产生的。这具体在我们的代码中是这样的:首先哈希锁脚本是

 hash_script = Script([
        'OP_SHA256',
        preimage_hash,
        'OP_EQUALVERIFY',
        'OP_TRUE'
    ])

Bob的签名脚本是

bob_script = Script([
        bob_public.to_x_only_hex(),
        'OP_CHECKSIG'
    ])

两者合成默克尔数:

all_leafs = [hash_script, bob_script]

再产生的Taproot地址

taproot_address = alice_public.get_taproot_address(all_leafs)

我们知道,这里用了库函数的get_taproot_address方法,背后其实就是Q = P + H(P || T) × G

我们的代码如下:

"""
Bob + HTLC 双叶子脚本测试 - Commit 脚本

创建一个包含 hash script (helloworld) 和 Bob script 的双脚本 Taproot 地址
这样我们可以测试 Bob Script Path 是否正确工作

=== 脚本树结构 ===
简单的双叶子树:
     ROOT
    /    \
   /      \
HASH     BOB
(hello  (P2PK)
 world)   

三种花费方式:
1. Script Path 1:任何人提供 preimage "helloworld" 来花费
2. Script Path 2:Bob 使用私钥签名来花费
3. Key Path:Alice 用私钥直接花费
"""

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 的密钥(内部密钥,Key Path)
    alice_private = PrivateKey('cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT')
    alice_public = alice_private.get_public_key()
    
    # Bob 的密钥(Script Path 2)
    bob_private = PrivateKey('cSNdLFDf3wjx1rswNL2jKykbVkC6o56o5nYZi4FUkWKjFn2Q5DSG')
    bob_public = bob_private.get_public_key()
    
    # 创建 preimage 的哈希(Script Path 1)
    preimage = "helloworld"
    preimage_bytes = preimage.encode('utf-8')
    preimage_hash = hashlib.sha256(preimage_bytes).hexdigest()
    
    # Script Path 1: 哈希锁脚本 - 验证 preimage
    hash_script = Script([
        'OP_SHA256',
        preimage_hash,
        'OP_EQUALVERIFY',
        'OP_TRUE'
    ])
    
    # Script Path 2: Bob 的签名脚本 - P2PK
    bob_script = Script([
        bob_public.to_x_only_hex(),
        'OP_CHECKSIG'
    ])
    
    print("=== Bob + Hash 双脚本 Taproot 地址信息 ===")
    print(f"Alice 私钥 (Key Path): {alice_private.to_wif()}")
    print(f"Alice 公钥 (Internal Key): {alice_public.to_hex()}")
    print(f"Bob 私钥 (Script Path 2): {bob_private.to_wif()}")
    print(f"Bob 公钥 (Script Path 2): {bob_public.to_hex()}")
    print(f"Bob 公钥 (x-only): {bob_public.to_x_only_hex()}")
    print(f"Preimage (Script Path 1): {preimage}")
    print(f"Preimage Hash: {preimage_hash}")
    
    # 按照验证的双脚本模式创建脚本树:平铺结构
    all_leafs = [hash_script, bob_script]
    
    # 创建 Taproot 地址
    taproot_address = alice_public.get_taproot_address(all_leafs)
    
    print(f"\nTaproot 地址: {taproot_address.to_string()}")
    
    print(f"\n=== 脚本路径信息 ===")
    print(f"Hash Script (索引 0): {hash_script}")
    print(f"Bob Script (索引 1): {bob_script}")
    
    print(f"\n=== 使用说明 ===")
    print("1. 向这个 Taproot 地址发送比特币")
    print("2. 可以通过以下三种方式花费:")
    print("   - Key Path: Alice 使用她的私钥直接签名")
    print("   - Script Path 1: 任何人提供正确的 preimage 'helloworld'")
    print("   - Script Path 2: Bob 使用他的私钥签名")
    
    print(f"\n=== 脚本树结构 ===")
    print("简单的双叶子树:")
    print("     ROOT")
    print("    /    \\")
    print("   /      \\")
    print("HASH     BOB")
    print("(hello  (P2PK)")
    print(" world)    ")
    
    # 验证 Control Block 构造
    from bitcoinutils.utils import ControlBlock
    
    print(f"\n=== Control Block 预览 ===")
    
    # Hash Script Control Block (索引 0)
    hash_cb = ControlBlock(alice_public, all_leafs, 0, is_odd=taproot_address.is_odd())
    print(f"Hash Script Control Block: {hash_cb.to_hex()}")
    
    # Bob Script Control Block (索引 1) 
    bob_cb = ControlBlock(alice_public, all_leafs, 1, is_odd=taproot_address.is_odd())
    print(f"Bob Script Control Block: {bob_cb.to_hex()}")
    
    print(f"\n✅ 准备就绪!向地址 {taproot_address.to_string()} 发送测试币后,")
    print("就可以测试 Bob Script Path 是否正确工作了!")

if __name__ == "__main__":
    main()

代码运行后产生了一个中间地址

tb1p93c4wxsr87p88jau7vru83zpk6xl0shf5ynmutd9x0gxwau3tngq9a4w3z

整体代码的运行结果如下:

=== Bob + Hash 双脚本 Taproot 地址信息 ===
Alice 私钥 (Key Path): cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT
Alice 公钥 (Internal Key): 0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3
Bob 私钥 (Script Path 2): cSNdLFDf3wjx1rswNL2jKykbVkC6o56o5nYZi4FUkWKjFn2Q5DSG
Bob 公钥 (Script Path 2): 0284b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5
Bob 公钥 (x-only): 84b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5
Preimage (Script Path 1): helloworld
Preimage Hash: 936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af

Taproot 地址: tb1p93c4wxsr87p88jau7vru83zpk6xl0shf5ynmutd9x0gxwau3tngq9a4w3z

=== 脚本路径信息 ===
Hash Script (索引 0): ['OP_SHA256', '936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af', 'OP_EQUALVERIFY', 'OP_TRUE']
Bob Script (索引 1): ['84b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5', 'OP_CHECKSIG']

=== 使用说明 ===
1. 向这个 Taproot 地址发送比特币
2. 可以通过以下三种方式花费:
   - Key Path: Alice 使用她的私钥直接签名
   - Script Path 1: 任何人提供正确的 preimage 'helloworld'
   - Script Path 2: Bob 使用他的私钥签名

=== 脚本树结构 ===
简单的双叶子树:
     ROOT
    /    \
   /      \
HASH     BOB
(hello  (P2PK)
 world)    

=== Control Block 预览 ===
Hash Script Control Block: c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d32faaa677cb6ad6a74bf7025e4cd03d2a82c7fb8e3c277916d7751078105cf9df
Bob Script Control Block: c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3fe78d8523ce9603014b28739a51ef826f791aa17511e617af6dc96a8f10f659e

✅ 准备就绪!向地址 tb1p93c4wxsr87p88jau7vru83zpk6xl0shf5ynmutd9x0gxwau3tngq9a4w3z 发送测试币后,
就可以测试 Bob Script Path 是否正确工作了!

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

1. Script Path 1- 用preimage解开哈希锁

关键代码片段,control block 的写法:

control_block = ControlBlock(
        alice_public,           # internal_pub
        all_leafs,             # all_leafs
        0,                     # script_index (hash_script 是第 0 个)
        is_odd=taproot_address.is_odd()
    )

见证数据的构成:

tx.witnesses.append(TxWitnessInput([
        preimage_hex,              # preimage 的十六进制
        hash_script.to_hex(),      # 脚本的十六进制
        control_block.to_hex()     # 控制块的十六进制
    ]))

注意Script Path 花费 - 不需要签名,只需要提供 preimage。

完整的代码如下:

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
import hashlib

def main():
    setup('testnet')
    
    # Alice 的密钥(内部密钥)
    alice_private = PrivateKey('cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT')
    alice_public = alice_private.get_public_key()
    
    # Bob 的密钥
    bob_private = PrivateKey('cSNdLFDf3wjx1rswNL2jKykbVkC6o56o5nYZi4FUkWKjFn2Q5DSG')
    bob_public = bob_private.get_public_key()
    
    # Preimage 和哈希
    preimage = "helloworld"
    preimage_hex = preimage.encode('utf-8').hex()
    preimage_hash = hashlib.sha256(preimage.encode('utf-8')).hexdigest()
    
    # 重建脚本(必须与 commit 时完全相同)
    hash_script = Script(['OP_SHA256', preimage_hash, 'OP_EQUALVERIFY', 'OP_TRUE'])
    bob_script = Script([bob_public.to_x_only_hex(), 'OP_CHECKSIG'])
    
    # 重建脚本树
    all_leafs = [hash_script, bob_script]
    taproot_address = alice_public.get_taproot_address(all_leafs)
    
    print(f"=== Hash Script Path 解锁 ===")
    print(f"Taproot 地址: {taproot_address.to_string()}")
    print(f"Preimage: {preimage}")
    print(f"Preimage Hex: {preimage_hex}")
    print(f"使用脚本: {hash_script} (索引 0)")
    
    # 输入信息(需要替换为实际的 UTXO)
    commit_txid = "f02c055369812944390ca6a232190ec0db83e4b1b623c452a269408bf8282d66"  # 替换为实际的交易ID
    input_amount = 0.00001234  # 5000 satoshis,替换为实际金额
    output_amount = 0.00001034  # 4500 satoshis,扣除手续费
    
    # 构建交易
    txin = TxInput(commit_txid, 0)
    # 输出到 Alice 的简单 Taproot 地址
    txout = TxOutput(to_satoshis(output_amount), alice_public.get_taproot_address().to_script_pub_key())
    tx = Transaction([txin], [txout], has_segwit=True)
    
    # 创建 Control Block
    # hash_script 是索引 0
    control_block = ControlBlock(
        alice_public,           # internal_pub
        all_leafs,             # all_leafs
        0,                     # script_index (hash_script 是第 0 个)
        is_odd=taproot_address.is_odd()
    )
    
    print(f"Control Block: {control_block.to_hex()}")
    
    # Script Path 花费 - 不需要签名,只需要提供 preimage
    # 见证数据格式:[preimage, script, control_block]
    tx.witnesses.append(TxWitnessInput([
        preimage_hex,              # preimage 的十六进制
        hash_script.to_hex(),      # 脚本的十六进制
        control_block.to_hex()     # 控制块的十六进制
    ]))
    
    # 输出信息
    print(f"\n=== 交易信息 ===")
    print(f"Input Amount: {input_amount} tBTC ({to_satoshis(input_amount)} satoshis)")
    print(f"Output Amount: {output_amount} tBTC ({to_satoshis(output_amount)} satoshis)")
    print(f"Fee: {input_amount - output_amount} tBTC ({to_satoshis(input_amount - output_amount)} satoshis)")
    print(f"TxId: {tx.get_txid()}")
    print(f"Raw Tx: {tx.serialize()}")
    
    print(f"\n=== 验证 ===")
    # 验证 Control Block 是否与预期一致
    # 基于之前的成功经验,hash script (索引 0) 的 Control Block 应该是:
    expected_hash_cb = "c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d312273573f45d51a1c48a75e76fe1d955f9f00acb1fe288510ab242f0851a7bf5"
    our_cb = control_block.to_hex()
    
    print(f"我们的 Control Block: {our_cb}")
    print(f"预期 Control Block:   {expected_hash_cb}")
    print(f"Control Block 匹配: {'✅' if our_cb == expected_hash_cb else '❌'}")
    
    if our_cb != expected_hash_cb:
        print("⚠️  Control Block 不匹配,可能需要调整参数")
    else:
        print("✅ Control Block 正确,交易应该能成功!")
    
    print(f"\n📝 使用说明:")
    print("1. 替换 commit_txid 和 input_amount 为实际值")
    print("2. 任何知道 preimage 'helloworld' 的人都可以执行此花费")
    print("3. 不需要任何私钥,只需要知道 preimage")

if __name__ == "__main__":
    main()

运行的结果请见

https://mempool.space/testnet/tx/b61857a05852482c9d5ffbb8159fc2ba1efa3dd16fe4595f121fc35878a2e430

从mempool浏览器的链上数据上显示的上一节课程内容中我们熟悉的HTLC解锁的结构。

2. Script Path 2- 用Bob签名解开哈希锁

关键代码片段,control block 的写法:

  # 构造 Control Block(基于双 hashlock 成功经验,Bob Script 索引 = 1)
    control_block = ControlBlock(
        alice_public,           # internal_pub
        all_leafs,             # all_leafs
        1,                     # script_index (bob_script 是索引 1)
        is_odd=taproot_address.is_odd()
    )

签名部分的写法:

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
import hashlib

def main():
    setup('testnet')

    # Alice 和 Bob 的密钥
    alice_private = PrivateKey('cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT')
    alice_public = alice_private.get_public_key()
    bob_private = PrivateKey('cSNdLFDf3wjx1rswNL2jKykbVkC6o56o5nYZi4FUkWKjFn2Q5DSG')
    bob_public = bob_private.get_public_key()

    # 构造 hash_script 和 bob_script
    preimage = "helloworld"  # 实际使用时请替换
    preimage_hash = hashlib.sha256(preimage.encode('utf-8')).hexdigest()
    hash_script = Script(['OP_SHA256', preimage_hash, 'OP_EQUALVERIFY', 'OP_TRUE'])
    bob_script = Script([bob_public.to_x_only_hex(), 'OP_CHECKSIG'])

    # 构造 Taproot 地址(包含两个叶子)
    all_leafs = [hash_script, bob_script]
    taproot_address = alice_public.get_taproot_address(all_leafs)
    print(f"Taproot 地址: {taproot_address.to_string()}")
    print(f"Bob 公钥 (x-only): {bob_public.to_x_only_hex()}")
    print(f"Bob Script: {bob_script}")

    # UTXO 信息(请替换为实际 UTXO)
    commit_txid = "8caddfad76a5b3a8595a522e24305dc20580ca868ef733493e308ada084a050c"  # UTXO 的 txid
    vout = 1  # UTXO 的索引
    input_amount = 0.00001111  # 输入金额(BTC)
    output_amount = 0.00000900  # 输出金额(BTC),已扣除手续费

    # 构造交易
    txin = TxInput(commit_txid, vout)
    txout = TxOutput(to_satoshis(output_amount), bob_public.get_taproot_address().to_script_pub_key())
    tx = Transaction([txin], [txout], has_segwit=True)

    # 构造 Control Block(bob_script 索引为 1)
    control_block = ControlBlock(
        alice_public,
        all_leafs,
        1,  # bob_script 在 all_leafs 中的索引
        is_odd=taproot_address.is_odd()
    )
    print(f"Control Block: {control_block.to_hex()}")

    # 用 Bob 的私钥进行 Script Path 签名(标准写法)
    sig = bob_private.sign_taproot_input(
        tx, 0,
        [taproot_address.to_script_pub_key()],
        [to_satoshis(input_amount)],
        script_path=True,
        tapleaf_script=bob_script,
        tweak=False
    )
    print(f"签名: {sig}")

    # 构造见证数据
    tx.witnesses.append(TxWitnessInput([
        sig,
        bob_script.to_hex(),
        control_block.to_hex()
    ]))

    print(f"TxId: {tx.get_txid()}")
    print(f"Raw Tx: {tx.serialize()}")
    print("\n请将原始交易广播到 testnet 网络。")

if __name__ == "__main__":
    main() 

结果运行如下

https://mempool.space/testnet/tx/185024daff64cea4c82f129aa9a8e97b4622899961452d1d144604e65a70cfe0

解释一下链上各部分的含义:

① 签名

26a0eadca0bba3d1bb6f82b8e1f76e2d84038c97a92fa95cc0b9f6a6a59bac5f9977d7cb33dbd188b1b84e6d5a9447231353590578f358b2f18a66731f9f1c5c
  • 这是 Bob 用私钥 对该输入(包括 bob_script 作为 tapleaf)做的 Schnorr 签名。

  • 该签名证明了 Bob 拥有 bob_script 中公钥的私钥,且同意花费这笔 UTXO。

② 脚本(bob_script)

2084b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5ac
  • 20 开头,后面 32 字节是 Bob 的 x-only 公钥(84b595...63af5),ac 是 OP_CHECKSIG。

  • 反汇编为:

  OP_PUSHBYTES_32 84b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5
  OP_CHECKSIG
  • 这就是 Taproot 地址的第二个叶子脚本(bob_script),只有 Bob 能签名花费。

③ 控制块(Control Block)

c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3fe78d8523ce9603014b28739a51ef826f791aa17511e617af6dc96a8f10f659e
  • Control Block 证明 bob_script 是该 Taproot 地址的一个叶子。

  • 结构为:

    • 1 字节前缀(c0...,包含奇偶性和内部公钥信息)

    • 32 字节内部公钥(Alice 的 x-only 公钥)

    • 32 字节 Merkle sibling(另一个叶子的哈希,即 hash_script 的哈希)

    • 验证者用 Control Block + bob_script 可以重建 Taproot Merkle root,进而验证该脚本属于该地址。

3. Key Path - 用Alice签名解锁

这个跟上一节的类似,完整代码如下

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

def main():
    setup('testnet')
    
    # Alice 的密钥(内部密钥)
    alice_private = PrivateKey('cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT')
    alice_public = alice_private.get_public_key()
    
    # Bob 的密钥
    bob_private = PrivateKey('cSNdLFDf3wjx1rswNL2jKykbVkC6o56o5nYZi4FUkWKjFn2Q5DSG')
    bob_public = bob_private.get_public_key()
    
    # 重建脚本(Key Path 花费需要完整的脚本树信息来计算 tweak)
    preimage = "helloworld"
    preimage_hash = hashlib.sha256(preimage.encode('utf-8')).hexdigest()
    
    hash_script = Script(['OP_SHA256', preimage_hash, 'OP_EQUALVERIFY', 'OP_TRUE'])
    bob_script = Script([bob_public.to_x_only_hex(), 'OP_CHECKSIG'])
    
    # 重建脚本树
    all_leafs = [hash_script, bob_script]
    taproot_address = alice_public.get_taproot_address(all_leafs)
    
    print(f"=== Alice Key Path 解锁 ===")
    print(f"Taproot 地址: {taproot_address.to_string()}")
    print(f"Alice 私钥: {alice_private.to_wif()}")
    print(f"Alice 公钥: {alice_public.to_hex()}")
    print(f"花费方式: Key Path (最私密)")
    
    # 输入信息(需要替换为实际的 UTXO)
    commit_txid = "9fafbb99a88e75e2c023bd89d2c7ad7f55be7c615d99737700ed97636e7d069b"  # 替换为实际的交易ID
    input_amount = 0.00001266  # 5000 satoshis,替换为实际金额
    output_amount = 0.00001066  # 4500 satoshis,扣除手续费
    
    # 构建交易
    txin = TxInput(commit_txid, 0)
    # 输出到 Alice 的简单 Taproot 地址
    txout = TxOutput(to_satoshis(output_amount), alice_public.get_taproot_address().to_script_pub_key())
    tx = Transaction([txin], [txout], has_segwit=True)
    
    print(f"\n=== 交易构建 ===")
    print(f"Input: {commit_txid}:0")
    print(f"Output: {alice_public.get_taproot_address().to_string()}")
    
    # Alice 使用 Key Path 签名
    # Key Path 需要完整的脚本树信息来计算正确的 tweak
    sig = alice_private.sign_taproot_input(
        tx,
        0,
        [taproot_address.to_script_pub_key()],  # 输入的 scriptPubKey
        [to_satoshis(input_amount)],            # 输入金额
        script_path=False,                      # Key Path 花费
        tapleaf_scripts=all_leafs               # 完整的脚本树(用于计算 tweak)
    )
    
    print(f"Alice 签名: {sig}")
    
    # Key Path 花费的见证数据只包含签名
    tx.witnesses.append(TxWitnessInput([sig]))
    
    # 输出信息
    print(f"\n=== 交易信息 ===")
    print(f"Input Amount: {input_amount} tBTC ({to_satoshis(input_amount)} satoshis)")
    print(f"Output Amount: {output_amount} tBTC ({to_satoshis(output_amount)} satoshis)")
    print(f"Fee: {input_amount - output_amount} tBTC ({to_satoshis(input_amount - output_amount)} satoshis)")
    print(f"TxId: {tx.get_txid()}")
    print(f"Raw Tx: {tx.serialize()}")
    
    print(f"\n=== Key Path 特性 ===")
    print("✅ 只需要 Alice 的私钥")
    print("✅ 见证数据只有一个签名,最小化")
    print("✅ 外界无法知道还有其他花费路径(完美隐私)")
    print("✅ 与普通的单签名交易无法区分")
    print("✅ 手续费最低,因为见证数据最少")
    
    print(f"\n=== 见证数据分析 ===")
    print("Key Path 见证数据结构:")
    print("  [alice_signature]  <- 只有一个元素")
    print("")
    print("对比 Script Path 见证数据结构:")
    print("  [signature/preimage, script, control_block]  <- 三个元素")
    print("")
    print("这就是 Key Path 的优势:简洁、私密、高效!")
    
    print(f"\n📝 使用说明:")
    print("1. 替换 commit_txid 和 input_amount 为实际值")
    print("2. 只有 Alice 可以执行此花费")
    print("3. 这是最推荐的花费方式(如果 Alice 同意)")

if __name__ == "__main__":
    main()

运行结果见浏览器,一个典型的签名+公钥 解锁的链上结构:

https://mempool.space/testnet/tx/b11f27fdbe2323179260093f387a1ab5d5c1ea4b5524e2facd89813fe1daca8d

至此,我们掌握了更复杂的双叶子节点形成的taproot地址的编程。

Last updated