第5章:Taproot:Bitcoin 脚本系统的演化#
参考:
examples/ch05_simple_taproot.py
最后更新: 2025-12-06
Taproot 代表了 Bitcoin 脚本演化的顶点,展示了最复杂的智能合约如何看起来与简单支付完全相同。这种革命性方法将 Schnorr 签名与密码学密钥调整(key tweaking)相结合,创建 Bitcoin 最先进和私密的授权系统。
5.1 Taproot 承诺:统一隐私#
Taproot 的根本突破是支付统一性。无论交易代表:
简单的单签名支付
复杂的多方合约
Lightning Network 通道
具有多个授权级别的企业资金库
它们在花费之前在区块链上看起来完全相同。这种统一性由两个数学创新实现:Schnorr 签名和密钥调整。
5.2 Schnorr 签名:数学基础#
在理解 Taproot 架构之前,我们需要掌握使一切成为可能的数学优雅:Schnorr 签名及其变革属性,这些属性革命性地改变了 Bitcoin 的授权系统。
为什么选择 Schnorr?ECDSA 的局限性#
Bitcoin 最初使用 ECDSA(椭圆曲线数字签名算法)进行数字签名,但这一选择带来了 Schnorr 完全消除的重大局限性:
ECDSA 问题:
可延展性:签名可以在不使其失效的情况下被修改
无法聚合:多个签名无法组合
更大尺寸:签名通常为 71-72 字节
复杂验证:需要更多计算资源
无线性性:数学运算不保持关系
Schnorr 的革命性优势:
不可篡改:在 BIP340 下,确定性 nonce、x-only 公钥和严格编码规则消除了 ECDSA 中看到的第三方可延展性向量
密钥聚合:多个公钥可以组合为一个
单签名输出:产生单个聚合签名
紧凑尺寸:固定 64 字节签名
高效验证:更快更简单的验证过程
数学线性性:支持高级密码学构造
改变游戏规则的属性:线性性#
使 Taproot 成为可能的数学突破是 Schnorr 的线性性属性:
If Alice has signature A for message M
And Bob has signature B for the same message M
Then A + B creates a valid signature for (Alice + Bob)'s combined key
这个简单的数学关系支持三个革命性能力:
密钥聚合:多个人可以将他们的公钥组合为一个
单签名输出:多方可以协作产生一个统一的签名
密钥调整:密钥可以通过承诺确定性修改
注意:”单签名输出”指的是通过 MuSig2(钱包级协议)在链上产生一个 BIP340 签名,而不是跨输入的共识级签名聚合。
视觉对比:ECDSA vs Schnorr#
ECDSA Multisig (3-of-3):
┌─────────────────────────────────────┐
│ Transaction │
├─────────────────────────────────────┤
│ Alice Signature: [71 bytes] │
│ Bob Signature: [72 bytes] │
│ Charlie Signature: [70 bytes] │
├─────────────────────────────────────┤
│ Total Size: ~213 bytes │
│ Verifications: 3 separate │
│ Privacy: REVEALS 3 participants │
│ Appearance: 👥👥👥 (obviously multi) │
└─────────────────────────────────────┘
Schnorr Aggregated (3-of-3):
┌─────────────────────────────────────┐
│ Transaction │
├─────────────────────────────────────┤
│ Aggregated Signature: [64 bytes] │
├─────────────────────────────────────┤
│ Total Size: 64 bytes │
│ Verifications: 1 single check │
│ Privacy: REVEALS nothing about # │
│ Appearance: 👤 (looks like single) │
└─────────────────────────────────────┘
隐私魔法:
External Observer sees:
┌─────────────────┬─────────────────┐
│ Transaction A │ Transaction B │
├─────────────────┼─────────────────┤
│ 64-byte signature│ 64-byte signature│
│ Looks like: 👤 │ Looks like: 👤 │
└─────────────────┴─────────────────┘
Reality:
┌─────────────────┬─────────────────┐
│ Transaction A │ Transaction B │
├─────────────────┼─────────────────┤
│ Actually: 👤 │ Actually: 👥👥👥 │
│ (1 person) │ (3 people) │
└─────────────────┴─────────────────┘
🔮 Impossible to distinguish from outside!
5.3 密钥调整:通往 Taproot 的桥梁#
Taproot 通过密钥调整(key tweaking)(在 BIP340/341/342 哲学中也称为可调整承诺(tweakable commitment))利用 Schnorr 的线性性。
概念上:
t = H("TapTweak" || internal_pubkey || merkle_root)
形式上(BIP341):
t = int(HashTapTweak(xonly_internal_key || merkle_root_or_empty)) mod n
P' = P + t * G
d' = d + t
Even-Y 要求(BIP340):
Taproot 使用 x-only 公钥——但 secp256k1 上的实际点仍然有两个可能的 y 值(偶数/奇数)。
BIP340 规则是:最终调整的输出密钥必须对应于 even-y 点。
如果点最终是 odd-y,实现将私钥翻转为 d' = n − d',使得 P' = d'*G 落在偶数分支上。
(为什么这很重要:在 script-path 支出中,这个奇偶性被编码到控制块的最低位。如果现在不跟踪这个,script-path 以后将无法验证。)
密钥调整结构的视觉表示#
Internal Key (P) ─────────► + tweak ─────────► Output Key (P')
▲ │
│ │
Merkle Root ◄────────────────┘
script_path_commitment
密钥关系图:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Internal Key │ │ Tweak Value │ │ Output Key │
│ (P) │ │ t = H(P||M) │ │ (P') │
│ │───►│ │───►│ │
│ User's original │ │ Deterministic │ │ Final address │
│ private key │ │ from commit │ │ seen on chain │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ ▲ │
│ │ │
└─── Can compute d' ─────┘ │
│
┌─────────────────────────┘
│
▼
┌─────────────────┐
│ Merkle Root │
│ (M) │
│ │
│ Commitment to │
│ all possible │
│ spending paths │
└─────────────────┘
其中:
P= 内部密钥(Internal Key)(原始公钥,用户控制)M= Merkle 根(Merkle Root)(对所有可能花费条件的承诺)t= 调整值(Tweak Value)(从 P 和 M 确定性计算)P'= 输出密钥(Output Key)(最终 Taproot 地址,出现在区块链上)d'= 调整后的私钥(Tweaked Private Key)(用于 key-path 支出)
这种数学关系确保:
任何人都可以从 P 和承诺计算 P’(给定内部密钥 P 和(可选)Merkle 根 M)
只有密钥持有者可以从 d 和调整值计算 d’
关系 d’ × G = P’ 得到保持(签名验证有效)
关键代码#
# Key-path-only: tree 为空,t = HashTapTweak(internal_pubkey || b''), P' = P + t*G
address = pubkey.get_taproot_address([])
# 示例 1: Key-path-only Taproot 地址(btcaaron)
# 参考: examples/ch05_simple_taproot.py
from btcaaron import Key, TapTree
sender = Key.from_wif("cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT")
program = TapTree(internal_key=sender).build()
print("=== KEY-PATH-ONLY TAPROOT 地址 ===")
print(f"内部密钥 (x-only): {sender.xonly}")
print(f"Taproot 地址: {program.address}")
print(f"叶子脚本: {program.leaves}(空)")
print("btcaaron 内部完成: t=HashTapTweak(x-only||merkle_root), P'=P+t×G")
=== KEY-PATH-ONLY TAPROOT 地址 ===
内部密钥 (x-only): 898711e6bf63f5cbe1b38c05e89d6c391c59e9f8f695da44bf3d20ca674c8519
Taproot 地址: tb1pjyjeruun8pc5ln3wtv2d6lsxqn55frpyc83kn473h7848d0kj73sxy3ku8
叶子脚本: [](空)
btcaaron 内部完成: t=HashTapTweak(x-only||merkle_root), P'=P+t×G
密钥调整的关键洞察:
双重花费路径:调整后的密钥创建两种花费方法:
Key Path:使用调整后的私钥直接签名(协作)
Script Path:揭示内部公钥并证明脚本执行(回退)
密码学绑定:调整值将输出密钥密码学绑定到特定脚本承诺
确定性验证:任何人都可以验证调整后的密钥正确承诺到特定条件
通过不可区分性实现隐私:调整后的公钥在数学上与任何其他 Schnorr 公钥不可区分
5.4 为什么这能实现统一外观#
Schnorr 签名和密钥调整的结合创造了”统一外观”魔法:
Simple Payment:
├── Internal Key: Just a regular private key
├── Script Commitment: Empty (no conditions)
├── Tweaked Key: Internal key + H(key || empty)
└── Spending: 64-byte Schnorr signature
Complex Contract:
├── Internal Key: Same regular private key
├── Script Commitment: Merkle root of 100 conditions
├── Tweaked Key: Internal key + H(key || merkle_root)
└── Spending: 64-byte Schnorr signature (if cooperative)
🔍 External View: IDENTICAL 64-byte signatures!
5.5 简单 Taproot 交易:整合所有内容#
现在让我们通过基本的 Taproot 到 Taproot 交易看看这在实践中如何工作:
# 示例 2: 简单 Taproot 交易(btcaaron)
# 参考: examples/ch05_simple_taproot.py
from btcaaron import Key, TapTree
sender = Key.from_wif("cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT")
program = TapTree(internal_key=sender).build()
tx = (program.keypath()
.from_utxo("b0f49d2f30f80678c6053af09f0611420aacf20105598330cb3f0ccb8ac7d7f0", 0, sats=29200)
.to("tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h", 29000)
.sign(sender)
.build())
print("=== TAPROOT 交易 ===")
print(f"从: {program.address}")
print(f"到: tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h")
print(f"金额: 29,000 sats (手续费: 200 sats)")
print(f"TXID: {tx.txid}")
print()
print("见证: 64 字节 Schnorr 签名,与任何 Taproot 支付无法区分")
=== TAPROOT 交易 ===
从: tb1pjyjeruun8pc5ln3wtv2d6lsxqn55frpyc83kn473h7848d0kj73sxy3ku8
到: tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h
金额: 29,000 sats (手续费: 200 sats)
TXID: 2a1312702ca1bb5f69d4099dd9f1da74c9601e885512e19a99ef018544c47c24
见证: 64 字节 Schnorr 签名,与任何 Taproot 支付无法区分
关键观察:
Taproot 地址生成:
get_taproot_address()自动应用调整过程Schnorr 签名:
sign_taproot_input()产生 64 字节签名最小见证:见证栈只需签名(SIGHASH_DEFAULT 时 64 字节)
相同外观:与任何 Taproot 交易不可区分
5.6 真实交易分析#
TXID: a3b4d0382efd189619d4f5bd598b6421e709649b87532d53aecdc76457a42cb6
Input: ScriptPubKey OP_1 912591f3...5f697a3, Witness [7d25fbc9...da99f3]
Output: tb1p53ncq9...
见证:64 字节 Schnorr 签名(r 32B + s 32B),无公钥。
5.7 Taproot 栈执行(Key Path 简要)#
│ (empty) │ → OP_1 → │ 912591f3...5f697a3 (output_key) │
└───────────┘ └───────────────────────────────────┘
→ 见证压栈 → │ 7d25fbc9...da99f3 (schnorr_signature) │
│ 912591f3...5f697a3 (output_key) │
└───────────────────────────────────────┘
→ Schnorr 验证 → │ 1 (TRUE) │
5.8 不可区分性#
视觉对比#
Legacy P2PKH:
├── ScriptPubKey: OP_DUP OP_HASH160 <20-byte-hash> OP_EQUALVERIFY OP_CHECKSIG
├── ScriptSig: <signature> <public_key>
└── Size: ~225 bytes
Information Revealed: Single signature spending
SegWit P2WPKH:
├── ScriptPubKey: OP_0 <20-byte-hash>
├── Witness: [signature, public_key]
└── Size: ~165 bytes
Information Revealed: Single signature spending
Taproot P2TR (Simple):
├── ScriptPubKey: OP_1 <32-byte-output-key>
├── Witness: [schnorr_signature]
└── Size: ~135 bytes
Information Revealed: Nothing about internal complexity
Taproot P2TR (Complex Contract):
├── ScriptPubKey: OP_1 <32-byte-output-key>
├── Witness: [schnorr_signature]
└── Size: ~135 bytes
Information Revealed: Nothing about internal complexity
魔法:简单和复杂的 Taproot 交易在花费之前完全不可区分!
# 可运行:解析 64 字节 Schnorr 签名为 r/s(标准库)
sig_hex = "7d25fbc9b98ee0eb09ed38c2afc19127465b33d6120f4db8d4fd46e532e30450d7d2a1f1dd7f03e8488c434d10f4051741921d695a44fb774897020f41da99f3"
sig = bytes.fromhex(sig_hex)
r, s = sig[:32], sig[32:]
print(f"r ({len(r)}B): {r.hex()[:16]}...{r.hex()[-8:]}")
print(f"s ({len(s)}B): {s.hex()[:16]}...{s.hex()[-8:]}")
5.9 编程差异:SegWit vs Taproot#
# SegWit P2WPKH: address = pk.get_segwit_address(); witness = [sig, pubkey]
# Taproot P2TR: address = pubkey.get_taproot_address([]); witness = [sig]
要点:Taproot 地址需公钥、见证只需签名(无公钥)。
5.10 协作优势#
Taproot 为协作创造强大激励:
Cooperative Spending (Key Path):
├── Parties: Alice, Bob, Charlie (all agree)
├── Witness: [64-byte signature]
├── Size: ~135 bytes
├── Privacy: Maximum (looks like single-sig)
└── Efficiency: Optimal
Non-Cooperative Spending (Script Path):
├── Parties: Alice, Bob, Charlie (dispute)
├── Witness: [script_data, revealed_script, control_block]
├── Size: ~200-500 bytes
├── Privacy: Partial (reveals one condition)
└── Efficiency: Reduced but still functional
经济激励:
协作奖励:更小的费用,更好的隐私
冲突成本:更大的交易,减少隐私
对齐:技术优化与经济协作对齐
章节总结#
Taproot 通过两个关键数学创新代表了 Bitcoin 交易的范式转变:
Schnorr 签名:线性性属性支持密钥聚合、单签名输出,最重要的是密钥调整。这创造了固定 64 字节签名,可以表示任何复杂程度,同时看起来完全相同。
密钥调整(可调整承诺):数学关系 P' = P + t×G 允许密钥通过脚本承诺确定性修改,创建双重花费路径,同时保持密码学安全性。
结果:复杂的智能合约在计算和观察上与简单支付完全相同,在不牺牲功能的情况下提供前所未有的隐私。
隐私革命:所有 Taproot 交易在花费之前看起来完全相同,使得无法区分:
简单的单签名支付
复杂的多方合约
Lightning Network 操作
企业资金库交易
效率提升:
更小的交易大小(64 字节签名)
更快的验证(单签名检查)
减少区块链膨胀(未使用的条件保持私有)
协作激励:Taproot 使经济激励与技术优化对齐——协作成为最有效的选择。
随着密钥调整奠定密码学基础,下一步是探索任意智能合约条件如何在 Merkle 树内紧凑承诺——在揭示之前保持不可见。
在下一章中,我们将探索 Merkle 树如何在统一外观背后组织复杂的脚本条件,展示无限的花费条件如何被承诺和证明,而不揭示未使用的替代方案。