脚本里直接读内部密钥:BIP-349的OP_INTERNALKEY省字节又留退路 图 1
脚本里直接读内部密钥:BIP-349的OP_INTERNALKEY省字节又留退路 · 图 1

看得见摸不着的内部密钥

一个 Taproot 输出由两部分承诺合成:内部密钥(key-path 直接用那把密钥签)与脚本树(tapscript 分支的默克尔根)。有意思的是,运行在脚本路径里的代码反而”看不见”自己所在输出的内部密钥——脚本上下文里有哈希、有时间锁参数,却没有承诺点本身。想让脚本按这把密钥行事,唯一办法是把公钥字节写死进脚本。两个后果:每条含钥分支多花 8 个虚字节;将来内部密钥轮换(比如多签成员变更、密钥轮转制度到期),所有嵌着旧钥的脚本分支全部作废重写,整棵脚本树连带重哈希。BIP-349 用一条操作码解决这两桩事。

一条操作码的动作

BIP-349 的描述短到可以背下来:验证 taproot 脚本路径支出(叶子版本 0xc0)时,用 OP_INTERNALKEY 取代 OP_SUCCESS203(字节 0xcb),执行时把本次支出的 32 字节 x-only 内部密钥压上栈。就这些。和 CSFS 一样走”收紧 OP_SUCCESS”的软分叉路线——曾经依赖 0xcb 通过语义的脚本激活后会被拒绝;参考实现在 Bitcoin Core 的 29269 号 PR;状态 Draft,2024 年 11 月 14 日分配编号,概念的雏形来自 2022 年初 Russell O’Connor 与 Jeremy Rubin 在比特币讨论频道的一次聊天,源头可以追到 BIP-118 里”拿内部密钥当任意公钥用”的文字游戏。

省字节只是开胃菜

提案给了三个动机。最直观的是”带条件的密钥支出”:多方用聚合密钥做内部密钥时,若大家同意”用主密钥签可以,但必须先满足某段脚本条件”,脚本里引用主密钥不再需要重复写死,8 个虚字节省进钱包。第二个动机更工程:不需要 key-path 的输出会把内部密钥设成 NUMS 类无钥点,其字节如果在脚本里也要另写,哈希锁类脚本借此同样省 8 字节——提案顺带提醒,内部密钥必须是曲线点,拿哈希硬凑大约要试两次才能落进合法横坐标。第三个动机是提案的精髓:换钥匙不塌树。设想把 CTV <X> CSFS 里的 X 换成 OP_INTERNALKEY,那么输出从 TR(X, 脚本) 迁到 TR(Y, 脚本) 时,叶子脚本一个字不改,验签自动改认新钥。这在希望”作废旧状态签名”的合约设计里价值很大:理论上的撤销轮次机制正是靠改内部密钥来宣布旧签名失效,OP_INTERNALKEY 保证这个新密钥对整棵脚本树同步生效。

与CSFS的合体

单独看,OP_INTERNALKEY 只是取数操作码;与 CSFS 组合才见真章:CTV OP_INTERNALKEY CSFS——CTV 把承诺哈希推上栈,INTERNALKEY 提供密钥,CSFS 验证”当前内部密钥对这笔承诺的签名”。于是通道状态更新这种”每次换状态、旧签名作废”的场景获得一条不依赖 ANYPREVOUT 的实现路径,代价是脚本稍长。两提案都在 2024 年 11 月分中旬内先后分配编号(349 是 14 日、348 是 26 日)、出自同一对作者、PR 编号相邻,生态里常被打包讨论。

省八字节的世界线

8 个虚字节听起来寒酸,乘上使用场景就是另一幅图景:对每笔都要走脚本路径的高频协议(通道、轮换保管),脚本尺寸直接折进每笔费率与每块能装下的分支数;对需要批量创建输出的服务,尺寸决定了同一笔链上交易能携带多少逻辑账户。更重要的是”可替换性”这个属性——把硬编码密钥换成引用,脚本第一次获得了”参数”概念:合约作者可以把内部密钥当变量而非常量。这个转变的涟漪在提案动机里处处可见:密钥轮换从”重写整个输出”降为”换一把承诺密钥”,撤销机制从”发布所有分支的作废旧签”变成”改一次内部密钥”。比特币脚本长期被诟病”没有变量”,INTERNALKEY 只开了最小一口子,但引用胜过字面量这件事,本身就是脚本语言演化的第一课。

快速问答

问:现在钱包能用到它吗? 答:不能。Draft 阶段,主网激活前它仍是 OP_SUCCESS203,钱包逻辑普遍禁止生成含 OP_SUCCESS 的脚本。

问:内部密钥和输出地址里的公钥是一回事吗? 答:key-path 支出时对外表现为 tweak 后的输出密钥;INTERNALKEY 压栈的是 tweak 之前的 32 字节内部密钥,脚本里再自行拼 tweak。

问:它会先于 CSFS 激活吗? 答:两者相互独立又相互需要,社区更常见的做法是与其他脚本升级捆绑评估,顺序以 BIP 流程公告为准。

风险提示:本文内容为脚本提案机制科普,不构成投资建议;对激活与否、何时激活不做任何预测。