bellman是Zcash团队用Rust语言开发的一个zk-SNARK软件库,实现了Groth16算法。
项目地址:https://github.com/zcash/librustzcash/tree/master/bellman

1. 总体流程


总体流程大致可以分为以下几个步骤:
1.将问题多项式拍平(flatten),构建对应的电路(Circuit)。这一步是由上层应用程序配置的。
2.根据电路生成R1CS(Rank 1 Constraint System)
3.将R1CS转化为QAP(Quadratic Arithmetic Program)。传统做法是通过拉格朗日插值,但是为了降低计算复杂度,可以通过快速傅立叶变换来实现。
4.初始化QAP问题的参数,即CRS(Common Reference Strings)
5.根据CRS和输入创建proof
6.验证proof

下面依次介绍各个步骤的细节。

2. Setup阶段:

Setup阶段最主要的工作是生成CRS数据,相关公式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ykwhzTEF-1594742340878)(零知识证明|bellman源码分析.resources/54C36D90-F201-4370-A78D-565031AAFFD9.png)]
注1:公式中的x对应代码中的变量tau,相应的xix^ixi对应于powers_of_tau。
注2:t(τ)=(τ−ω0)(τ−ω1)...(τ−ωn−1)=τn−1t(\tau)=(\tau-\omega^0)(\tau-\omega^1)...(\tau-\omega^{n-1})=\tau^{n}-1t(τ)=(τ−ω0)(τ−ω1)...(τ−ωn−1)=τn−1 ,代码中的powers_of_tau.z(&tau)就是计算该值。

2.1 参数数据结构

pub struct VerifyingKey<E: Engine> {pub alpha_g1: E::G1Affine,pub beta_g1: E::G1Affine,pub beta_g2: E::G2Affine,pub gamma_g2: E::G2Affine,pub delta_g1: E::G1Affine,pub delta_g2: E::G2Affine,pub ic: Vec<E::G1Affine>
}

其中ic=βui(τ)+αvi(τ)+wi(τ)γic=\frac{\beta u_i(\tau) + \alpha v_i(\tau) + w_i(\tau)}{ \gamma}ic=γβui​(τ)+αvi​(τ)+wi​(τ)​

pub struct Parameters<E: Engine> {pub vk: VerifyingKey<E>,pub h: Arc<Vec<E::G1Affine>>,pub l: Arc<Vec<E::G1Affine>>,pub a: Arc<Vec<E::G1Affine>>,pub b_g1: Arc<Vec<E::G1Affine>>,pub b_g2: Arc<Vec<E::G2Affine>>
}

其中h=τit(τ)δh=\frac{\tau^i t(\tau)}{\delta}h=δτit(τ)​,l=βui(τ)+αvi(τ)+wi(τ)δl=\frac{\beta u_i(\tau) + \alpha v_i(\tau) + w_i(\tau)}{\delta}l=δβui​(τ)+αvi​(τ)+wi​(τ)​
最后3个参数a, b_g1, b_g2似乎公式中没有出现,实际上它们是根据xix^ixi算出来的QAP多项式的值,也就是[u(x)]1,[v(x)]1,[v(x)]2[u(x)]_1,[v(x)]_1,[v(x)]_2[u(x)]1​,[v(x)]1​,[v(x)]2​,后面在计算proof的时候会用到。以a为例,假设其中一个多项式的系数等于c0,c1,...,cn−1c_0,c_1,...,c_{n-1}c0​,c1​,...,cn−1​,则a=Σi=0n−1cixia=\Sigma_{i=0}^{n-1}c_i x^ia=Σi=0n−1​ci​xi,最后再映射到椭圆曲线上。

2.2 变量类型

Variable类型代表输入数据中的每一个值,分为公开的statement数据和私有的witness数据:

  • Input类型:即statement数据
  • Aux类型:即witness数据
pub enum Index {Input(usize),Aux(usize)
}
pub struct Variable(Index);

2.3 ConstraintSystem

ConstraintSystem是一个接口,定义了下面几个函数用于产生不同类型的变量:

  • one(): 产生Input类型的变量,索引为0
  • alloc(): 产生Aux类型的变量,索引递增
  • alloc_input(): 产生Input类型的变量,索引递增

示例:

