比特币及Blockstream的Liquid等相关区块链使用ECDSA签名算法 来验证储存在系统里的币的所有权归属。业界于2008年决定使用这个算法是基于当时广泛使用且未申请专利的数字签名系统。但ECDSA存在一些严重的技术局限,尤其是多重签名和阈值签名这两种需要多个独立方而非单一方的签名非常难以通过ECDSA来实现。ECDSA签名拥有十分复杂的代数结构,导致他们非常不灵活、难以操作,迫使比特币开发者不得不用比特币脚本 来开发跨链原子交换或闪电网络这类应用,如果使用更加现代的签名方案的话,可以让这些应用更加小巧且隐私性更好。
自2008年以来,数字签名技术已有了很大的发展,但新出的签名方案却忽略了实践中亟需的几个重要功能。尤其是方案设计者经常假设签名者能够控制密钥何时以及如何生成、总是能够获得一致、可靠、安全的随机性,以及拥有安全稳定的内存。实际上,比特币用户并不总是能够直接接触到自己的密钥,对于精确的密钥生成机制几乎没有控制权,对其他用户如何使用他们产生的地址则完全没有控制权。为了解决这些问题,我们开始设计一个全新的签名方案,并通过务实的工程方法来确保它保持稳健不易破坏。
简介
去年前半年,Blockstream密码学家Pieter Wuille和我以及Yannick Seurin、Gregory Maxwell发表了一个全新的多重签名方案,叫做MuSig。这个多重签名方案提供可证明的安全性能,甚至能够抵御多名恶意签名者的勾结,并创建与普通的单一签名者Schnorr签名别无二致的签名。
从那以后,我们就一直致力于将MuSig从一篇学术论文变成可用的代码,这周我们将这段代码并入了secp256k1-zkp,这是secp256k1的一个分叉,secp256k1是Bitcoin Core使用的一个高保证的加密库,我们将之扩展来为Elements和Liquid提供保密交易功能。
目前比特币社区还在探索Schnorr签名在比特币中的应用,我们希望我们的代码最终能够进入被Bitcoin Core 及许多其他项目使用的上游库secp256k1。
我们的代码生成的签名与BIP-schnorr相兼容,也可以生成适配器签名(adaptor signatures),从而使得闪电网络在无脚本脚本中成为可能。
为什么使用MuSig?
正如我们去年讨论过的,加密学研究中已有许多多重签名方案,那么为什么我们要开发自己的签名方案?因为我们有两个现有方案无法解决的功能需求:
- 简短的、体积固定的签名,不管签名者集如何,在验证者看来都是一样的。在区块链系统中,验证效率是最重要的因素,除非在安全上确实需要,否则就不必要向验证者提供签名者组成细节了。此外还有一个好处是MuSig签名还能够改进隐私性能,因为它能隐藏具体的签名者政策。
- 在简单公钥模型中的可证明的安全性。这就意味着签名者可以使用普通的密钥配对来参加多重签名,而不需要提供任何关于这些密钥生产及控制的具体方式的信息。在一些比特币情境中,个人签名者的密钥管理政策互不相同且有限制,这时候就很难获得关于密钥生成的信息。此外,对于密钥生成细节的依赖可能与Taproot不兼容,这是一个预想的比特币扩展,使得公开签名密钥可以拥有额外的隐形语义。
此外在推出MuSig之后,我们发现许多已经发布的签名方案,包括一个未公布的MuSig版本都是不安全的!我们会在以后的文章中展开讲这一点,但目前我们已经开发出了一个适用于比特币和Liquid的多重签名方案。
常见误区与安全API开发
和其他多重签名协议的学术解释一样,MuSig假设参与者在整个签名过程中都有持续的、易于升级的内存,并且攻击者无法将之”重置”为之前的状态。同时也假设签名者能够获得随机性资源。不幸的是,真实的世界并没有这么简单,为了将数学理论应用到现实世界,我们花了很长时间开发一个API,能够在各种各样的情景下使用,同时又不会因为未声明的假设而导致密钥材料丢失。
就和Schnorr签名或ECDSA一样,MuSig签名也使用必须随机均匀生成的”随机数(nonce)”。稍微有一点偏差都可能导致密钥丢失、财产被盗。
我们主要的设计目标是创建一个安全的防止误用的API,即使在受限制的环境中也不会鼓励脆弱的使用方式。
均匀随机性
使用单独的签名时,要获得均匀的随机数的标准做法是:把一些秘密数据以及需要签名的消息放到一个加密学哈希函数里,就能得到均匀的随机数,这些随机数对于每个要签名的消息都是独立的。
但如果使用的是多重签名,这个简单稳健的方法反而会带来风险。恶意签名者可能会在同一条消息中请求两个多重签名,在第二次迭代时调整自己对签名的贡献,即所谓的平行攻击。如果第一个签名者通过在消息旁边哈希一个秘密数据来选择他的随机数,他将在两个非常不同的签名中使用相同的随机数,PS3受到黑客攻击也是因为同样的原因。和只有单一签名者的情况不同,对于平行攻击并没有简单的解决方案,因为每个签名者在了解所要生成签名的所有细节之前,就必须选择随机数。
在哈希流行起来之前,这个问题的一个传统解决方案是使用硬件随机数生成器。不过这些硬件十分昂贵,容易受到环境或其他外部影响,并且最重要的是,没有办法验证他们是否正确操作。
后面一个关于验证的问题有一些较有创意的解决方案,我们会在今后的文章中谈到。目前我们选择的解决方案是要求API用户为每一次签名会话提供一个独特的”会话ID”。随机数的生成需要对签名者的秘密数据、签名者集、需要签名的消息以及这个独特的ID进行哈希。拥有随机数生成器的用户可以使用它来生成这个会话ID,拥有一致内存的用户可以直接使用一个计数器。
如果能够无需要求随机数或一致内存则比较理想,我们认为如果我们继续进行研究,终将能够开发出一个真正稳健的解决方案。
重放攻击
即使拥有可靠的随机源,如果可以在签名过程中重放一个签名协议,仍然可以从多重签名的参与者中提取密钥。这种攻击被称为”重放攻击”,可以攻击在可重新启动的虚拟机内运行的签名者,也可以攻击支持间断签名及从可序列化状态恢复的虚拟机。即使没有活跃的攻击者也可能意外地出现这种情况,例如通过运行从同一状态克隆的两个虚拟机,或者通过在已经不同步的分布式数据库上执行代码。
具体来说,如果签名者对多重签名做出贡献,并且在选择随机数之后签名过程重新启动,则可以修改其他签名者对签名的贡献,以进行和上文提到的基本相同的攻击。
这些类型的攻击根本不适用于单一签名,因为单一签名是一步生成的,没有可重新启动的中间状态。 这些额外的问题是多轮加密协议所独有的。
如果没有新的方案的话,我们根本无法保护使用虚拟机签名的用户,不过我们正在积极进行相关研究。我们认为使用虚拟机是一种不太安全的方式,因为如果攻击者能够重置虚拟机的话,那攻击者也很有可能能够从中提取机密数据。
一些用户会将停滞状态系列化且从这里重新开始,为了保护这些用户,我们的API就不支持签名会话的序列化。
在实际操作中,这意味着使用我们的代码的用户,如果想要支持可以安全度过电源重启或中断干扰的签名会话——这对于硬件钱包来说是很合理的目标——就必须要保证安全一致的内存、如果这类钱包想要同时支持多个签名会话,那么每个平行的会话都需要额外的一致内存。
我们认为可以通过我们正在研究的这个方案来解决这个局限。
结论
从上文中我们得到的结论是,多方协议比单方协议带来了更多新的问题。在数学复杂性方面,MuSig比Bulletproofs之类的解决方案要简单得多。但在安装复杂性方面,MuSig付出了更多努力,并且需要在抗脆弱性和API灵活性之间做更多权衡取舍。
本文只讨论了多重签名,多重签名是指n个签名者一起合作生成一个单一签名。在未来的文章中我们将会讨论阈值签名,即在n足够大的情况下,n个签名者的任何子集都能生成签名,而不需要整个群组的贡献。
在未来的文章中我们还会讨论一些安全生成随机数并让随机数更易于验证的技术。尤其是通过一个被称为sign-to-contract的技术,主计算机可以消除来自不可靠的硬件钱包的随机数生成器的任何偏差。
让我们来进一步解释关于消除随机性和一致内存的研究,我们可以通过零知识证明来消除随机数偏差或重放攻击,不再要求一致内存,并且将MuSig协议从三轮减少到两轮。我们对这个反感的可行性非常激动,并期待能够继续跟大家分享我们的进展。
读者可以到Github上查看我们的代码,希望大家能够踊跃尝试我们的代码并给我们反馈!