隔离见证把签名从交易主体挪进了见证区,但”挪进去”不等于”随便装”。节点在收交易时有一项专门检查见证内容是否规矩的策略,撞上它的拒绝代号是 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 源码的策略实现撰写,各项上限与判据可能随版本调整;涉及资产与交易构造请以实际节点返回为准,本文不构成任何投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。