BLS (Boneh-Lynn-Shacham)签名算法

数字签名在区块链中有着很重要的作用,保障了消息的真实性(是由私钥的拥有方签名的)和一致性(签名内容不可被篡改)。在 PoW 算法中,一个区块只需要有正确的哈希值就可以表明它是正确的,矿工的身份无关紧要,因此矿工无需签名;在 PoS 系统中,验证者是需要被识别且需要对他们的证明负责,原有的 ECDSA 签名无法实现这一点。BLS 签名算法通过实现签名聚合和密钥聚合,提升了 PoS 质押过程的效率,降低了节点运行的复杂度。

一、ECDSA: 在 PoS 的缺陷

1、PoS Node Structure

要成为以太坊的验证者,除了需要质押 32 个 ETH 外,节点还需要运行三个独立客户端。

  1. 执行客户端(Execution Client):负责处理、转发和验证交易(transaction),同时负责状态转换和支持 EVM ,提供 RPC 作为用户和以太坊连接的窗口;
  2. 共识客户端(Consensus Client):负责同步以太坊网络,接收来自 p2p 网络传来的区块,同时运行一个 fork choice 算法。
  3. 验证客户端(Validator Client):验证客户端和共识客户端是绑定在一起的,当用户向合约中 deposit 32 ETH 时,验证客户端自动会被激活,同时开始负责出块和断言。

2、ECDSA 的缺陷

由于 PoS 共识层中,需要验证者对同一个区块进行签名断言(Attestation)来验证区块的有效性,这就造成可能需要在一个 epoch 内验证数十万个验证者的签名。如果我们使用执行层的 ECDSA 签名算法,那么将花费近 30 分钟来验证这些签名,这对于信标链来说还不够好。

最初 PoS 设计是于 2018 年初制定的 [EIP-1011],该提案专注于链上 PoS 管理,并估计由于消息开销高,该协议最多只能处理大约 900 个验证器。每个验证者设定了 1500 ETH 的巨额权益大小。这是有问题的,因为它会增加中心化,BLS 的提出优化了这个问题。

原则上,为了保证足够的去中心化,Validator 肯定是越多越好。上海升级以后优化了 Validator 的退出政策,使得 Validator 的进出更加平衡,数量上也更加稳定。目前 Validator 数量 87 万,Casper FFG 为了保证投票有序进行,大量协议消息需要在共识层处理。

假设每个 slot 会产生 1.8 万个签名,每个签名 96 个字节,那么每个 slot 需要 1.68M 来缓存投票签名,等到区块被确认之前需要缓存 107M 的签名数据,况且还有其它另外两个元素的数据。这就是大量的 Validator 带来的存储问题,同样也会带来计算问题,verifier 如果对每一个 Validator 的投票都进行一次验证,每个 slot 中的每个 verifier 都要对收到的 1.75 万份签名进行验证,是相当大的工作量。

针对以上问题,数字签名的压缩成为了摆在面前的可行的出路,有没有可能把所有 Validator 的签名压缩成一个,同时验证过程也压缩成一次?这就是 BLS 解决的问题。下面我们就拆解一下 BLS 是如何做到的。

二、Boneh-Lynn-Shacham (BLS)

BLS 是斯坦福大学研究团队开发的一种新的签名方案,它可以将许多数字签名聚合为一个,同时保持每个验证器是可识别和可负责的。聚合不仅减少了需要通过网络传递的单个消息的数量,而且还减少了验证消息完整性的费用。

在使用方面,BLS 签名技术类似于 ECDSA ,尽管在数学上有很大的不同。它们具有其他签名方案所没有的两个明显特征:

  • 可以将任意数量的消息上的任意数量的签名组合成具有恒定大小的单个签名。虽然这些签名比 ECDSA 签名更大(96字节),但这意味着我们不再需要在块中专门为签名分配兆字节的空间。
  • 可以在固定时间内验证同一消息上的任意数量的签名。验证同一消息上的一百万个聚合签名所需的时间,与验证该消息上的单个签名所需的时间大致相同。这在共识层中是有利的,因为验证者经常签署相同的消息 (例如,给定的块)。

进入正题前,我们先来了解两个基础概念,曲线哈希(hashing to the curve,或译作 “哈希成曲线上的点”)和曲线配对(curves pairing)。

1、曲线哈希(找到曲线上的点 q )

