Consensys团队 Gus Gutoski 2020年在zkStudyClub上分享:Multi-scalar multiplication: state of the art & new ideas。
Multi-scalar multiplication(MSM)又名Multi-exponentiation或multi-exp。
针对的场景为:
目标是:
直接的解决方案为:
有更优的解决方案么?
零知识证明ZKP中,Prover向Verifier证明其知道某些 x ⃗ = { a 1 , ⋯ , a n } \vec{x}=\{a_1,\cdots,a_n\} x={a1,⋯,an} secret inputs,使得 P ( x ⃗ ) = y P(\vec{x})=y P(x)=y成立。
在证明过程中,Prover需要使用proving key G 1 , ⋯ , G n G_1,\cdots,G_n G1,⋯,Gn 计算:
G = a 1 G 1 + ⋯ + a n G n G=a_1G_1+\cdots +a_nG_n G=a1G1+⋯+anGn
即Prover需要做MSM运算。
目标是:
MSM占据了约 80 % 80\% 80%的Prover工作量。
Justin Drake在2020的Zero-Knowledge podcast Episode 120: ZKPs in Ethereum with Vitalik Buterin & Justin Drake中指出:
MSM占据了Prover的主要开销,Prover开销占据ZKP主要开销。改进MSM即意味着提升ZKP效率。
由Consensys团队开发的:
中采用了bucket算法,针对BLS或BN曲线( b ≈ 256 b\approx 256 b≈256),所需group ops运算次数为:
16 n + ( constant ) 16n+(\text{constant}) 16n+(constant)
相比于直接方案的 384 n 384n 384n,性能提升了 24 24 24倍+。
bucket算法详细见2012年论文《Faster batch forgery identification》第4章的“Overlap in the Pippenger approach”,核心策略会:
选择 c ≤ b c\leq b c≤b,将每个scalar a 1 , ⋯ , a n a_1,\cdots,a_n a1,⋯,an以二进制表示,将二进制scalar切分为 c c c-bit parts。
如:给定 b = 12 , c = 3 b=12,c=3 b=12,c=3,将每个 12 12 12-bit scalar a a a切分为 3 3 3-bit parts。
若scalar a = 1368 a=1368 a=1368,则将 a a a表示为 a = ( 2 , 3 , 5 , 0 ) a=(2,3,5,0) a=(2,3,5,0):
从已切分的scalars推导出 b / c b/c b/c个 c c c-bit MSM instances。
如 ( b , c , b / c ) = ( 12 , 3 , 4 ) (b,c,b/c)=(12,3,4) (b,c,b/c)=(12,3,4),将每个scalar切分表示为 a i = ( a i , 1 , a i , 2 , a i , 3 , a i , 4 ) a_i=(a_{i,1}, a_{i,2}, a_{i,3},a_{i,4}) ai=(ai,1,ai,2,ai,3,ai,4),从而可获得4个 c c c-bit MSM instances T 1 , ⋯ , T 4 T_1,\cdots,T_4 T1,⋯,T4:
T 1 = a 1 , 1 G 1 + ⋯ + a n , 1 G n T_1=a_{1,1}G_1+\cdots +a_{n,1}G_n T1=a1,1G1+⋯+an,1Gn
⋮ \vdots ⋮
T 4 = a 1 , 4 G 1 + ⋯ + a n , 4 G n T_4=a_{1,4}G_1+\cdots +a_{n,4}G_n T4=a1,4G1+⋯+an,4Gn
常规方法为:double c c c次,然后求和。
仍然以 ( b , c , b / c ) = ( 12 , 3 , 4 ) (b,c,b/c)=(12,3,4) (b,c,b/c)=(12,3,4)为例,合并 T 1 , ⋯ , T 4 T_1,\cdots,T_4 T1,⋯,T4获得最终答案 T T T:
最终结果即为 T = a 1 G 1 + ⋯ + a n G n T=a_1G_1+\cdots +a_nG_n T=a1G1+⋯+anGn。
第三步中包含的加法运算开销为:
( b / c − 1 ) × ( c + 1 ) = b − c + b c − 1 \begin{equation} (b/c-1)\times(c+1)=b-c+\frac{b}{c}-1\end{equation} (b/c−1)×(c+1)=b−c+cb−1
如 ( b , c , b / c ) = ( 12 , 3 , 4 ) (b,c,b/c)=(12,3,4) (b,c,b/c)=(12,3,4),将每个scalar切分表示为 a i = ( a i , 1 , a i , 2 , a i , 3 , a i , 4 ) a_i=(a_{i,1}, a_{i,2}, a_{i,3},a_{i,4}) ai=(ai,1,ai,2,ai,3,ai,4),每个 a i , j a_{i,j} ai,j的取值范围为 1 1 1到 2 c − 1 2^{c}-1 2c−1( 0 0 0值直接忽略,不影响计算结果),对应的取值范围 1 1 1到 2 c − 1 2^{c}-1 2c−1称为bucket,实际的 a i , j a_{i,j} ai,j对应的group G i G_i Gi值放入对应的bucket中,从而有:
以 n = 14 n=14 n=14为例,将每个bucket求和,相应的 S 1 , S 2 , ⋯ , S 2 c − 1 S_1,S_2,\cdots,S_{2^c-1} S1,S2,⋯,S2c−1为:【所需的加法运算次数为:
n − ( 2 c − 1 ) = n − 2 c + 1 \begin{equation} n-(2^c-1)=n-2^c+1\end{equation} n−(2c−1)=n−2c+1
】
此时, a 1 G 1 + ⋯ a n G n a_1G_1+\cdots a_nG_n a1G1+⋯anGn的结果等价为:
S 1 + 2 S 2 + 3 S 3 + ⋯ + 7 S 7 S_1+2S_2+3S_3+\cdots+7S_7 S1+2S2+3S3+⋯+7S7
这也是一个MSM instance,其中inputs为 S 1 , ⋯ , S 7 S_1,\cdots,S_7 S1,⋯,S7,scalars为 1 , ⋯ , 7 1,\cdots,7 1,⋯,7,且:
快速计算bucket sums S 1 + 2 S 2 + 3 S 3 + ⋯ + 7 S 7 S_1+2S_2+3S_3+\cdots+7S_7 S1+2S2+3S3+⋯+7S7的方法为:
计算 S 1 + 2 S 2 + 3 S 3 + ⋯ + 7 S 7 S_1+2S_2+3S_3+\cdots+7S_7 S1+2S2+3S3+⋯+7S7所需的加法运算次数为:
2 × ( 2 c − 2 ) + 1 = 2 c + 1 − 3 \begin{equation} 2\times(2^c-2)+1=2^{c+1}-3\end{equation} 2×(2c−2)+1=2c+1−3
因此这3步中等式(1)(2)(3),总的加法运算次数为:
( b − c + b c − 1 ) + b c [ ( n − 2 c + 1 ) + ( 2 c + 1 − 3 ) ] = ( b − c + b c − 1 ) + b c ( n + 2 c − 2 ) ≈ b c ( n + 2 c ) \begin{equation} (b-c+\frac{b}{c}-1)+\frac{b}{c}[(n-2^c+1)+(2^{c+1}-3)]=(b-c+\frac{b}{c}-1)+\frac{b}{c}(n+2^c-2)\approx \frac{b}{c}(n+2^c)\end{equation} (b−c+cb−1)+cb[(n−2c+1)+(2c+1−3)]=(b−c+cb−1)+cb(n+2c−2)≈cb(n+2c)
当 c ≈ log n c\approx \log n c≈logn时,等式(4)具有最低值。初看,近似scaling为:
O ( b n log n ) O(b\frac{n}{\log n}) O(blognn)
注意,必须有 c ≤ b c\leq b c≤b,因此当 n > 2 b n>2^b n>2b时,无法选择 c ≈ log n c\approx \log n c≈logn。
当 n > 2 b n>2^b n>2b时,scaling revert为 O ( n ) O(n) O(n):
针对等式(4)中的 b c ( n + 2 c ) \frac{b}{c}(n+2^c) cb(n+2c):
为什么取 c = 16 c=16 c=16,而不是 c = log n c=\log n c=logn呢?原因在于:
对bucket method的改进措施有:
采用多核来允许bucket method。
自然的并行分配为:每个 c c c-bit MSM相互独立,类似为:
这样可充分利用 b / c b/c b/c个内核:
但是,所需内存也增加了,每个内核都需要 2 c 2^c 2c memory。
当有多于 b / c b/c b/c个内核时,另一种并行分配是:将inputs进行切分:
这样分配是inefficiency的:2个size为 n / 2 n/2 n/2的MSM,所需的加法运算次数要多于 1个size为 n n n的MSM。
所以gsnark中并未做这种实现,并行化限制为 b / c b/c b/c个核。
inputs G 1 , ⋯ , G n G_1,\cdots,G_n G1,⋯,Gn提前已知,可否利用该优势呢?
思路为:预计算并存储a bunch of points,如:
目标是:在预计算存储 与 run time之间 取得smooth平衡。
存在的问题是:大的MSM instants以使用了大部分可用内存。
(1)预计算并存储每个input G G G的: 2 G , 3 G , ⋯ , ( 2 c − 1 ) G 2G,3G,\cdots,(2^c-1)G 2G,3G,⋯,(2c−1)G。
注意, c c c-bit MSM的根本目的是计算 a 1 G 1 + ⋯ + a n G n a_1G_1+\cdots +a_nG_n a1G1+⋯+anGn for b b b-bit scalars a 1 , ⋯ , a n a_1,\cdots,a_n a1,⋯,an。
若 a i G i a_iG_i aiGi已存储,即无需再计算其它的:
所需的加法运算总次数reduce为:
b c n + ( b − c + b c − 1 ) ≈ b c n \frac{b}{c}n+(b-c+\frac{b}{c}-1)\approx \frac{b}{c}n cbn+(b−c+cb−1)≈cbn
所需的额外存储空间为: ( 2 c − 2 ) n (2^c-2)n (2c−2)n:
(2)在(1)的基础上进行平衡:【不过若存储空间有限,改进有限;若有非常大的存储空间,可由显著改进】
针对的是 a G aG aG,单个scalar a a a的计算。
常规是将 a a a以二进制表示,采用double-and-add标准算法,计算开销随 a a a的Hamming weight增加,如以8-bit scalar为例:
针对single-scalar multiplication加速思路有:
不过以更低的Hamming-weight表示法并不能改进bucket method的性能:
新的思路为:利用cheap group inversion。
根源在于:椭圆曲线群逆运算开销几乎为0:
将 c c c-bit的digit set由 { 0 , 1 , ⋯ , 2 c − 1 } \{0,1,\cdots, 2^c-1\} {0,1,⋯,2c−1},改为允许负数的 { − 2 c − 1 , ⋯ , 2 c − 1 − 1 } \{-2^{c-1},\cdots,2^{c-1}-1\} {−2c−1,⋯,2c−1−1}表示:
仍然以3-bit MSM with negative digits为例:
对每个bucket内容求和有:
计算 a 1 G 1 + ⋯ + a n G n a_1G_1+\cdots+a_nG_n a1G1+⋯+anGn等价为计算:
S 1 + 2 S 2 + 3 S 3 + 4 S 4 S_1+2S_2+3S_3+4S_4 S1+2S2+3S3+4S4
相比于之前的 2 c − 1 2^c-1 2c−1个buckets,现在仅需要 2 c − 1 2^{c-1} 2c−1个buckets。
Bucket累加工作跟之前类似,开销由约 2 c 2^c 2c降为了约 2 c − 1 2^{c-1} 2c−1。
总的计算开销约为:
b c ( n + 2 c − 1 ) \frac{b}{c}(n+2^{c-1}) cb(n+2c−1)
不过,有一些其它原因不建议改变 c c c值。
进一步想象,可便宜计算 ( − 1 ) G (-1)G (−1)G可将bucket累加开销乘以 1 / 2 1/2 1/2因子,若可便宜计算 − μ 1 G , ⋯ , − μ k G -\mu_1G,\cdots,-\mu_kG −μ1G,⋯,−μkG,是否可将bucket累加开销乘以 1 / k 1/k 1/k因子?以 ( c , k ) = ( 16 , 16 ) (c,k)=(16,16) (c,k)=(16,16),MSM性能可提升约20%。
假设可便宜计算 ( − 1 ) G , λ G (-1)G,\lambda G (−1)G,λG:
从而可使用如下digit set 来表示scalar:
该digit size约为 4 × 2 c 4\times 2^c 4×2c,仅需要约 2 c 2^c 2c个buckets.
此时,引入新的 λ \lambda λ,使得 k k k值double了,不过这种情况并不总有。
不过,将scalar转为 λ \lambda λ的digit 表示的开销不容小觑:【 − 1 -1 −1就足够好了,很容易处理overflow的问题】
若可解决将scalar以 λ \lambda λ digit表示的问题,则性能改进还是很客观的,如BLS12-377 ∣ G ∣ |G| ∣G∣为256 bits, λ \lambda λ为128 bits,则可由约50%的性能改进。
[1] Consensys团队 Gus Gutoski 2020年在zkStudyClub上分享:Multi-scalar multiplication: state of the art & new ideas