私钥 → P2TR 地址(Taproot)
BIP86 key-path-only 的 Taproot bc1p… 地址,每一步都摊开给你看:点 P = k·G、x-only 内部公钥及其奇偶性规则、TapTweak tagged hash、调整后的点 Q = P + t·G、输出公钥 x(Q),以及 witness 版本 1 的 bech32m 编码。
请在上方输入内容后点击「转换」。不知道填什么?点「填入示例」即可载入占位符里的演示数据。
本页计算了什么
输入一个 256 位私钥,输出 BIP86 key path 的 Taproot 地址,也就是以 bc1p
开头的字符串。Taproot 并不是「程序更长的 SegWit」—— 公钥要先被一个由哈希得到的标量调整过,而地址承诺的
正是调整之后的结果:
结果面板会打印每一个中间值,包括奇偶性的判定、tagged hash 内部那两个 32 字节哈希,以及调整后的点本身。
1. Taproot 想解决什么
Taproot 是三个提案一起落地的,于 2021 年 11 月 14 日激活:
| BIP | 定义了什么 |
|---|---|
| BIP340 | secp256k1 上的 Schnorr 签名,定义在 32 字节的x-only公钥之上 |
| BIP341 | Taproot 输出与花费规则:输出公钥、TapTweak 调整、key path 与 script path |
| BIP342 | Tapscript —— script path 花费中所用的脚本语言 |
| BIP350 | bech32m,witness 版本 1 及以上地址必须使用的校验和变体 |
隐私。 在 Taproot 之前,付给一份合约的钱和付给一个人的钱,在链上长得不一样:输出承诺的是
脚本哈希,观察者一眼就能看出「这里发生了复杂的事」,甚至能猜出是什么。Taproot 让两者无法区分。所有输出
都长成 OP_1 <32 字节>。如果多签或通道的参与方能够合作,他们就只用 key path 上的一个签名
把钱花掉,链上呈现出来的和一笔普通的单签名付款一模一样。只有当合作失败时,script path 才会暴露 —— 而且
即便暴露,也只暴露真正用到的那一个分支。
用 Schnorr 取代 ECDSA。 Schnorr 签名(BIP340)是固定 64 字节;ECDSA 签名用 DER 编码, 长度在 70 到 72 字节之间浮动,而且编码本身是可塑的,SegWit 当初不得不绕开这个问题。更重要的是,Schnorr 是 线性的:签名之和就是「公钥之和」的合法签名。正是这一性质,让 MuSig 式的公钥聚合、门限签名和批量 验签无需任何取巧手段就能实现。
2. x-only 公钥:BIP340 为什么要丢掉 y
secp256k1 的点有两个坐标,但对任意一个 x,候选点只有两个:P 和
−P = (x, p − y)。其中必有一个的 y 是偶数。BIP340 规定:32 字节公钥的规范解释就是「x 等于该值、
且 y 为偶数的那个点」,这个函数叫 lift_x。丢掉 y 让每个签名少一个字节,也
消除了 ECDSA 拖了很多年的那层歧义。
实践中有两个后果值得牢记:
- 如果你的私钥
k得到的点 y 是奇数,BIP340 的签名方会悄悄改用n − k,因为(n − k)·G = −P,而−P才是同一个 x 对应的偶 y 代表元。以k = 6为例,该点的 y 是ae12777a…6b075f297(奇数),签名方必须 取负 —— 而地址依然是bc1p4rsld9ryjhte00drc0r23r8ngd63xrzh5s4fvmy6q5yt70xzlsdqcuvtzv。钱包里这一步写错, 表现就是「签名总是验不过」。 - 因为下面的 tweak 只依赖 x,
P与−P会得到同一个 Taproot 地址 —— 本页会把两者都算出来并展示它们相等 —— 但它们传统的 P2PKH 地址并不相同。x-only 不是压缩技巧, 它是另一套签名模型。
3. TapTweak 的构造,以及为什么必须用 tagged hash
输出公钥从来不是你推导出来的那个公钥本身,而是它加上生成元的一个标量倍:
对于 key-path-only 的地址(BIP86),Merkle 根被完全省略,输入就只有 32 字节的 x-only 内部公钥。本页实现的
正是这一种。结果 Q 就是输出公钥,而地址里只放它的 x 坐标。从地址上没有任何人能看出
是否存在脚本树 —— 这个承诺已经被内部公钥吸收进去了。
| 偏移 | 字节数 | 内容 |
|---|---|---|
| 0 | 32 | x(P) —— x-only 内部公钥 |
| 32 | 0 或 32 | 脚本树的 Taproot Merkle 根:BIP86 key-path-only 时不存在,有脚本树时为 32 字节 |
tagged hash 是 BIP340 给出的、从 SHA-256 派生专用哈希的标准套路:
标签的哈希被放在最前面两次。这多花一个压缩块的开销,换来的是域分离:为某个用途
算出的哈希,永远不可能与为另一个用途算出的哈希撞上。没有它,攻击者就可以尝试构造一个「既是签名随机数、
又是 TapTweak 输入」的值 —— 这是一片零成本就能关掉的跨协议攻击面。对于标签 "TapTweak",
SHA256("TapTweak") 等于
e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9。
加上 t·G 正是让脚本树变成可选的原因:树的 Merkle 根被折进公钥里,于是公钥本身
就是承诺。它同时意味着:你公布出去的公钥(内部公钥)并不是验签用的公钥(输出公钥)—— 如果有人设法替换了
内部公钥,算出的输出公钥就会不同,地址也随之不同。如果算出的 tweak ≥ n,或者 Q 落到无穷远点,
按 BIP341 这次构造就失败、必须重试;其概率约为 2−128。
4. key path 与 script path
花费一笔 Taproot 输出有两条路。
- key path。 用调整后的私钥给出一个签名即可。witness 里只有一个 64 字节(偶尔 65 字节) 的项。关于脚本、分支、参与方的任何信息都不会暴露。这是最便宜、最私密的方式,也是本 BIP86 页面唯一 构建的那一种。
- script path。 需要提供叶子脚本、它所需的数据,以及一个控制块(control block): 内部公钥,加上从该叶子一路到 Merkle 根的 32 字节兄弟哈希。验证者重算根、重做 tweak,并核对输出公钥是否 匹配。只有被用到的叶子会被揭示,其余分支全部保持隐藏 —— 这是 P2SH 多签永远做不到的,因为它会把整份 脚本都公布出来。
这也是本页只做 key path 的原因:单密钥钱包(BIP86)就是这么做的,而只有在这种形式下,地址才完全只依赖内部
公钥。如果你想要 script path,tweak 就必须带上树的 Merkle 根,于是 tweak 改变、输出公钥改变、地址也跟着改变。
举个量级上的例子:把 32 字节的假根 1111…11 代进去,演示私钥的 tweak 会从真实的
bae91685…85e8fa4d 变成
254264f6410f1b3890db1ee95ea9990b28875f4b3e06cf35dfd0d0a6dda6d04b,地址自然也就不同了。
地址是对某个特定输出公钥的承诺。如果你生成了 BIP86 地址,之后却想用 script path 去花它,那些脚本根本不在 承诺里,这笔花费是无效的。反过来说,如果你带着脚本树生成了地址、却始终只用 key path,币是安全的 —— 但这个 地址不会和「普通」钱包显示的一致,而这正是 BIP86 存在的理由:它钉死一种确定性的、不含脚本的构造方式, 让所有钱包口径一致。
5. bech32m,以及为什么 witness v1+ 必须用它
Taproot 地址使用 bech32m(BIP350)—— 字母表与结构与 bech32 相同,但校验和常量不同:
0x2bc830a3(734539939)而非 1。witness 版本 0 必须用 bech32,版本 1 及以上必须用
bech32m。不强制检查这种配对关系的解码器,会接受别的软件拒绝的字符串。
需要第二个变体的原因,是原始校验和存在一个插入弱点,BIP350 原文写得很直白:只要 bech32 字符串的最后
一个字符是 p,在它前面紧邻位置插入或删除任意多个 q 字符都不会破坏校验和。
校验字符会整体错位,于是同样的六个字符可以验证长度不同的数据部分 —— 同一串字符就能对应两个「不同的地址」。
这很容易演示,本页背后的服务器也演示过:合法地址
bc1qgey9e3xtk8fpdsgchch25saytdp6nlhdt9gm0p 在末尾 p 之前插入 1、2、3 个 q
之后(…gm0qp、…gm0qqp、…gm0qqqp),校验和依然有效。在针对
bc 前缀字符串所做的 78,624 次单字符插入尝试中,凡是校验和仍然成立的插入形状全部如此,而且
全部属于 bech32 —— 没有任何一个 bech32m 字符串接受了插入。对同样以 p 结尾的
bech32m 地址重复这个实验,校验和会直接失败,这正是换用新常量的意义所在。
witness 版本 0 之所以躲过了这个问题,是因为它的程序长度被限制为恰好 20 或 32 字节,执行结构检查的解码器会 拒绝被改动的字符串;而 witness v1+ 没有这层限制,Taproot 地址也更长,所以 BIP350 为它规定了修正后的常量。
把本页的 Taproot 地址用 bech32 常量重新编码,得到
bc1pmsrhxnvmj8tjxxu9wj69ntvwvhvr28qufyc26tshz3rjj777tx3szg9sln,解码会报错
「Witness v1+ must use bech32m, not bech32」。把演示用的 P2WPKH 地址用 bech32m 常量重新编码,得到
bc1qy86hkm0telzmvuvzm76279jp7z5p3yqjxpf0nc,解码会报错
「Witness v0 must use bech32, not bech32m」。两者只差最后六个字符 —— 这正是为什么校验和变体是
安全属性,而不是外观细节。
6. witness 程序与地址,逐字节拆解
一个 Taproot 输出脚本共 34 字节:一个版本操作码、一次 32 字节推送,以及输出公钥本身。
| 偏移 | 字节数 | 内容 | 含义 |
|---|---|---|---|
| 0 | 1 | 51 | OP_1 —— 推送数值 1,同时也表示 witness 版本 1 |
| 1 | 1 | 20 | 推送接下来的 32 字节(0x20 = 32) |
| 2 | 32 | x(Q) | x-only 调整后的输出公钥 —— 即 witness 程序 |
以及把它编码出来的地址:
| 部分 | 演示值 | 规则 |
|---|---|---|
hrp | bc | 主网 bc,测试网 tb |
| 分隔符 | 1 | 从最后一个 1 处切开;1 不在数据字符集里 |
| 版本字符 | p | p = 数值 1 = witness v1(q 是数值 0,P2WPKH 用它) |
| 程序字符 | msrhxnvm…777tx3s(52 个) | 256 位 ÷ 5 = 51.2,故为 52 个字:51 个整字再加 1 位、后面补 4 个 0 |
| 校验和 | h54u63 | 6 个字符,bech32m 常量 0x2bc830a3 |
| 总长度 | 62 个字符 | 2 + 1 + 53 + 6 |
对演示地址做解码,会得到 witver = 1、变体 bech32m,以及一个与结果面板里打印的
输出公钥完全一致的 32 字节程序 —— 这是真的往返验证,不是假设。
7. 实例演算 —— 真实的演示私钥
| 步骤 | 取值 |
|---|---|
私钥 k | 0824c314772b2c2859c889471390a36bf66e53a1ca1642f96f0f2c9307a171ca |
| P = k·G 的 x | ff812e26116a9aa140abe629e7f8a38401653caf925119def59ed3758c67ee80 |
| P = k·G 的 y | 8f49cde13c7ffd64f1d730bf87a743a578eda4971ddac4a08118160732b424ae |
| 奇偶性 | 最后一位十六进制是 e → 偶数,无需取负,内部公钥就等于 x(P) |
| 内部公钥(x-only) | ff812e26116a9aa140abe629e7f8a38401653caf925119def59ed3758c67ee80 |
SHA256("TapTweak") | e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9 |
tweak t | bae9168523281e7f9e91d897a8d23f7ae6fe66d61854019dcb8abb5185e8fa4d |
t·G 的 x | baa35582aa3a12f4c5fb2cace80671a765dfc95cee356d28b31eb757c52b94a8 |
t·G 的 y | be8ddfba65063b45db3fb0c2c14d909e0981d1f04cdcde251d5fb18eeda211ff |
| Q = P + t·G 的 x(输出公钥) | dc07734d9b91d7231b8574b459ad8e65d8351c1c4930ad2e171447297bde59a3 |
| Q = P + t·G 的 y | f54d5ae5bfeade97f7148ebd308fa916f268bde1bea6c64594e5baf904831284 |
| 输出脚本 | 5120dc07734d9b91d7231b8574b459ad8e65d8351c1c4930ad2e171447297bde59a3 |
| 数据部分 | pmsrhxnvmj8tjxxu9wj69ntvwvhvr28qufyc26tshz3rjj777tx3s |
| 校验和 | h54u63(对应值 23 20 21 28 26 17) |
| P2TR 地址 | bc1pmsrhxnvmj8tjxxu9wj69ntvwvhvr28qufyc26tshz3rjj777tx3sh54u63 |
| 同一私钥的测试网地址 | tb1pmsrhxnvmj8tjxxu9wj69ntvwvhvr28qufyc26tshz3rjj777tx3squrnq7 |
8. Taproot 与 P2WPKH 的对照
| P2WPKH(witness v0) | P2TR key path(witness v1) | |
|---|---|---|
| 地址 | bc1q…,42 个字符 | bc1p…,62 个字符 |
| 编码 | bech32,常量 1 | bech32m,常量 0x2bc830a3 |
| witness 程序 | 20 字节:hash160(压缩公钥) | 32 字节:x-only 调整后的输出公钥 |
| 输出脚本 | 0014…(22 字节) | 5120…(34 字节) |
| 签名 | ECDSA,DER 编码,约 71–72 字节 | Schnorr(BIP340),64 字节 |
| 单输入权重 | 272 WU(68 vB) | 230 WU(57.5 vB)—— 轻 15.4% |
| 花费时暴露什么 | 每次都暴露公钥 | 走 key path 时什么也不暴露 |
| 脚本灵活度 | 固定:一个公钥、一个签名 | key path 或任意 tapscript 叶子,未使用前完全不可见 |
| 激活时间 | 2017 年 8 月 24 日 | 2021 年 11 月 14 日 |
9. 它在推导链中的位置
- 上游:私钥 → 压缩公钥,以及 P2WPKH —— 同一个私钥、但程序是 20 字节的哈希。
- 相关:secp256k1 点运算能解释
P + t·G究竟在做什么;想把这个地址 重新拆开,看 bech32 / bech32m 解码页。 - 钱包:BIP39 助记词 → BIP32 子私钥 —— BIP86 钱包沿
m/86'/0'/0'/0/i派生子密钥,然后构建的正是本页所构建的地址。
10. 安全须知
- 地址承诺的是调整后的公钥,所以只看自己的公钥是认不出自己的 Taproot 地址的。永远去推导, 不要靠眼睛比对。
- x-only 意味着签名之前可能需要根据点的 y 奇偶性取负。如果钱包对一个确定正确的密钥报「签名验证失败」, 请检查它有没有处理这一步。
- script path 花费需要内部公钥、叶子脚本和控制块。控制块一旦丢失,那条分支就永久无法花费;key path 是你的 兜底 —— 这也是任何 Taproot 钱包都该保留 key path 的原因。
- 重复使用同一个 Taproot 地址,会把多笔付款关联起来,和其它地址类型一样。Taproot 隐藏的是脚本, 不是「同一个私钥被付了两次」这个事实。
11. 常见错误
- 用错了 tag 去算 tagged hash。
"TapTweak"区分大小写,而且标签要被哈希两次。 写错一个字符,就会得到一个看似合法、实则无用的地址。 - 把内部公钥当成地址。 地址承诺的是
x(Q),不是x(P)。往用未调整 公钥构造出来的「地址」转账,币是花不掉的。 - 给 v1 用 bech32 编码。 所有合规解码器都会拒绝;上面已经给出真实的报错信息。
- 以为 Taproot 会隐藏金额或发送方。 它隐藏的是脚本,以及在 key path 情况下「到底有没有过 脚本」。金额和交易图谱依旧完全公开。
- 忽略版本字符。
bc1p与bc1q只差一个字符,描述的却是完全不同 的脚本;地址请复制,不要手打。
12. 速查表
| 项目 | 取值 |
|---|---|
| 地址类型 | P2TR,witness 版本 1,key-path-only(BIP86 风格) |
| tweak | t = int(tagged_hash("TapTweak", x(P))) mod n,不含 Merkle 根 |
| tagged hash | SHA256(SHA256(tag) ‖ SHA256(tag) ‖ msg) |
| 输出公钥 | Q = P + t·G,对外只公布 x(Q)(32 字节) |
| 地址长度 | 主网 62 个字符 |
| 校验和 | bech32m,常量 0x2bc830a3 = 734539939 |
| 输出脚本 | OP_1 0x20 ‖ 32 字节公钥(共 34 字节) |
| key path 花费的 witness | 一个 64 字节的 Schnorr 签名 |