let a = cs.alloc(...)
let b = cs.alloc(...)
let c = cs.alloc_input(...)
cs.enforce(|| "a*b=c",|lc| lc + a,|lc| lc + b,|lc| lc + c
);

在上面这个例子里,c是statement,a和b是witness,需要验证a * b = c这个Circuit。
如果想验证a + b = c,需要写成下面这样:

cs.enforce(|| "a+b=c",|lc| lc + a + b,|lc| lc + CS::one(),|lc| lc + c
);

2.4 构建R1CS

Circuit的synthesize()会调用ConstraintSystem的enforce()构建R1CS。
KeypairAssembly是ConstraintSystem的一个实现,R1CS的参数会保存在其成员变量中:

struct KeypairAssembly<E: Engine> {num_inputs: usize,num_aux: usize,num_constraints: usize,at_inputs: Vec<Vec<(E::Fr, usize)>>,bt_inputs: Vec<Vec<(E::Fr, usize)>>,ct_inputs: Vec<Vec<(E::Fr, usize)>>,at_aux: Vec<Vec<(E::Fr, usize)>>,bt_aux: Vec<Vec<(E::Fr, usize)>>,ct_aux: Vec<Vec<(E::Fr, usize)>>
}

以上面的a * b = c为例:
num_inputs = 1
num_aux = 2
num_constraints = 1
后面6个字段就对应R1CS中的A、B、C矩阵:

2.5 构建QAP

接下来就是要完成R1CS到QAP的转换。其中有一步会利用逆离散快速傅立叶变换实现拉格朗日插值:

powers_of_tau.ifft(&worker);
let powers_of_tau = powers_of_tau.into_coeffs();

这里有一个单位根(root of unity)的概念:
如果ωn=1\omega^n = 1ωn=1,则称ω\omegaω为单位根。以复平面为例,ωi\omega^iωi实际上把单位圆等分成了n份。

现在我们是在有限循环群的条件下,根据费马小定理:假如p是质数,且ω\omegaω和p互质,那么ωp−1(modp)=1\omega^{p-1}(mod \ p) = 1ωp−1(mod p)=1。因此,和p互质的素数都可以作为单位根。
同时我们发现,任何一个元素都可以用ω1,ω2,...,ωp−1\omega^1, \omega^2, ..., \omega^{p-1}ω1,ω2,...,ωp−1的线性组合来表示,也就是说它们可以作为循环群的一组基。我们利用这组基进行逆离散傅立叶变换,就可以得到拉格朗日插值系数,将R1CS转化为QAP。

2.6 准备验证参数

最后,由于验证proof的时候需要用到下面公式里带中括号的那些参数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-80Kqu7Kh-1594742340883)(零知识证明|bellman源码分析.resources/1987F236-735B-4F40-BE7B-09C43AF40C5F.png)]
为了能快速验证,预先把这些参数的值计算出来。代码如下:

pub fn prepare_verifying_key<E: Engine>(vk: &VerifyingKey<E>
) -> PreparedVerifyingKey<E>
{let mut gamma = vk.gamma_g2;gamma.negate();let mut delta = vk.delta_g2;delta.negate();PreparedVerifyingKey {alpha_g1_beta_g2: E::pairing(vk.alpha_g1, vk.beta_g2),neg_gamma_g2: gamma.prepare(),neg_delta_g2: delta.prepare(),ic: vk.ic.clone()}
}

一个小细节:在有限域中如何计算除法?
显然,a÷b=a×b−1a \div b = a \times b^{-1}a÷b=a×b−1,那么乘性逆元素(multiplicative inverse)b−1b^{-1}b−1如何计算呢?
根据费马小定理:假如p是质数,且a和p互质,那么ap−1(modp)=1a^{p-1}(mod \ p) = 1ap−1(mod p)=1。
因此,在有限域中,b−1=bp−2b^{-1}=b^{p-2}b−1=bp−2。
对应代码:

fn inverse(&self) -> Option<Self> {if <Fr as Field>::is_zero(self) {None} else {Some(self.pow(&[(MODULUS_R.0 as u64) - 2]))}
}

3. 创建Proof阶段

Groth16算法生成Proof的公式如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N8WtAX9v-1594742340885)(零知识证明|bellman源码分析.resources/96969F8D-9104-4322-B60E-F417869ABE72.png)]
随机选取r和s,计算[A]1,[B]2,[C]1[A]_1,[B]_2,[C]_1[A]1​,[B]2​,[C]1​这三个值。

