转账时钱包不会告诉你的是:所谓”验证签名”,其实是一小段程序在跑。比特币每笔输出的锁定条件是一段脚本,每笔输入的证明是另一段脚本,全节点逐笔把它们喂给脚本引擎,栈顶为真才放行。今天几乎没人手写脚本,但理解这台栈机,才能解释为什么比特币的脚本”简陋”是有意为之,也能看懂历次脚本类事故的共同根源。
两段脚本,一台机器
脚本引擎是个极简的类Forth栈机:没有变量、没有寄存器、没有跳转,只有操作数栈和一个布尔结果。执行顺序是固定的:先跑解锁脚本(通常只压签名和公钥),再跑锁定脚本(通常是「几个公钥里凑够几个有效签名」或「一个公钥验一个签名」的条件)。锁定脚本里的 OP_DUP、OP_HASH160 这些操作码各自规定”从栈顶拿几个、算完压回几个”,跑完后栈里应只剩一个值,非零且不是空串则整笔通过。整个过程确定、同步、毫秒级,每个节点对同一段脚本得到同样结果——这正是共识要的:验证逻辑越笨拙,节点之间产生分歧的机会越少。

刻意的减法
比特币脚本主动放弃了三样东西:循环、超栈、超内存。中本聪时代就明确脚本不能死循环,最早的版本里含有一批带拼接、数学位运算能力的操作码,2010 年因发现安全缺陷被整批改禁——这些不是没做完的功能,而是删掉的功能。原因朴素:脚本在全网每个节点上重复执行,一个能被构造出长跑、大内存的脚本,等于给攻击者递上”一笔交易拖垮全网”的杠杆。于是比特币选择把复杂性推到链下(更大的条件交给隔离见证、跨链协议或二层实现),基座只保留够记账的语言。二十年来这个选择的维护成本是”想做的事做不了”的争论不断,收益是脚本执行从未成为整网停摆级事故的直接原因。
历史事故的共同配方
回看脚本层的真实问题,配方几乎相同:实现者的直觉与规范的模糊处。最著名的三个:CHECKMULTISIG 少弹参数的小缺陷,规范要求多弹一个无用值,实现里漏弹一个就永远匹配失败,最后社区把”将错就错”固化成标准,至今仍在——它是”一致性高于正确性”的极端教材;2010年的整数溢出增发事故,出在脚本引擎的算术指令边界检查缺失,攻击者构造出天文数字金额的输出,其中一个含坏块的区块一度被链接纳,社区随后紧急修正检查规则并用检查点把坏块隔离出主链;以及脚本指纹问题:因为验证只用栈而不读交易其他字段,早期锁定脚本可能接受”第二套签名+第二套公钥”的等价脚本对,这是此后交易延展性讨论的技术根源之一,由隔离见证换掉验证输入的方式才根治。三者共同的教训:机器越简单,可钻的缝越少;缝一旦出现,修补方式是先统一全网行为,再谈对错。
快速问答
问:普通用户需要会写脚本吗? 答:不需要,钱包自动生成锁定脚本、填解锁脚本;读懂脚本主要服务于自建节点审计、链上分析或研究场景。
问:智能合约和比特币脚本差在哪? 答:合约链(以太坊等)的脚本是图灵完备方向的状态机,会改写世界状态;比特币脚本只做无状态的”这笔钱可以花吗”问答,算完就丢。
问:脚本报错怎么排查? 答:节点日志给的是栈错误码;社区工具能把脚本逐操作码回放,看死在哪一步、栈当时长什么样。
常见误区
一是把脚本”简陋”当落后。基座脚本每复杂一分,全网验证成本和审计面就涨一分,这是安全权衡不是能力缺陷。二是以为脚本能”主动做事”。比特币脚本只在有人花钱时被验证一次,它不观察世界、不定时触发、不记得上一次。三是把执行环境当万能调试器。脚本执行被限制得极死(步数、栈深、数据大小都有帽),复杂到超限的合法脚本同样会被直接拒绝,写得对还要写得小。
风险提示:自行构造脚本或批量操作地址涉及资金丢失风险,上线前务必先小额演练并用测试网验证;本文不构成操作或投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。