比特币脚本是如何实现多签的?
阿牛哥 Lv4

多签(MultiSig),又叫“多重签名”,是指一笔交易需要多个私钥签名才能被执行。

大多数情况下,一笔交易只包含一个付款人,当交易被执行时,只要验证这个付款人的签名就可以了。

但也会有这样一类场景:一个钱包被多个人共同持有。比如,某公司有5个合伙人,公司钱包相应就有5个私钥,每个合伙人持有其中1个私钥。任何一个人想靠自己手上的私钥去动公司的钱包都是不可能的,必须有5个人中的3人同时签名才可以把钱包中的币取走。

上面这类情况就需要使用“多重签名”。

在比特币中,多签的含义是:一个钱包如果有 mm 个私钥,交易的时候,需要其中 n (1n m)n \ ( 1 \le n \le \ m) 个私钥的签名,交易才能被执行。

使用多签有这些优点:

  • 一个钱包可以由多人共同管理
  • 避免单点故障,防止某一个私钥被盗影响整个钱包
  • 多个私钥可以起备份作用

多签的代码表示

从程序实现的角度看,多签的代码大概是这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
// publicKey[]包含m个公钥
// sig[]包含m个签名
// count通过验证的个数
int count = 0;
for (int i = 0; i < m; i++) {
if (CHECKSIG(sig[i], publicKey[i]) {
count++;
}
}
if (count >= n) {
// 验证通过
}

以上代码用比特币脚本是实现不了的,原因是比特币脚本不支持循环和赋值语句。(关于比特币脚本的基础知识,请看《理解比特币交易脚本》)。

因此,为了支持多签,比特币引入了OP_CHECKMULTISIG语句。

CHECKMULTISIG实现多签

输出脚本:

1
2
3
4
5
6
7
n
OP_PUSHDATA(publicKey_1)
OP_PUSHDATA(publicKey_2)
...
OP_PUSHDATA(publicKey_m)
m
OP_CHECKMULTSIG

输入脚本:

1
2
3
4
OP_PUSHDATA(sig_1)
OP_PUSHDATA(sig_2)
...
OP_PUSHDATA(sig_n)

拼接后的脚本:

1
2
3
4
5
6
7
8
9
10
11
OP_PUSHDATA(sig_1)
OP_PUSHDATA(sig_2)
...
OP_PUSHDATA(sig_n)
n
OP_PUSHDATA(publicKey_1)
OP_PUSHDATA(publicKey_2)
...
OP_PUSHDATA(publicKey_m)
m
OP_CHECKMULTSIG

执行过程如图:

CHECKMULTISIG语句在栈上执行

可见,OP_CHECKMULTISIG一条语句就能完成整个多签的验证。

但是这种验证方式也有不足,它需要付款人(输出脚本)提供所有收款人的公钥。换句话说,要给有5个合伙人的公司打款,需要付款人先去获得每个合伙人的公钥,这样很不方便。

如果5个合伙人能提供单一的收款地址,不需要付款人把他们的公钥一一列出来,那样是最简便的。

P2SH就可以做到这一点。

P2SH

P2SH(Pay to Script Hash)是除P2K、P2KH之外的另一种付款方式。P2SH的输入脚本是一段成为RedeemScript的脚本,输出脚本则是RedeemScript的哈希。

输出脚本:

1
2
3
OP_HASH160
OP_PUSHDATA(redeemScriptHash)
OP_EQUALVERIFY

输入脚本:

1
2
OP_PUSHDATA(sig)
OP_PUSHDATA(redeemScript)

拼接后的脚本:

1
2
3
4
5
OP_PUSHDATA(sig)
OP_PUSHDATA(redeemScript)
OP_HASH160
OP_PUSHDATA(redeemScriptHash)
OP_EQUALVERIFY

P2SH要求付款人提供收款人提供收款人的Redeem Script的哈希;当收款人要花这笔钱的时候,需要提供相应的Redeem Script。如果Redeem Script执行后,栈顶等于TRUE,那么表示整个付款的验证通过。

回到上面的脚本,如果OP_EQUAL执行结果为TRUE,那么接下来会执行redeemScript,而redeemScript是收款人自定义的。我们可以通过它来实现多签。

包含redeemScript的脚本执行流程

Redeem Script实现多签

输出脚本:

1
2
3
OP_HASH160
OP_PUSHDATA(redeemScriptHash)
OP_EQUALVERIFY

输入脚本:

1
2
3
4
5
OP_PUSHDATA(sig_1)
OP_PUSHDATA(sig_2)
...
OP_PUSHDATA(sig_n)
OP_PUSHDATA(redeemScript)

拼接后:

1
2
3
4
5
6
7
8
OP_PUSHDATA(sig_1)
OP_PUSHDATA(sig_2)
...
OP_PUSHDATA(sig_n)
OP_PUSHDATA(redeemScript)
OP_HASH160
OP_PUSHDATA(redeemScriptHash)
OP_EQUALVERIFY

如果OP_EQUALVERIFY等于TRUE,将开始执行redeemScript。

redeemScript:

1
2
3
4
5
6
7
n
OP_PUSHDATA(publicKey_1)
OP_PUSHDATA(publicKey_2)
...
OP_PUSHDATA(publicKey_m)
m
OP_CHECKMULTISIG

Redeem Script实现多签的本质是将输出脚本的一部分转移到了收款人的输入脚本中,由输入脚本负责具体的多签验证。

总结

本篇文章和上一篇《理解比特币交易脚本》介绍了比特币脚本系统,相对于以太坊的EVM,比特币的基于栈的脚本系统在功能上不够强大,但就处理交易而言已经足够了。

比特币将每一笔交易分割为输入脚本和输出脚本,将本次交易的输入脚本拼接到上次交易的输出脚本来完成验证。这种设计很巧妙,同时也是跟比特币的UTXO模型相互绑定的。

参考:

  1. Multi-signature
  2. 《区块链技术与应用》公开课-比特币脚本