作者:Anony

前篇文章见此处

在本系列的上一篇文章中,我们介绍了比特币系统的基本概念。本章,我们正式进入最常被使用的比特币复杂脚本模块:多签名。

“多签名” 简介

如前文所述,比特币中没有账户,有的只是输出 1。普通用户最常使用的个人钱包(单签名钱包),实际上是使用单个公钥锁定的输出(资金)。

而 “多签名(multisig)”,顾名思义,就是需要多个签名来解锁的脚本。一般我们使用 “m-of-n” 来描述一个多签名脚本的设置:它记录了 n 个公钥,需要提供其中 m 个公钥的签名来解锁。例如,“2-of-3” 意味着脚本预先设定了 3 个公钥,需要提供其中 2 个公钥的签名才能解锁这个脚本。

由此可见,比特币世界里常用的 “多签名(multisig)” 与密码学里的 “多签名技术(multi signature)” 并不完全等同。因为前者泛指一切使用多个签名来解锁的技术,而后者单指 n 个公钥中的每一个都需要提供签名的方案,用上面的术语来说,就是 “n-of-n” 多签。某种程度上说,比特币 multisig 兼有 “门限签名” 的一些特性,即需要提供的签名数量可以小于公钥的数量。详见此文 2

接下来,我们来看看具体的 multisig 脚本长什么样。

多签名脚本示例

假定我们要编写一个比特币脚本,其花费条件是,Alice、Bob 和 Carol 三人(或者说三个公钥)中的两个的签名,即可花费这个脚本;这个花费条件用 policy 语言表达出来是这样的:

thresh(2,pk(Alice),pk(Bob),pk(Carol))

它就像函数式子一样,前面的 thresh() 表示使用门限条件,而以 pk() 括起的是公钥,表示依据这个公钥检查签名;综合起来,表示花费条件是 3 个公钥中的 2 个提供了签名。Policy 语言可以使用函数一共有 7 种 3(这里我们已经接触到了两种),理解起来都很简单。

上面这个网站还提供了将 Policy 语句编程成 Miniscript 代码和 Script 的编译器,我们来看看它的 Miniscript 代码:

multi(2,Alice,Bob,Carol)

而它的 Script 代码是:

2 <Alice> <Bob> <Carol> 3 OP_CHECKMULTISIG

对于了解比特币脚本的读者来说,最后这段代码会更亲切。但是,对于不了解脚本操作码和堆栈结构的人来说,前面两段看起来像函数的式子无疑简单很多。而且,这只是我们接触的第一种脚本,往后,我们还会看到 Policy 和 Miniscript 如何让脚本的编写变得更加简单。(提醒:编写脚本的难度可不仅仅体现在编写赎回脚本的时候,根据脚本提供 witness 也不是那么简单!)

上述 Miniscript 代码可以翻译成符合 P2SH 和 P2WSH 规则的赎回脚本,但是,它无法翻译成符合 P2TR(taproot 地址)的赎回脚本,因为 tapscript(P2TR 的脚本规则)改变了实现多签名的操作码。如果需要翻译成 tapscript,你需要使用这样的 Miniscript 代码:multi_a(k,key1,...,keyn)

此外,因为 Taproot 升级引入了 Schnorr 签名,基于 Schnorr 签名交互协议而非比特币脚本来实现 n-of-n 签名和门限签名的技术也因此成为可能,例如 MuSig 4 和 FROST 5。因为这些技术并不基于比特币脚本,因此不在本系列文章的范围之内。但它们代表了进一步隐藏链上足迹(从而提高隐私性)的方向。

接下来,我们看看这么简单的东西,有哪些用场!

多签名脚本的用途

个人保险柜

假设一人设置了一个 2-of-3 的多签名钱包,那么,TA 就不怕这三个私钥中的其中一个失盗、丢失、损坏。用行话来说,这就是消除了其中任何一个私钥的 “单点故障”。比如,用户可以设置分别放在手机、工作电脑和硬件钱包里的私钥,这三台设备任何两台一起,都可以签名、花费资金;当用户发觉某一台设备失盗或信息已经泄露的时候,可以从容使用另外两台设备转移资金。

这样的多签名脚本还可以帮助用户消除对签名设备的硬件制造商的信任:如果你使用单签名的钱包,把私钥放在手机里面,如果手机厂商安装了后门,你就完了;而如果你使用了多签名签名,必须有 m 个设备的制造商串谋,才能拿走你的资金。

这样的个人保险柜对用户提出的唯一要求是:不要将这些私钥的备份都放在一起,即不能让这些私钥一起暴露。

