同一个数字多种写法:比特币脚本的最小编码与交易可延展性 图 1
同一个数字多种写法:比特币脚本的最小编码与交易可延展性 · 图 1

脚本世界的整数长什么样

比特币脚本不直接用 CPU 的整数格式,它自带一套编码:little-endian 的符号数值表示——字节序倒着排,最高字节的最高位充当符号位。数字 7 编码为单字节 0x07,负 7 则是 0x87。1 到 16 还有专用操作码 OP_1OP_16,用时一个字节都不用推。算术类操作码(加法、比较)参与计算的数值操作数长度不超过四字节;而通用的栈元素长度上限五二〇字节管的是数据(签名、公钥这类),不是数值。

这套编码的第一问题不是难,是不唯一。7 可以写成 0x07,也可以写成 0x0700(多补一个零字节,数值不变),还能写成 0x87 加负号再取反之类的绕路形式——只要字节串合法地解码成 7,多数宽松校验就放行。而”同一数据多种字节形态”在依赖哈希寻址的系统里是一类结构性危险的根源,这正是它值得单写一篇的原因。

编码歧义如何变成攻击面

比特币交易的指纹 txid 由全部字节的哈希算出。如果一段脚本或一个签名存在”等价但不同字节”的写法,攻击者就能在不动任何余额语义的前提下改写一笔交易——换一种签名 DER 编码、把推送改成非最小形式、重排多签签名的顺序——txid 随之改变,这叫交易可延展性。伤害不发生在链上,发生在链下系统:交易所与状态通道依赖”以 txid 追踪这笔提交”,同一逻辑交易换了 ID,就同时出现两份待确认记录、双记账或状态错乱。历史上真实发生过针对第三方系统的可延展性事故,协议层随后分几步关门:BIP66 要求签名严格 DER 编码,脚本数字校验普遍要求最小化,隔离见证(SegWit)则把签名从 txid 的覆盖范围移到独立的见证哈希里,从构造上斩断了外部方改写签名的能力。

现在谁在执行”字节洁癖”

要分清共识与政策:严格的 DER 与部分最小形式规则进入了共识与标准脚本校验标志,例如 SCRIPT_VERIFY_MINIMALDATA 是比特币核心标准交易校验的默认标志之一——非最小编码的推送在标准性检查阶段就吃闭门羹,交易进不了内存池;见证程序长度、公钥格式则各有专门标志约束。也就是说,绝大多数场景里非规范字节不是”非法”,而是”不标准、没人帮你中继”。

对开发者的三条纪律

第一,序列化必须用经过审计的库,自己手拼整数字节的脚本工具几乎必然在某个边界(零值、负号、多字节进位)上写出非最小形式。第二,测试要双端测:一笔交易同时跑共识语义校验与标准性校验,链上合法却被政策拒收的问题只有标准测试才能暴露;txid 与 wtxid 都要记录,两者在含见证交易上不相等。第三,签名环节锁定确定性签名(RFC 6979 类)与 DER 编码归一化,防止同一笔待签内容产出多种合法签名导致下游追踪漂移。

对用户而言,这件事的价值是理解力:当某款钱包、某个桥接服务在旧链分叉或旧节点版本下报”交易无法追踪”时,原因可能不是资金丢了,而是它的第三方系统撞上了一次教科书式的可延展性场景——先核对原交易与其等价变体的字节差异,比在两套界面里反复点击有效得多。

一次演练胜过一次事故

可以在测试网做一次低成本实验:挑一笔自己发起的小额交易,记下 txid;用钱包提供的原始交易接口取出字节串,手工把某个签名的长度前缀或脚本里的数字推送改成等价非最小写法,重新提交。在标准节点上这笔”改写版”多半直接被政策拒绝;而在政策宽松的对端上它可能成功进入内存池,两笔不同 txid、同一逻辑的交易同时待确认——收款方软件若按 txid 记账,此刻就会看到两笔”新”入账。这正是隔离见证之前现实世界的事故脚本。做过一次这种演练,之后无论遇到桥接服务报交易重复、还是旧系统对重组的反应过激,你都能第一时间从字节层问起,而不是在三个客服窗口之间转圈。