pub struct Proof<E: Engine> {pub a: E::G1Affine,pub b: E::G2Affine,pub c: E::G1Affine
}

公式中其他的参数都是已知的,最主要的难点是计算h(x),下面会进行详细介绍。

3.1 计算s⋅A(x),s⋅B(x),s⋅C(x)s \cdot A(x), s \cdot B(x), s \cdot C(x)s⋅A(x),s⋅B(x),s⋅C(x)

回顾一下QAP,我们需要通过拉格朗日插值,把R1CS转化为一系列多项式:
A(x)=[A0(x),A1(x),...,Am(x)]A(x)=[A_0(x), A_1(x), ..., A_m(x)]A(x)=[A0​(x),A1​(x),...,Am​(x)]
B(x)=[B0(x),B1(x),...,Bm(x)]B(x)=[B_0(x), B_1(x), ..., B_m(x)]B(x)=[B0​(x),B1​(x),...,Bm​(x)]
C(x)=[C0(x),C1(x),...,Cm(x)]C(x)=[C_0(x), C_1(x), ..., C_m(x)]C(x)=[C0​(x),C1​(x),...,Cm​(x)]
然后我们需要验证s⋅A(x)∗s⋅B(x)−s⋅C(x)=h(x)∗t(x)s \cdot A(x)\ * \ s \cdot B(x) \ - \ s \cdot C(x) = h(x) \ * \ t(x)s⋅A(x) ∗ s⋅B(x) − s⋅C(x)=h(x) ∗ t(x)。
因此,我们需要计算s⋅A(x),s⋅B(x),s⋅C(x)s \cdot A(x), s \cdot B(x), s \cdot C(x)s⋅A(x),s⋅B(x),s⋅C(x)。一种方法是直接进行多项式运算,还有一种方法是算出它们在ω0,ω1,ω2,...,ωn−1\omega^0, \omega^1, \omega^2, ..., \omega^{n-1}ω0,ω1,ω2,...,ωn−1处的值,然后通过iFFT(逆快速傅立叶变换)获得多项式系数。
值得注意的是,当x分别取ω0,ω1,ω2,...,ωn−1\omega^0, \omega^1, \omega^2, ..., \omega^{n-1}ω0,ω1,ω2,...,ωn−1时,A(x),B(x),C(x)A(x),B(x),C(x)A(x),B(x),C(x)的值其实就是R1CS的值。因此,s⋅A(x)s \cdot A(x)s⋅A(x)其实就是所有门电路的左输入,s⋅B(x)s \cdot B(x)s⋅B(x)就是所有门电路的右输入,而s⋅C(x)s \cdot C(x)s⋅C(x)就是所有门电路的输出。我们可以复用Circuit的synthesize()函数来进行计算。
生成proof的时候使用的是ConstraintSystem的另外一个实现ProvingAssignment来调用Circuit的synthesize()函数。

struct ProvingAssignment<E: Engine> {...a: Vec<Scalar<E>>,b: Vec<Scalar<E>>,c: Vec<Scalar<E>>,input_assignment: Vec<E::Fr>,aux_assignment: Vec<E::Fr>
}

和Setup阶段基本类似:

  • alloc():把witness数据送入aux_assignment中
  • alloc_input():把statement数据送入input_assignment中
  • enforce()/eval():计算出s⋅A(x),s⋅B(x),s⋅C(x)s \cdot A(x), s \cdot B(x), s \cdot C(x)s⋅A(x),s⋅B(x),s⋅C(x),分别放入a、b、c这3个成员变量中

然后就可以通过iFFT获得对应的多项式系数了。

3.2 计算h(x)