在 ECDSA 和 Schnorr 签名算法中,我们对消息进行哈希计算后,结果(哈希值)是数字。BLS 签名算法则不同,它略微修改了哈希算法,结果对应到椭圆曲线上(的一个点)。最简单的修改是:哈希函数保持不变,将得到的哈希值作为点的 x 值寻找椭圆曲线上的对应点。通常来说(比如比特币所用的曲线),椭圆曲线有 2²⁵⁶ 个点,而 SHA-256 哈希算法的值也恰好是 256 位。不过,一个有效的 x 坐标,会对应一正一负两个 y 坐标(因为(x, y)和(x, -y)都是曲线 y²=x³+ax+b 上的点)。换句话说,新的哈希算法大约有 50% 的概率在曲线上找到 2 个对应点,另 50% 的概率则一个点也找不到(因为椭圆曲线只有 2^256 个点,如果要让每个哈希值都能找到对应点,椭圆曲线得有 2^257 个点才行)。

因此,对消息求哈希时,为确保能在曲线上找到对应的点,可以在消息体后附加一个数,若(寻找对应点)失败则累加该数并重新计算。

2、曲线配对(曲线上的两个点映射为一个数)

我们还需要一个特殊的函数,能够把一条(或2条不同的)曲线上的两个点 PQ 映射为一个数:

e(P, Q) → n

此函数还要有一个重要的特性。即对于未知数 x 和两个点 P 、 Q ,无论哪个点乘以 x,结果相同,即:

e(x*P, Q) = e(P, x*Q)

如此,除了乘数交换仍能保持等式成立外,更进一步,以下所有的交换都要保持等式成立:

e(a*P, b*Q) = e(P, ab*Q)
            = e(ab*P, Q)
            = e(P, Q)^(ab)

3、曲线配对函数 e

有一个(或一种)特殊的函数记为e,它可以接受输入一条(或两条不同)曲线上两点P和Q,输出至一个数字,如:e(P, Q) → n ,我们把 e 称为曲线配对函数。

之所以说这个函数特殊,是因为它有一些特殊性质。例如我们有一个数 x 和两点 P 和 Q,无论哪一个点乘以这个数字,函数结果相同即:e(x * P, Q) = e(P, x * Q)

更进一步还有: e(a * P, b * Q) = e(P, ab * Q) = e(ab * P, Q) = e(P, Q)^(ab)

我们用 pk 代表私钥,P = pk*G 代表公钥,m 代表要签名的消息。为了计算签名,先对消息求曲线哈希 H(m) ,再将获取的结果(曲线坐标点)乘以私钥即可:S = pk*H(m)。BLS 不需要随机数(不同于ECDSA,Schnorr签名),不需要额外的步骤,仅仅将哈希结果乘以私钥即可。签名结果是一个曲线上的点,用压缩的序列化格式保存,只占 33 个字节。

生成 BLS 签名。将消息的哈希结果乘以私钥即可

我们可以借助曲线配对函数,使用公钥 P 来验证签名,即 e(P, H(m)) = e(G, S)

因为配对函数的特性使得如下等式成立:

e(P, H(m))  = e(pk*G, H(m))
            = e(G, pk*H(m))
            = e(G, S)

[BLS 签名验证。我们只需验证 公钥和消息的哈希值(曲线上两个点) 与 曲线生成点和签名(曲线上另两个点) 是否映射到同一个数(若是则说明这是一个有效的 BLS 签名)]

BLS 签名验证。我们只需验证 公钥和消息的哈希值(曲线上两个点) 与 曲线生成点和签名(曲线上另两个点) 是否映射到同一个数(若是则说明这是一个有效的 BLS 签名)

4、签名聚合

BLS 签名的美妙之处在于聚合。聚合签名的大小与普通签名相同(96字节)。聚合意味着同一消息上的多个签名(可能有数千个签名)可以通过单个验证操作进行检查。这有助于扩展PoS区块链,并使共识协议可行。

现在让我们把区块中的签名都聚合在一起。假设一个区块中有 1000 笔交易,每笔交易都由 Si(签名),Pi (公钥)和 mi(消息)组成(i 在这里表示序号)。如果这些签名可以被合并,那又何必分开保存呢?毕竟,我们只关心区块中所有的签名(而不是每一个)是否正确。为获得聚合签名,只需要将区块中的所有签名加起来:

S = S1 + S2 +…+ S1000

为验证该区块是否正确,我们需要保证以下等式成立:

e(G, S) = e(P1, H(m1)) * e(P2, H(m2)) *…* e(P1000, H(m1000))

如果签名都有效,那么该等式的确是成立的:

e(G, S) = e(G, S1+S2+…+S1000)
        = e(G, S1) × e(G, S2) *…* e(G, S1000)
        = e(G, pk1×H(m1)) *…* e(G, pk1000×H(m1000))
        = e(pk1×G, H(m1)) *…* e(pk1000×G, H(m1000))
        = e(P1, H(m1)) × e(P2, H(m2)) *…* e(P1000, H(m1000))