已有的应用:Blue Wallet 的 Vault 功能 6;用户也可以自己使用一些软件钱包配置出相同功能的钱包。

关于个人用户的多签实践,这里有一份很好的指南 7

同类应用:家庭保险柜;压岁钱。

企业财务

在企业财务中,为避免单点故障、不受约束的大权独揽,我们也可以使用多签名。在这里,多签名的用途除了避免单点故障,还有赋予权限、约束参与者的作用。只要(n - m + 1)个公钥是诚实的,资金就不会被任意挪用。

联盟侧链

与企业财务有相通之处。一个足够大的多签名团体(例如 11-of-15),这个团体可以成为一条联盟侧链的运营者;这条侧链可以接收比特币,让用户的比特币在另一个环境中使用比特币系统无法提供的功能;当用户想要退出这个环境时,15 位成员中的 11 位集体响应,就可以帮助用户的资金离开侧链、回到用户自己控制的钱包中。

在这里,用户不需要信任任何一位联盟成员,但必须有至少 5 位成员是诚实的,才能保证侧链资金不会被任意挪用、形成亏空;此外,至少 11 位成员是诚实的,才能保证用户的资金不会被审查、总能回到自己的钱包中。

已有的应用:Liquid Network 8。实际上,当前几乎所有的比特币侧链,都使用这种方式来托管进入侧链的资金。

此外,Liquid Network 以及类似的侧链往往不会只使用一个多签名设置,也就是说,同一笔资金实际上有不止一套多签名的锁附加在上面;这些多签名锁是相互独立的,而且都可以独自解锁资金。这是怎么做到的呢?我们文末揭晓。

电子商务/免信任的仲裁

假定 Alice 和 Bob 要做一笔买卖,但无法做到一手交钱一手交货,于是他们约定,把钱锁入一个多签名输出中,当 Bob 收到 Alice 发出的货时,再把钱完整支付给 Alice。但是,如果 Bob 明明收到了货,却不愿意给钱怎么办?

他们可以引入一个第三方(例如 Carol),形成一个 2-of-3 的多签名输出;当交易发生争议时,比如 Bob 拒不付款,可以请求第三方来主持公道。如果第三方认为 Alice 确实已经履行了责任,就可以跟 Alice 联手将资金转走。

但是,问题又来了,如何避免这个第三方与其中一方勾结呢?答案很简单,这是一场买卖,所以双方约定一个(实际上可以做到多个,但涉及到至此还未介绍到的脚本知识)双方都认为公正的仲裁方即可。

那么如何防止第三方主动寻租(主动找到其中一个参与方,引诱 TA 诈骗对手)呢?答案是,第三方可以根本不知道自己参与了这样的合约!假定愿意当仲裁方的人都主动公开自己的公钥,参与电子商务的人就可以在 2-of-3 多签名脚本中使用他们的公钥,从而加入仲裁者,这完全可以在仲裁方不知情的情况下做到(因为链上输出上记录的是脚本的哈希值,所以他们观察区块链也不知道其中的内容)。

实际上,这就是现实世界中 “仲裁” 的通行用法。国际贸易(跟我们这里的电子商务一样,做不到钱货两讫)的合同基本都指定了仲裁庭作为争议的裁决方法,这样的合同只需得到参与双方的同意,无需在发生争议前知会仲裁庭。而仲裁庭的存在也显然约束了双方的行为。

这样的做法,可以创造出一个 “公正服务的市场”,即各仲裁者相互竞争,提供准则而公正的仲裁服务,以争取人们的使用并获得服务费。

这样的带仲裁服务的合约,理论上可以相当普遍,商务合同、打赌,都可以使用这样的结构。

暂无这样的服务。

免信任的托管方

个人用户可以选择让托管方提供一把公钥,放入 2-of-3 多签名输出中,作为应对单私钥故障(丢失、失盗、损坏)的应急措施。托管方承诺在用户面临这样的情况时检查用户身份、提供签名、解锁资金。

这样的托管方是无法单方面取走你的资金的。

已有的服务:Casa 9

点对点合约

基于 2-of-2 多签名,我们可以制作出多种点对点的合约。例如:支付通道,仅基于我们已经学习的脚本技术,可以允许单方多次向另一方支付,而不必让每一次支付都广播到比特币区块链上(在本系列的后续文章中,我们将逐渐学习实现闪电支付通道 —— 双向无限次相互支付的通道—— 的脚本模块)。