有了s⋅A(x),s⋅B(x),s⋅C(x)s \cdot A(x), s \cdot B(x), s \cdot C(x)s⋅A(x),s⋅B(x),s⋅C(x),就可以计算h(x)了:
h(x)=s⋅A(x)∗s⋅B(x)−s⋅C(x)t(x)h(x)=\frac{s \cdot A(x) \ * \ s \cdot B(x) \ - \ s \cdot C(x)}{t(x)}h(x)=t(x)s⋅A(x) ∗ s⋅B(x) − s⋅C(x)​
但是这里有个问题,t(x)=(x−ω0)(x−ω1)...(x−ωn−1)=xn−1t(x)=(x-\omega^0)(x-\omega^1)...(x-\omega^{n-1})=x^n-1t(x)=(x−ω0)(x−ω1)...(x−ωn−1)=xn−1,而x的取值是ω0,ω1,ω2,...,ωn−1\omega^0, \omega^1, \omega^2, ..., \omega^{n-1}ω0,ω1,ω2,...,ωn−1,所以分母为零!显然我们是不能除以零的,因此需要对x做一定的“变换”或者“偏移”。用bellman里的术语,叫做从“Evaluation Domain”转换到“Coset”上。
1.定义ω=σ(r−1)/n\omega=\sigma^{(r-1)/n}ω=σ(r−1)/n,则ωn=1\omega^n=1ωn=1(费马小定理)
2.先通过3次iFFT,计算出a,b,c的多项式系数
3.计算多项式在σω0,σω1,...,σωn−1\sigma\omega^0, \sigma\omega^1, ..., \sigma\omega^{n-1}σω0,σω1,...,σωn−1这个“偏移集合”上的值
4.再通过3次FFT,获得偏移后的点的集合a’,b’,c’
5.由于t(σωi)=(σωi)n−1=σn−1t(\sigma\omega^i)=(\sigma\omega^i)^n-1=\sigma^n-1t(σωi)=(σωi)n−1=σn−1,计算h(x)在偏移集合上的点h(σωi)=ai′∗bi′−ci′σn−1h(\sigma\omega^i)=\frac{a'_i \ *\ b'_i \ -\ c'_i}{\sigma^n\ -\ 1}h(σωi)=σn − 1ai′​ ∗ bi′​ − ci′​​
6.做一次iFFT,计算出偏移后的多项式系数
7.根据尺度变换定理:f(σx)←→F(1σω)f(\sigma x) \leftarrow \rightarrow F(\frac{1}{\sigma}\omega)f(σx)←→F(σ1​ω),把上一步得到的结果除以σ\sigmaσ,就获得了h(x)的多项式系数
根据以上分析,一共需要执行3次FFT,4次iFFT。
bellman基本上就是按照上面的算法来计算的,我们来详细分析一下。

首先,获取上一节的计算结果:

    let mut a = EvaluationDomain::from_coeffs(prover.a)?;let mut b = EvaluationDomain::from_coeffs(prover.b)?;let mut c = EvaluationDomain::from_coeffs(prover.c)?;

然后,通过3次iFFT获取多项式系数,计算在coset上的值,再通过3次FFT获取偏移后的点:

    a.ifft(&worker);a.coset_fft(&worker);b.ifft(&worker);b.coset_fft(&worker);c.ifft(&worker);c.coset_fft(&worker);

接下来,计算h(σωi)=ai′∗bi′−ci′σn−1h(\sigma\omega^i)=\frac{a'_i \ *\ b'_i \ -\ c'_i}{\sigma^n\ -\ 1}h(σωi)=σn − 1ai′​ ∗ bi′​ − ci′​​:

    a.mul_assign(&worker, &b);drop(b);a.sub_assign(&worker, &c);drop(c);a.divide_by_z_on_coset(&worker);

最后,通过iFFT获取多项式系数,然后除以σ\sigmaσ:

    a.icoset_fft(&worker);

另外,后面还有一步是计算h(τ)t(τ)δ\frac{h(\tau)t(\tau)}{\delta}δh(τ)t(τ)​,作为[C]1[C]_1[C]1​的一部分:

    multiexp(&worker, params.get_h(a.len())?, FullDensity, a)

4. 验证阶段

Groth16算法的proof验证公式如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z3xOyIJO-1594742340886)(零知识证明|bellman源码分析.resources/94E9FC17-7AA8-4022-BAFB-DC00DA7A70F7.png)]
中间的求和部分参见以下代码(使用了CRS中的ic),生成中间变量acc:

    let mut acc = pvk.ic[0].into_projective();for (i, b) in public_inputs.iter().zip(pvk.ic.iter().skip(1)) {acc.add_assign(&b.mul(i.into_repr()));}

