谈比特币脚本的限制,多数人先想到一万个字节的脚本尺寸线、五十二万点权重的区块账,或每签名字节收费的 bytespersigop 政策。但在解释器内部还有一条更朴素的资源线:一段脚本最多执行两百零一个「操作」。它的常量名是 MAX_OPS_PER_SCRIPT,定义在 script/script.h,取值 201。有意思的是它两百零一的零头,以及它计费清单上的豁免项——这两个细节才是这条线的真正性格。
数的是操作,不是字节
在解释器的逐步执行循环里,每读到一个操作码,解释器先判断它的编码值:只有严格大于 OP_16 的操作码才让计数器加一,一旦超过 201 立即以 SCRIPT_ERR_OP_COUNT 错误终止脚本验证。这意味着脚本里大量的”内容”根本不占这份预算:压数据的 OP_PUSHDATA1/2/4 与直接压入,以及编码值不超过 OP_16 的小常数(OP_1 到 OP_16),统统免费。源码注释还特意点名一个古董:OP_RESERVED 不计入操作数上限——这条豁免是为了维护早期共识状态的兼容。
为什么压数据可以免费?因为这条预算线的目的不是衡量体积,而是衡量计算回合数:每执行一次真操作(哈希、签名校验、算术、条件分支)才是一次让解释器干活的指令,而压栈只是往工作台上添材料。字节的账由脚本尺寸线管,计算的账由操作数线管,两条线各管各的。

多签按公钥数量计费
古典 CHECKMULTISIG 是这条线上唯一的「大客户」。在 P2SH 相关的计数逻辑里,解释器不是给 CHECKMULTISIG 记一个操作,而是把脚本声明的公钥数量整体加进计数器(源码对应 nOpCount += nKeysCount 这一行)。于是一枚 15-of-15 的多签输出,光这一条指令就吃掉 15 个操作名额。设计意图与签名操作数(sigops)预算同源:一个多签校验的真实工作量随公钥数量线性增长,按声明数量计费比按指令条数计费更贴近成本。
201 而不是 200
为什么上限是两百零一?这个零头来自共识实现的历史习惯——计数器的起点与判定条件的组合结果,最终固化为不可更改的共识值。对使用者,值得记住的不是这个零头的来历,而是它如今的身份:一条与脚本字节数、栈深度、单元素长度、sigops 并列的资源线(几条线的整体关系可读 脚本的四道天花板:共识硬上限与政策标准线各管什么,资源线家族的完整清单见 脚本的五道资源红线)。任何一条线触顶,脚本都验不过,区别只在报错码。
Tapscript 换了账本
201 这条线的适用范围要注意版本边界:源码中的操作计数只对传统脚本与见证 v0(P2WPKH、P2SH 包装的 P2WSH)生效。Taproot 之后的 tapscript 用另一套成本模型:每个运算按不同的加权成本计入签名校验预算,条件分支还引入了「未执行分支」的概念(详见 BIP342)。换言之,201 属于旧账本;写新脚本方案时,别把旧上限套进新语义。
实际会碰到的场景
对普通用户,201 几乎隐形:标准支付脚本只有几个操作。它现身在三个场合。一是设计复杂锁脚本时——把一连串条件判断、哈希时间锁与多签组合进一条脚本,操作数预算可能与 sigops 预算同时吃紧,要两本账一起算;二是评估第三方生成的脚本模板时,触顶脚本会表现为永远花不出去的输出,代价是资金滞留;三是阅读历史脚本时,某些实验性设计(大量小操作拼接)正是被这条线拦在共识之外。
还有一层防御视角值得点破:所有脚本资源线合起来构成对「验证成本 DoS」的防线——攻击者若能用一段脚本迫使全网做无上限的计算,节点成本就会失控。操作数上限、sigops 上限、字节上限,每一条都把单个脚本的最坏情况钉死,全网才敢并行验证每个区块里的成百上千段脚本。
总结这行 201 的三重性格:它是计算回合数的预算,不是体积的预算;压数据与小常数免费、多签按公钥计费;它守的是旧脚本世界,tapscript 已另立账本。读脚本限制清单时,能把这三点分清楚,就不会再把尺寸线和执行线混为一谈。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。