这样的单向支付通道的实现方式是:每当 A 要给 B 支付时,就给 B 一笔新的、花费他们的 2-of-2 输出的交易,B 无需让这笔交易得到区块确认,就知道这样的支付是真实、安全的,因为 A 无法独自将资金卷走(需要 B 的签名才可以转移其中的资金)。由于这些交易花费的是同一个输出,所以它们无法都得到区块确认 —— 对 B 来说,当需要广播这些交易(也就是关闭支付通道)的时候,最好的当然是广播 A 支付了最多资金的那一笔,也就是最新的那一笔交易。

一个有趣而重要的问题是:在 2-of-2 多签名这样的输出中,任何一方如果拒绝签名,就会使资金在其中锁死,那么用户要如何防止对方的有意欺诈和无意的故障(比如下线了,无法响应)呢?

这就涉及到一个重要的概念,也是比特币合约式编程的基本工具:承诺交易。意思是说,给定我们知晓 A 交易将形成 A-0 输出,此输出为 Alice 和 Bob 的 2-of-2 多签名输出,为了防止资金在其中锁死,我们可以在 A 交易实际广播到比特币网络之前,提前签名花费 A-0 输出的交易,比如签名一笔将资金原路退回给双方的交易,作为双方的安全保障措施;如果一方拒绝签名这样的交易,另一方可以直接离开,不会有任何损失(因为资金还未实际存入这个 2-of-2 多签名输出)。一旦签名了这样的交易,就可以放心将资金锁入这样的 2-of-2 输出(这就是为什么 “输出点”、交易索引号如此重要,也是为什么我们需要隔离见证升级)。即,如果资金进入多签名输出之后,对方没有响应,则另一方可以向网络广播这笔承诺交易,从而拿回自己的资金。

点对点合约有非常多的应用,如谨慎日志合约、Statechain,但这些应用往往都用到了其它的脚本模块或非脚本的密码学技术,我们在此就不展开了。往后我们会慢慢接触到。

小结

如上,我们了解了多签名输出的许多用途。在这些应用场景中,多签名的用途包括:消除单点故障、消除对单一参与者的信任、赋予参与权限、分散资金的控制权。重复我们在本系列的上一篇文章中讲过的话:比特币脚本的作用,不是将需要做的事情全部计算一遍,而是为参与者的互动提供密码学锚,凭借这些锚,参与者就可以预期一些事情在自己的控制之下。比特币的 UTXO 结构以及多签名支持,非常好地体现了这一点。

题外话

我们在上文介绍联盟侧链的时候,提到 Liquid Network 为自己保管的资金设置了不止一套多签名的锁。这是怎么实现的呢?

答案是,它涉及到比特币脚本的一类操作码,称为 “流程控制操作码”,它可以设置 IF...ELIF...(如果……,那么……;不然,就 ……)这样的条件式语句,从而形成并列的多个解锁条件

它看起来毫不起眼,实际上非常强大。比如,我们可以在小孩子的压岁钱上设置两套多签名,一套是爸爸妈妈和小孩的 2-of-3 多签名,一套是爷爷奶奶和小孩的 2-of-3 多签名,从而小孩子只要说服任何一个人,就可以花费自己的压岁钱。(这个例子比较差劲,因为如果需要实现类似的效果,只需要拿到 4 位家长的公钥,制作一个 1-of-4 多签名即可。)

同理,我们也可以形成设置多个托管方或多个仲裁方的合约。假设某个托管方想趁火打劫,用户可以使用另一个托管方。

这样的流程控制,是真正发挥比特币脚本强大功能的必要条件。往后我们会不断遇到它。

参考文献

1. https://www.btcstudy.org/2023/04/18/interesting-bitcoin-scripts-and-its-use-cases-part-1-introduction/#%E8%BE%93%E5%87%BA

2. https://www.btcstudy.org/2022/02/14/aggregate-threshold-multisig-and-multisignatures/#%E5%A4%9A%E7%AD%BE%E5%90%8D%EF%BC%88Multisignatures%EF%BC%89

3. https://bitcoin.sipa.be/miniscript/

4. https://www.btcstudy.org/2021/11/29/schnorr-applications-musig/

5. https://www.btcstudy.org/2021/12/09/schnorr-applications-frost/

6. https://bluewallet.io/multisig-wallet/

7. https://www.btcstudy.org/2022/12/09/a-guide-for-bitcoin-multi-sig-wallets-by-mi-zeng/

8. https://www.btcstudy.org/2022/10/18/exploring-liquid-sidechain/

9. https://keys.casa/

续篇见此处