然后把公式略做变形,将问题转化为验证以下等式:
[A]1⋅[B]2+acc⋅(−[γ]2)+[C]1∗(−[delta]2)=[α]1⋅[β]2[A]_1 \cdot [B]_2 + acc \cdot (-[\gamma]_2) + [C]_1 * (-[delta]_2) = [\alpha]_1 \cdot [\beta]_2[A]1​⋅[B]2​+acc⋅(−[γ]2​)+[C]1​∗(−[delta]2​)=[α]1​⋅[β]2​
对应代码如下:

    Ok(E::final_exponentiation(&E::miller_loop([(&proof.a.prepare(), &proof.b.prepare()),(&acc.into_affine().prepare(), &pvk.neg_gamma_g2),(&proof.c.prepare(), &pvk.neg_delta_g2)].into_iter())).unwrap() == pvk.alpha_g1_beta_g2)

至此,bellman源码的基本流程就分析完毕了,欢迎一起交流讨论~

零知识证明 - bellman源码分析相关推荐

  1. SpringBoot-web开发(四): SpringMVC的拓展、接管(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) SpringBoot-web开发(二): 页面和图标定制(源码分析) SpringBo ...

  2. EOS智能合约:system系统合约源码分析

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. eosio.system 概览 笔者使用的IDE是VScode,首先来看eosio.system的源码结构.如下图所示. ...

  3. Java并发基础:了解无锁CAS就从源码分析

    CAS的全称为Compare And Swap,直译就是比较交换.是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,在i ...

  4. kazoo源码分析:Zookeeper客户端start概述

    kazoo源码分析 kazoo-2.6.1 kazoo客户端 kazoo是一个由Python编写的zookeeper客户端,实现了zookeeper协议,从而提供了Python与zookeeper服务 ...

  5. Django源码分析9:model.py表结构的初始化概述

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-model概述 Django项目中提供了内置的orm框架,只需要在models.py文件中添加 ...

  6. Python3.5源码分析-Dict概述

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的Dict对象 在生成d = {}和d['1'] ...

  7. Python3.5源码分析-垃圾回收机制

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的垃圾回收概述 随着软硬件的发展,大多数语言都已 ...

  8. Python3.5源码分析-内存管理

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的内存管理概述 python提供了对内存的垃圾收 ...

  9. Android 源码分析之 EventBus 的源码解析

    1.EventBus 的使用 1.1 EventBus 简介 EventBus 是一款用于 Android 的事件发布-订阅总线,由 GreenRobot 开发,Gihub 地址是:EventBus. ...

最新文章

  1. 【22,23节】Django的GET和POST属性笔记
  2. YOLO算法史上最全综述:从YOLOv1到YOLOv5
  3. 基于maven使用IDEA创建多模块项目
  4. C++中的final关键字
  5. textarea 换行_textarea自动换行方法总结
  6. Jquery常用正则验证
  7. String转int,int转String
  8. Java设计模式学习记录-单例模式
  9. [Git]解决Permission denied, please try again问题
  10. PCA,ZCA,ICA,白化,稀疏编码和自编码器
  11. 刀具寿命预测研究方法
  12. python实现 文件排版
  13. img图片路径错误时,显示破图/图片裂开,如何处理?
  14. java中jdbc查询有返回值_使用JdbcTemplate查询方法的返回值 | 学步园
  15. 基于 MQTT 通讯一个简单的 Java工程
  16. 【操作篇】Excel中如何批量删除批注
  17. 你今天Git了吗?上传资源上Github最新教程!
  18. linux脚本中的gt,shell中’-gt’与’’的区别
  19. 仿京东天猫商品详情页
  20. IE6兼容性问题及解决办法汇总

热门文章

  1. k线图的分析小技巧以及买入卖出信号
  2. 豆粕5连跌四月季节性偏弱,铁矿石认购翻倍,甲醇05-09季节性反套2022.3.30
  3. Go: 模拟一张银行卡存、取、查的功能(综合练习)
  4. 三月模拟题——炉石传说
  5. 踌躇满志 未来可期 华云数据广西公司正式乔迁
  6. 手把手教你用GoEasy实现Websocket IM聊天
  7. 服务器进pe iso安装系统,进入PE肿么安装iso系统?
  8. about 日问输入法
  9. 养生年龄的早龄化一一朱乐睿教授
  10. Python Basic Grammar