前两讲的工具足以支撑比特币。但权益证明需要更多,原因是规模:
以太坊有约 100 万个验证者。
每 12 秒的一个 slot 里,要处理数万条投票(attestation)。
⚠️ 如果每条投票都是一个独立的 ECDSA 签名:
数万 × 65 字节 = 几 MB / 12 秒,且要逐个验证
⟹ 完全不可行
这一讲的四样工具,全部是为了让大规模协作在链上可行。
打个比方
想象一场几万人的联名签署。
普通签名的做法是:每个人在纸上签一笔,最后你得到一摞几万页的文件,核验的人得一页一页看过去。
BLS 做的是另一件事:几万个签名可以合并成一个,而且核验这一个签名,就等于核验了那几万个人每一个都签了。文件从几万页变成一页,核验从几万次变成一次。
⚠️ 这听起来太好了,值得先说清代价:合并之后你就分不清是谁签的了——你只知道"这批人都签了"。所以协议里必须另外记录"这批人是谁"(以太坊用的是一个位图),而这个记录本身也要占空间。压缩掉的是签名,不是身份。
一、BLS 签名:把上万个签名压成一个
需要一样新东西:双线性配对
前面的椭圆曲线只有"加法"。BLS 需要一个额外的运算:
e : G₁ × G₂ → G_T (把两个曲线上的点映射到另一个群)
满足双线性:
e(a·P, b·Q) = e(P, Q)^(ab) ⭐ 这一条就是全部
⭐ 直觉理解:配对能把"点上的标量"搬到"指数上",从而让原本乘不到一起的两个标量相乘。
不是所有椭圆曲线都有高效的配对,需要特别构造的"配对友好曲线"。以太坊用 BLS12-381。
签名与验证
密钥: 私钥 d,公钥 Q = d·G
签名: σ = d · H(m) ⚠️ H 把消息哈希到曲线上的一个点,不是普通哈希
验证: e(σ, G) == e(H(m), Q)
推导只有两步:
e(σ, G) = e(d·H(m), G) = e(H(m), G)^d = e(H(m), d·G) = e(H(m), Q) ✅
注意签名有多简单:就是私钥乘以消息点。没有随机数 k,所以:
✅ 不存在 k 重用泄露私钥的风险(第 7 讲)
✅ 签名是确定性的
✅ 只有一个群元素,48 或 96 字节
聚合:这才是重点
多个签名可以直接相加:
σ_agg = σ₁ + σ₂ + ... + σₙ
当所有人签的是同一条消息 m 时(PoS 投票正是这种情况):
验证:e(σ_agg, G) == e( H(m), Q₁ + Q₂ + ... + Qₙ )
⭐ 一次配对运算,验证一万个人的签名。 链上只需存:
一个聚合签名(96 字节) + 一个位图(标记哪些人参与了)
以太坊的信标链正是这么做的:每个委员会的投票被聚合,最终一个区块里可能承载数十万验证者的签名,而占用的空间只有几 KB。
代价
| ECDSA | Schnorr | BLS | |
|---|---|---|---|
| 签名大小 | 64–65 B | 64 B | 48 或 96 B |
| 验证速度 | 快 | 快 | 慢(配对运算昂贵) |
| 需要交互才能聚合 | — | 需要(MuSig2 多轮) | 不需要,直接相加 |
| 需要特殊曲线 | 否 | 否 | 是(配对友好曲线) |
| 抗量子 | 否 | 否 | 否 |
BLS 最大的优势不是签名小,而是"非交互聚合":签名者们互不相识、也不需要通信,事后任何人都能把他们的签名合并。Schnorr 的聚合需要签名者之间预先协商。
恶意密钥攻击同样存在:如果 Bob 能自由声明公钥,他可以选 Q₂ = Q' − Q₁ 从而伪造聚合签名。防御是持有证明(Proof of Possession)——注册公钥时必须附带一个用该私钥对公钥自身的签名。以太坊的验证者存款流程里就有这一步。
二、阈值签名:t-of-n
⭐ 上一节是"几万人都签了,压成一个"。这一节换了个要求:不需要所有人签,够 t 个人就算数——而合出来的那一个签名,验起来和单人签名没有区别。
有时我们想要:“n 个人里任意 t 个人同意就能签名”。
与多签的区别
传统多签(比特币 2-of-3 脚本):
链上能看到:这是多签、总共几个人、哪几个人签了
大小:3 个公钥 + 2 个签名
阈值签名:
⭐ 链上只看到一个普通签名,与单签完全无法区分
大小:1 个签名
怎么做到的
用 Shamir 秘密分享:把私钥 d 藏进一个 t−1 次多项式的常数项。
构造多项式 f(x) = d + a₁x + a₂x² + ... + a_{t−1}x^{t−1} (a 随机)
给第 i 个人的份额: sᵢ = f(i)
⭐ 任意 t 个点唯一确定一个 t−1 次多项式(拉格朗日插值)
⟹ t 个人可以还原出 f(0) = d
t−1 个人对 d 一无所知(信息论意义上的零知识)
结合 BLS 的线性性,参与者甚至不需要真的还原出私钥——各自用自己的份额签名,再按拉格朗日系数加权求和即可得到有效签名。私钥从头到尾没有在任何一台机器上完整出现过。
用在哪
① 分布式验证者(DVT):一个以太坊验证者由多台机器共同运行,
⭐ 任何单台机器被攻破都不会泄露质押密钥
② 跨链桥的多方托管(第 30 讲会讲它的信任假设)
③ 随机数信标(drand 等)
三、承诺方案:先锁定,后揭示
需求
很多协议需要"我先定下一个值,但暂时不告诉你,而且事后不能反悔":
链上抽奖:所有人先提交自己的数,都提交完后再一起揭示,异或得到随机数
密封竞价:所有人先提交出价,截止后揭示,最高者中标
两个性质
隐藏性(Hiding): 看到承诺 c,推不出被承诺的值 x
绑定性(Binding): 承诺者无法事后把 c 解释成另一个值 x'
哈希承诺
承诺:c = H(x ‖ r) r 是随机数
揭示:公布 (x, r),任何人验证 H(x ‖ r) == c
⚠️ 那个 r 绝对不能省。 如果直接用 c = H(x):
拍卖场景,出价范围 0–1000 元。
攻击者把 H(0), H(1), ..., H(1000) 全算一遍,
⟹ 一千次哈希就能反查出所有人的出价,隐藏性完全失效。
⭐ r 叫盐(salt)或致盲因子。它的作用是把低熵的 x 变成高熵的输入。 这个错误在实际的链上抽奖合约里反复出现。
commit-reveal 的固有缺陷:最后揭示者优势
所有人提交承诺 → 依次揭示 → 结果 = 所有揭示值的异或
⚠️ 最后一个揭示的人,能先算出"我揭示会得到什么结果"。
如果结果对他不利,他可以选择不揭示(认赔保证金)。
⟹ 他实际上获得了对最终随机数的一次"重掷"机会
这个问题无法靠加大保证金彻底解决——只要操纵收益大于保证金就仍然划算。真正的解法是 VDF 或 VRF(第五节)。
Pedersen 承诺:带同态性
C(x, r) = x·G + r·H G、H 是两个生成元,⚠️ 且没有人知道它们之间的离散对数关系
- 隐藏性:完美的(
r随机 ⟹C在群上均匀分布,即使无限算力也推不出x) - 绑定性:计算性的(依赖离散对数难题)
关键性质是加法同态:
C(x₁, r₁) + C(x₂, r₂) = (x₁+x₂)·G + (r₁+r₂)·H = C(x₁+x₂, r₁+r₂)
两个承诺相加,等于和的承诺。 这让"在不知道具体数值的前提下验证账目平衡"成为可能:
机密交易(Confidential Transactions):
金额全部用 Pedersen 承诺表示,链上看不到任何数字。
但验证者仍可检查:Σ输入承诺 − Σ输出承诺 == 0
⟹ ⭐ 证明了没有凭空增发,却不知道任何一笔的金额
还需要额外的范围证明(range proof)来防止负数金额——否则可以用 −100 和 +100 凭空造钱。这正是 Bulletproofs 要解决的问题。
KZG 多项式承诺
一句话版本:用一个 48 字节的值承诺一整个多项式,之后可以对任意一点给出「该点取值是多少」的常数大小证明。
它是 EIP-4844 blob 数据可用性方案的基础,第 29 讲会展开。
四、可验证随机函数(VRF)
为什么链上随机数很难
先看两个错误做法:
❌ blockhash(block.number − 1)
⚠️ 出块者能看到自己的区块哈希会导致什么结果。
如果不利,他可以丢弃这个块重挖(代价只是区块奖励)。
⟹ 只要操纵收益 > 区块奖励,攻击就划算
❌ keccak256(block.timestamp, msg.sender)
全部是可预测或可操纵的输入。矿工能微调时间戳,
调用者能挑选时机。
⭐ 核心困难:链上一切数据都是公开的,而任何"参与生成随机数的人"都能提前看到结果并选择是否推进。
VRF 是什么
(output, proof) = VRF_prove(sk, input)
bool = VRF_verify(pk, input, output, proof)
三个性质:
| 性质 | 含义 |
|---|---|
| 唯一性 | 给定 sk 和 input,输出唯一确定。持有者无法在多个输出中挑一个 |
| 伪随机性 | 不知道 sk 的人无法预测输出 |
| 可验证性 | 任何人用 pk 就能确认输出是正确计算的 |
⭐ “唯一性"是要害。 它意味着持有私钥的人没有选择空间——他只能算出那一个值,要么公布要么不公布,但不能挑一个更喜欢的。
(BLS 签名天然满足唯一性——因为它是确定性的——所以 VRF(sk, x) = H(BLS_Sign(sk, x)) 就是一个可用的构造。)
用在哪
Algorand 的密码抽签:
每一轮,每个验证者本地计算 VRF(sk, 轮次种子)。
若输出 < 阈值,则他被选中,且他可以用 proof 向所有人证明自己确实被选中。
⭐ 关键性质:在他自己公布之前,没有任何人知道他被选中了。
⟹ 攻击者无法提前针对下一个出块者发动 DDoS
以太坊的 RANDAO + VDF:
以太坊目前用 RANDAO——每个出块者提交一个值混入种子。这仍然有第三节讲的最后揭示者偏置:一个出块者可以选择不出块(放弃奖励),从而"重掷"一次。
控制连续 k 个 slot 的攻击者,能获得约 2^k 次重掷机会
⟹ 偏置有限但真实存在
计划中的修复是 VDF(可验证延迟函数):
VDF 的性质:必须串行计算 T 步才能得到结果,⭐ 无法并行加速,
但结果可以被快速验证。
⟹ 把 RANDAO 的输出再过一遍 VDF:
出块者在决定"要不要出这个块"的几秒内,
根本来不及算出 VDF 的结果,也就无从判断这个块对他有没有利
VDF 用"计算需要时间"这个物理事实,消灭了操纵者的信息优势。
五、本讲小结
- PoS 的规模逼出了新工具:100 万验证者、每 12 秒数万条投票,逐个 ECDSA 验证完全不可行。
- ⭐ BLS 的核心是双线性配对
e(aP, bQ) = e(P,Q)^(ab),它能把点上的标量搬到指数上相乘。 - BLS 签名就是
σ = d·H(m):没有随机数 k ⟹ 没有第 7 讲那类事故,且签名确定。 - ⭐ 同一消息的多个 BLS 签名可以直接相加,一次配对验证全部。 链上只存一个签名 + 一个参与位图。
- BLS 最大的优势是「非交互聚合」——签名者互不通信也能被事后合并,Schnorr 做不到。代价是验证慢、需要配对友好曲线、需要持有证明防恶意密钥。
- 阈值签名与多签的本质区别:链上看不出来。 用 Shamir 秘密分享 + 拉格朗日插值,
t−1个人对私钥信息论零知识,且私钥从不在任何单机上完整出现。 - 承诺 = 隐藏性 + 绑定性。 哈希承诺必须加盐——不加盐时,出价 0–1000 的拍卖只需一千次哈希就能全部反查。
- commit-reveal 有最后揭示者优势:最后一人可以算出结果后选择不揭示。加大保证金不能根治。
- Pedersen 承诺加法同态 ⟹ 可在不知道任何金额的前提下验证账目平衡(机密交易)。还需范围证明防止负数金额。
- VRF 的关键是唯一性:持有私钥者只能算出那一个输出,没有挑选空间。Algorand 用它做"事前无人知道谁会出块"的抽签。
- 区块哈希不能当随机数:出块者能预见结果并选择丢弃区块,代价仅为区块奖励。
- VDF 用「必须串行计算 T 步」这个物理约束,消灭操纵者的信息优势——他在决策窗口内根本算不出结果。
思考题
- 从双线性性质出发,完整推导 BLS 的验证等式。
- 一万个验证者对同一条消息签名。分别计算 ECDSA 和 BLS 方案下链上需要存储的字节数与验证运算次数。
- 为什么 BLS 的聚合不需要签名者之间通信,而 Schnorr 的需要?这个差别的根源是什么?
- 恶意密钥攻击:写出 Bob 声称
Q₂ = Q' − Q₁时能伪造出什么。持有证明为什么能挡住它? - 用 3-of-5 的 Shamir 分享举例:给出一个具体的二次多项式,写出五个份额,并验证任意三个能还原而任意两个不能。
- 一个链上抽奖合约用
c = keccak256(guess)作为承诺。写出攻破它的完整步骤,并给出修复方案。 - 证明 Pedersen 承诺的加法同态性。为什么必须保证"没人知道 G 和 H 之间的离散对数关系”?如果有人知道,会发生什么?
- 一个 NFT 铸造合约用
blockhash(block.number - 1)决定稀有度。攻击者是矿工时能做什么?是普通用户时能做什么? - RANDAO 中,控制连续 3 个 slot 的攻击者有多少次"重掷"机会?VDF 是怎么把这个机会消掉的?