比特币里的多重签名有两条截然不同的实现路线,多数教程只讲第一条:把若干公钥塞进一段脚本,用一条指令数签名够不够。在隔离见证与 Taproot 时代之后,第二条路线被写进了描述符规范,名字叫 multi_a 与 sortedmulti_a。它们看起来只是名字多了个后缀,脚本形态、密钥数量上限和适用位置全都变了。
老路线的一条指令
传统多签脚本长这样:先把 n 把公钥压进脚本,再写 n,再写一条 OP_CHECKMULTISIG,最后写阈值 k。执行时这条指令一次性遍历所有公钥、逐个比对签名,够 k 个就通过。它有一个写死在解释器里的约束:参与公钥的数量必须在 1 到 20 之间,超了直接失败,报错信息也是这么说的。这条限制同样出现在描述符的 multi 与 sortedmulti 上——用描述符写一个 21 把钥匙的传统多签,会被解析阶段就拦下。
除此之外,这种脚本还得把全部公钥明文放进脚本里公开,脚本体积随 n 线性膨胀,代价与暴露面都跟着涨。
新路线把一条指令拆成一串
multi_a 的脚本形态完全不同。它按顺序写入第一把密钥加一条 OP_CHECKSIG,然后对剩余每一把密钥写入密钥加一条 OP_CHECKSIGADD,最后写阈值和 OP_NUMEQUAL。含义也跟着变了:OP_CHECKSIG 通过时把 1 压上栈、不通过压 0;OP_CHECKSIGADD 则是”把上一个结果加上这一把的判定”。整段脚本执行完,栈顶是”共有几把钥匙签对了”,最后与阈值比一次大小。
这个拆分带来几个直接后果。第一,不再需要那个历史遗留的多余占位元素——传统 OP_CHECKMULTISIG 因早期实现缺陷要求脚本前方多推一个空值,新写法没有这个包袱。第二,密钥使用 32 字节的 x-only 形式,同一把钥匙在传统脚本里要占 33 字节,体积上更省。第三,签名数量与密钥数量一一对应:每一把密钥要么给签名、要么给一个空占位,不存在”少给几个签名”的写法,验签方工作量固定。
sortedmulti_a 多做一件事:在拼脚本前把 x-only 公钥按字典序排一遍,让”同一组钥匙、不同书写顺序”生成同一个脚本。这与传统路线上 sortedmulti 解决的问题一致,只是排序对象换成了 x-only 形式。
位置限制是硬规则
这四个函数不能混用位置:multi 与 sortedmulti 只能出现在 tr(...) 之外,multi_a 与 sortedmulti_a 只能出现在 tr(...) 里面。原因是它们生成的脚本 opcode 属于不同脚本版本的规则,Taproot 的脚本树里的分支必须按新的执行规则来。描述符解析器对此有硬性检查,写错位置直接报错,而不是悄悄给你换一种写法。
另一个值得留意的差别:二十把钥匙这条上限挂在传统多签那条路上。multi_a 与 sortedmulti_a 有自己的、宽松得多的上限——解析阶段卡在一把写死的九百九十九这个数字上,超出同样直接报错。所以真有”远超二十把钥匙”的需求时,答案不在传统多签描述符里,但也不要以为新写法就没有边界:脚本体积、签名字段总量与实际可花性都会先于这个数字成为真正的约束。
迁移时最容易忘的一件事
从传统多签换到 Taproot 路线的多签,生成的是完全不同的地址,旧地址不会因为你的钱包支持了新描述符就能收新类型的钱。描述符是账本的重建依据:那串带井号校验码的完整描述符文本,等于这份多签的全部规则说明——密钥来源、阈值、脚本结构、派生路径都在里面。少抄一个字符,校验码会当场报错,这是好事;真正麻烦的是只留下地址而没留描述符,将来恢复时需要靠链上历史反推脚本内容,工作量和不确定性都大得多。做完任何一次多签结构变更,把描述符原文按备份密钥同等标准存一份,才算真正完成了这次变更。
风险提示:本文描述脚本与描述符机制,不构成投资建议;多签结构涉及资产控制权,变更前后请完整备份描述符与全部密钥材料,并在小额场景验证后再投入使用。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。