协议演进 / BIP/比特币改进提案 / BIP-117

BIP-117 尾调用执行语义

为脚本执行末尾增加尾调用语义:栈顶留下的序列化脚本会被继续执行,与 BIP-116 配合实现广义 MAST。本 BIP 目前处于草案状态。

一、档案卡

档案卡
编号BIP-117
标题Tail Call Execution Semantics
作者Mark Friedenbach <[email protected]>, Kalle Alm <[email protected]>, BtcDrak <[email protected]>
指定日期2017-08-25
层级Consensus (soft fork)(共识(软分叉))
类型Specification(规范类)
状态Draft(草案)
阅读版https://bips.dev/117
官方原文https://github.com/bitcoin/bips/blob/master/bip-0117.mediawiki

二、放在哪一族

脚本执行语义与广义 MAST 族。它是 BIP-116(MERKLEBRANCHVERIFY)的姊妹提案,同日、同作者:BIP-116 负责证明“被选出的策略脚本确实在承诺集合里”,BIP-117 负责在证明之后把这个脚本真正跑起来。

上游基础是 BIP-16(P2SH)与 BIP-141(SegWit):两者都能在花费时才揭示脚本,但每个输出只能承诺一个脚本。对照方案是显式 MAST 的 BIP-114——它靠 BIP-141 的脚本版本机制激活专门的 MAST 构造;116+117 的路线则主张只用脚本本身完成成员验证与跳转,所需的共识改动少得多,也为日后纯脚本层面的创新留了口子。

三、它要解决什么

3.1 一个输出只能承诺一个脚本

P2SH 与 SegWit 解决了“脚本不必提前公开”,但没有解决“策略不止一种”:无论最终用哪条路径花费,全部策略都必须塞进同一个脚本里揭示。合约越复杂,脚本越大,未用路径的隐私泄露也越严重。

3.2 让赎回脚本自己算出要执行的策略

本提案的思路是推广既有做法:赎回脚本可以先做任意计算,把最终要执行的策略脚本放到栈顶,再由执行语义把它当作子脚本运行。配合 BIP-116,流程是:预先把合约的所有可能执行路径枚举、压平为互不分支的策略脚本,哈希进一棵默克尔树;花费时只出示用到的那条路径,用 MERKLEBRANCHVERIFY 证明它在树中,然后尾调用执行它。这就是原文所说的广义 MAST。

四、规则本身

4.1 尾调用的触发条件

脚本执行结束时,若同时满足:

则把栈顶元素弹出、解释为序列化脚本并执行它;主栈与 alt 栈的其余元素原地保留,作为该子脚本的输入。子脚本终止,整个脚本随之终止——执行不返回调用方,这正是“尾调用”。

4.2 单字节的特殊情形:分片拼接

若栈顶恰是 0x51(OP_1)至 0x60(OP_16)范围内的单字节,则它表示 N=2 至 17:从主栈(不够时续接 alt 栈)再弹出 N 个元素,按逆序拼接成一个序列化脚本再执行。此举是为了绕开单个压入 520 字节的上限,把大脚本分片带进来。原文据此算得:每个输入可用的最大脚本规模为 10000+17×520=18840 字节。

4.3 限制与例外

4.4 为什么这是软分叉

原文的论证:一个以序列化形式留在栈上、且数据意义上为真的脚本,本来就会让验证返回真;给子脚本一个“可以否决”的机会,只是进一步收紧规则。会因此变为无效的只有空脚本或只压空值/零值的脚本,原文认为它们没有现实用途。实现上也刻意选尾调用而非通用 OP_EVAL:不返回,就不必保存与恢复调用方状态。

只允许一层递归是原文承认的限制:多层递归需要新的资源计量与交易传播规则;将来若解决,取消这一限制本身仍是软分叉,门没有关死。

五、谁必须动

普通持币人:无直接动作。

钱包与合约开发者:需把合约拆成扁平的执行路径、建默克尔树,并在 SegWit 赎回脚本里按 CLEANSTACK 等规则安排参数传递(通常经 alt 栈)。

全节点与矿工:共识层软分叉,需升级执行新的收尾语义;部署计划用 BIP-8,部署名 “tailcall”、bit 3,主网与测试网起始高度在原文中均为待定,超时为起始高度后 50400 个区块。

共识层:改变的是“脚本执行如何收尾”的判定:原本留在栈顶的真值直接通过,新规则下它会被当作子脚本继续执行并可能使整笔花费失败。

六、现在算不算数,以及一个常见误读

状态为 Draft(草案),仍是草案、未激活。它与 BIP-116 一样停留在提案阶段;主网上不存在尾调用语义,脚本执行完栈顶为真即通过验证。

常见误读:“BIP-117 给比特币加了一个 OP_EVAL,可以任意递归执行脚本。”不对。它明确只做尾调用:执行不返回、每上下文只许一层、且仅限 SegWit 赎回脚本。原文恰恰是为了避开通用 EVAL 的状态保存与资源计量难题,才把语义收窄到这个程度。

七、延伸

本页内容整理自 bitcoin/bips 仓库与 bips.dev,事实已与仓库核对;解读仅供学习参考。