bad-witness-nonstandard:见证数据也得配得上前手脚本 图 1
bad-witness-nonstandard:见证数据也得配得上前手脚本 · 图 1

隔离见证把签名从交易主体挪进了见证区,但”挪进去”不等于”随便装”。节点在收交易时有一项专门检查见证内容是否规矩的策略,撞上它的拒绝代号是 bad-witness-nonstandard。

这个检查挂在哪

比特币核心 v31.0 里,交易进入内存池的流水线走到后半段会做一次判断:这笔交易带见证吗?当前节点是否要求标准交易?如果两个条件都成立,就调用策略层的见证标准性检查函数,不通过则返回内部结果 TX_WITNESS_MUTATED,代号 bad-witness-nonstandard。

两处细节值得注意。一是这条检查有开关前提:只有节点按标准性要求收交易时才会执行。测试链上有一个专门的参数允许放行非标准交易,主网默认不适用——也就是说主网用户没有把非标准见证放进内存池的常规手段。二是它属于策略而不是共识,代码里那句注释说得很清楚:这一步用的判据是策略性的,与共识层的脚本执行无关。所以一笔见证”不标准”的交易完全可能在某个块里被合法执行,只是没有节点愿意提前为它做中继和打包。

逐输入看什么

见证的合法性要看前一个输出的脚本类型,因此这套检查本质上是拿每个输入的前手脚本,去核对它配得上什么样的见证。

  • 币基交易直接放过,不参与这套判据。
  • 某个输入的见证是空的,跳过。空的见证不会占体积,脚本本身是否有效有别的环节负责。
  • 前手是 P2SH 时,先把解锁脚本粗解析成栈,取栈顶当作赎回脚本,再按赎回脚本指明的类型继续判断。这里不做完整的推送检查与哈希比对,那些留给后续脚本执行环节。
  • 前手根本不是见证脚本,却带着见证数据,直接判非标准。见证数据只能配见证脚本。
  • 前手是版本零的 P2WSH:见证栈最后一项作为脚本本体,长度不能超过策略设的上限;栈里其余项的数量、单项长度各有一条上限。任何一项越界都算非标准。
  • 前手是 P2WPKH:栈内项目数量被固定为两项,多一项少一项都不标准。
  • 前手是 Taproot(版本一、32 字节程序且不是 P2SH 包裹)时,先看栈尾是不是附项标记——在当前版本里,附项被定义语义之前一律算非标准。再看是脚本路径还是密钥路径:脚本路径要取出控制块,若叶子是 tapscript,则栈中每一项都要满足单项长度上限;密钥路径只有一项,不施加额外策略。零项的情况共识层已经判死,这里直接返回不通过。

为什么用”变异”这个内部标签

TX_WITNESS_MUTATED 这个结果类别的名字容易让人以为在说可延展性攻击。它确实是同源的分类:见证数据的改动会改变 wtxid 而不改变 txid,节点对这类”能改动见证区又不影响交易身份”的问题共用一个结果标签。但落到 bad-witness-nonstandard 上,实际含义只是”这份见证的形状不符合当前策略”,不需要往攻击方向联想。

对端送来这样的交易,节点按策略拒绝处理,属于正常摩擦,不会像共识无效那样直接触发处罚。

实操上常见的几种触发形状

第一种,前手是普通旧式脚本,钱包却在见证区塞了占位数据。最常见的来源是自己拼交易时把见证数组留成了含空元素的列表——非空的见证容器配非见证脚本,直接被这条判据拦下。

第二种,P2WSH 的脚本太长或栈太深,超出策略给的上限。多签、复杂时间锁脚本最容易踩到项数与单项长度的线,这时候要重新设计脚本结构,而不是指望换个宽松节点。

第三种,Taproot 用了附项,或者脚本路径里某个栈项超过了策略允许的长度。当前版本明确把附项视为非标准,任何带附项的见证都进不了主网内存池。

第四种,多签套在 P2SH 里再套隔离见证的复杂组合,前手脚本类型判断走的是赎回脚本推断那条路。解锁脚本结构稍有偏差,推断出的类型就和实际见证对不上。

排查顺序建议

先查交易里前一个输出到底是什么脚本类型,再对照上面那张表看见证的形状是否匹配,最后才怀疑签名本身错不错。绝大多数 bad-witness-nonstandard 的成因是”形状不匹配”而不是”签名算错”——签名错误会在脚本执行阶段报另一个错。如果是自己构造的交易,把每个输入的见证数组逐项打印出来数一遍项数,比反复重发有用得多。

风险提示:本文依据比特币核心 v31.0 源码的策略实现撰写,各项上限与判据可能随版本调整;涉及资产与交易构造请以实际节点返回为准,本文不构成任何投资建议。