5.1 单叶子节点的Taproot编程
场景设计
想象一下,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公钥:
0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3Alice的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 去查看。
相关的代码如下
两种花费路径揭示(Reveal Transaction)
Taproot的魅力在于它的双重解锁路径。我们用Python实现了两种方式:
1 . Script Path 揭示
Bob(或任何人)使用前像"helloworld"满足脚本条件,把钱花了。前像的英文是preimage,就是提供解锁的秘密的意思。
结果:
TxId:
68f7c8f0ab6b3c6f7eb037e36051ea3893b668c26ea6e52094ba01a7722e604f输出: tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne (4000 聪)。这里稍微有点歧义,大家看花回给Alice了?其实在代码里,在输出部分,Bob完全可以填一个别的地址。在我们的例子里,为了支付方便,Bob提供preimage以后,又支付回去了。反正就是把前面前面所在tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h的钱,5000聪给花了。
手续费: 1000 聪
状态: 成功
代码如下:
运行结果如下
我们来解析一下链上数据的情况。
就是helloword的Hex,而
就是的脚本
的内容。整个堆栈其实就是验证对helloworld的SHA256是否等于
而我们在运行结果已经看到了确实相等。
而
就是Control block。证明这个脚本,确实在这个地址对应的默克尔数上。
2、Key Path 揭示
Alice使用她的私钥直接花费资金,绕过脚本条件。在我们的例子里,我们让Alice tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne又支付了一笔钱到中间地址,这里交易ID是f4a0233be3dcdc3764646c3d2c7d02e29d38b3449aca98234a91e08a9c96a228。而就是中间地址现在有3000聪。
我们来看看代码:
结果:
TxId:
2a13de71b3eb9c5845bc9aed56de0efd7d8f1e5e02debb0e9b3464a4ad940d05输出: tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne(2000 聪)
手续费: 1000 聪
状态: 成功
这个看起来就是一个很普通的签名+公钥的交易
就是alice的签名。签名解锁了这个脚本
或者
而这个脚本,就是公钥
tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h
对应的Scriptpubkey。
解释一下工作的原理以及区块链浏览器上的链上数据解读
那这两个路径是怎么“合二为一”,变成一个普通地址的呢?
简单来说,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地址的编程。
Last updated