这里我们仍需用到所有的公钥,并计算 1001 次配对函数,好处是,区块中的签名只占 33 字节了。签名聚合可以由矿工在挖矿时完成,节省大量的区块空间。

5、密钥聚合

使用多重签名的地址时,我们会对同一笔交易用不同的密钥进行签名。这种情况下,可以和 Schnorr 算法一样使用聚合密钥,把所有密钥和签名聚合成单个公钥和签名。下面我们以 3-3 多重签名方案为例(同理可推导任意数量的多重签名方案)。

由于公钥和签名都是椭圆曲线点,因此我们可以利用配对函数的双线性特性在同一消息上形成公钥和签名的线性组合。验证仍然是一样的。

一种简单的聚合方法,是把所有的签名和密钥加起来即可。如此,签名聚合结果为 S=S1+S2+S3,密钥聚合结果为 P=P1+P2+P3。可以验证以下等式依然成立:

e(G, S) = e(P, H(m))

因为:

e(G, S) = e(G, S1+S2+S3)
        = e(G, (pk1+pk2+pk3)×H(m))
        = e((pk1+pk2+pk3)×G, H(m))
        = e(P1+P2+P3, H(m))
        = e(P, H(m))

和 Schnorr 一样,我们也需要杜绝伪造密钥攻击。一种方法是要求每个签名参与者证明它拥有公钥对应的私钥(用私钥给公钥签名)。另一种方法是加入非线性系数,使得攻击无法实施。要做到这一点,聚合不再是简单的将多个密钥和签名相加,而是将它们分别乘以某个系数后再相加:

S = a1×S1+a2×S2+a3×S3

P = a1×P1+a2×P2+a3×P3

公式中签名和密钥的系数,可以通过签名者以及其它所有人的公钥计算得出,公式如下:

ai = hash(Pi, {P1,P2,P3})举个例子,可以简单的将签名者的公钥和所有人公钥拼接在一起算出系数:

此时,上面的验证公式依然成立。虽然多了系数 ai,但计算逻辑未变。

该方案的好处是,无需在设备间进行多轮通信,只需知晓其它签名者的相应信息即可。它可比 Schnorr 算法(需要 3 轮通信)的多重签名方案简单多了。这个方案也不依赖随机性,是一种具有完全确定性的签名算法。

这个是目前 ETH2.0 共识层在用的签名算法,作为过渡方案,未来会转为 ZK-STARK 方案。

三、BLS 优势和不足

验证 BLS 签名比验证 ECDSA 签名的计算量要大得多。由于配对操作,它也相当慢。那么我们使用 BLS 有什么好处呢?

1、优势

  • 在我们可以聚合大量签名的情况下,BLS 是有优势的,例如信标链认证委员会(attestation committees)。 理想情况下,委员会的所有验证者都签署相同的证明数据,从而允许聚合他们的所有签名。尽管在实践中,由于委员会成员之间对链状态的看法不同,可能会产生两到三个不同的证明。 在这种情况下,总数仍然会比委员会成员总数少很多。
  • 聚合公钥和签名比验证便宜得多。 这是因为椭圆曲线点加法要比配对便宜得多。 因此,使用聚合会带来速度优势。我们可以通过 2 个配对来验证单个签名。 因此,我们可以简单地验证具有 2N 个配对的 N 个签名。 或者我们可以通过聚合 N 个签名来验证它们。
  • 空间优势。 聚合签名与常规 BLS 签名具有相同的 96 字节长度。 因此,N 个签名的聚合仅占用未聚合签名空间的 1/N。

2、缺点

2.1 配对函数并不那么高效

BLS 签名验证的复杂度要比 ECDSA 高上一个数量级。在验证区块中 1000 笔交易的聚合签名时,仍需要进行 1000 次配对计算,这可能比使用 ECDSA 时(对 1000 个单独签名进行验证)还要慢。唯一的好处在于,可以在区块中放更多笔的交易,毕竟聚合签名只占 32 字节。

与 BLS 不同,Schnorr 验证算法的效率很高,它可以把签名放一起验证,效率是 ECDSA 算法的三倍。

2.2 安全与效率

配对函数十分复杂,使用不当就会反受其累。一方面,我们希望用高效的配对函数来加快签名验证,另一方面,我们又希望密钥信息暴露越少越好。两者互相矛盾,我们需要异常小心地选择适宜配对的曲线。有一种针对椭圆曲线加密体系的攻击,叫 MOV 攻击(用攻击发现者 Menezes、Okamoto 和 Vanstone 的名字命名),它能利用配对函数来危害系统安全。所以,使用配对函数必须万分小心。

参考

BLS Signature Aggregation: Under the Hood: BLS Signature Aggregation: Under the Hood — stu