5.1 单叶子节点的Taproot编程

场景设计

想象一下,Alice想安全地发送一些测试网比特币(tBTC),并内置灵活性。她使用一个Taproot地址作为中间人——我们称之为“中间地址”——这个地址可以用两种方式解锁:

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

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

计划如下,由两部分或两笔交易组成:

  1. Commit Phase(提交阶段): Alice将资金发送到中间地址。

  2. Reveal Phase(揭示阶段):

    • Alice可以用她的私钥解锁(Key Path)。

    • Bob可以提供"helloworld"解锁(Script Path)。

为何叫Path?因为这两种路径,需要组成一个树的数据结构,有叶子、有默克尔根。让我们深入细节吧。

Alice的地址和提交交易(Commit Transaction)

在我们的实验中,我们使用比特币测试网和bitcoinutils库。Alice的密钥对是:

  • 私钥: cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT

  • 公钥: 0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3

  • Alice的Taproot地址: tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne

很快复习一下?私钥、公钥、地址的关系?

私钥、公钥和地址是比特币世界的“三兄弟”,它们层层递进,关系紧密又各司其职:

  1. 私钥(Private Key):

    • 是什么:一个随机的 32 字节(256 位)数字,比如 Alice 的 cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT 是 WIF 格式(Wallet Import Format),解码后是纯 256 位数字。

    • 作用:像你的“银行卡密码”,用来签名交易,证明你有权动用资金。

    • 特点:绝密,只能 Alice 自己知道,泄露就等于丢钱。

  2. 公钥(Public Key):

    • 是怎么来的:从私钥通过椭圆曲线加密(ECDSA,secp256k1 曲线)计算得出,公式是 P = d × G,其中 d 是私钥,G 是生成点。

    • 是什么:一个 33 字节(压缩格式)的数字,Alice 的公钥是 0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3(前缀 02 表示 y 坐标的奇偶性)。

    • 作用:像“银行卡号”,公开给别人,证明资金归属。

  3. 地址(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/testnetarrow-up-right 去查看。

相关的代码如下

两种花费路径揭示(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是解锁条件。

  • 验证过程:

    1. 网络用 50be5fc4...(P)和脚本的哈希(T)计算 H(P || T)。

    2. 算出 Q = P + H(P || T) × G,结果是 a46780148be98aaa861ad0b5dfc5c9b935d515c7be8c9e2bc6cedfa594e2b6d9。

    3. 和地址的 ScriptPubKey 比对,匹配!证明脚本属于 tb1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h(中间地址),解锁合法。

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

Last updated