Gabizon等人2019年论文《PLONK: permutations over lagrange-bases for oecumenical noninteractive arguments of knowledge》。
本文主要针对的代码库为:
主要见https://github.com/dusk-network/plonk/tree/master/src/commitment_scheme
,采用的曲线为bls12-381。
BlsScalar
:表示scalar fieldG1Affine
,G1Projective
:表示group point,实际commitment采用G1Affine
来表示,而借助.into()
函数可将G1Projective
转换为G1Affine
形式。identity是指无穷远点 ( 0 , 1 , 1 ) (0,1,1) (0,1,1)。满足任意点与该identity相加均为该点。
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
/// Holds a commitment to a polynomial in a form of a `G1Affine` Bls12_381 point.
pub struct Commitment(
/// The commitment is a group element.
pub G1Affine,
);
https://github.com/dusk-network/plonk
代码库中,实际实现是参考了 Kate等人2010年论文[kzg10]《Polynomial Commitments∗》中 不具有hiding属性的 3.2节“ P o l y C o m m i t D L PolyCommit_{DL} PolyCommitDL” 的属性,只是实际实现,采用的不是 symmetric pairing e : G × G → G T e:\mathbb{G}\times\mathbb{G}\rightarrow\mathbb{G}_T e:G×G→GT,而 unsymmetric pairing e : G 1 × G 2 → G T e:\mathbb{G}_1\times \mathbb{G}_2\rightarrow \mathbb{G}_T e:G1×G2→GT。
以degree 为 t t t的单个多项式 ϕ ( x ) ∈ Z p [ x ] \phi(x)\in\mathbb{Z}_p[x] ϕ(x)∈Zp[x]为例,构建polynomial commitment的数学背景为: ϕ ( x ) − ϕ ( z ) x − z \frac{\phi(x)-\phi(z)}{x-z} x−zϕ(x)−ϕ(z) 不存在余数 for z ∈ Z p z\in\mathbb{Z}_p z∈Zp。
g g g为generator of G 1 \mathbb{G}_1 G1, h h h为generator of G 2 \mathbb{G}_2 G2。
/// The Public Parameters can also be referred to as the Structured Reference String (SRS).
/// It is available to both the prover and verifier and allows the verifier to
/// efficiently verify and make claims about polynomials up to and including a configured degree.
#[derive(Debug, Clone)]
pub struct PublicParameters {
/// Key used to generate proofs for composed circuits.
pub commit_key: CommitKey,
/// Key used to verify proofs for composed circuits.
pub opening_key: OpeningKey,
}
/// Setup generates the public parameters using a random number generator.
/// This method will in most cases be used for testing and exploration.
/// In reality, a `Trusted party` or a `Multiparty Computation` will used to generate the SRS.
/// Returns an error if the configured degree is less than one.
pub fn setup(
max_degree: usize,
mut rng: &mut R,
) -> Result {
// Cannot commit to constants
if max_degree < 1 {
return Err(Error::DegreeIsZero);
}
// Generate the secret scalar beta
let beta = util::random_scalar(&mut rng);
// Compute powers of beta up to and including beta^max_degree
let powers_of_beta = util::powers_of(&beta, max_degree);
// Powers of G1 that will be used to commit to a specified polynomial
let g = util::random_g1_point(&mut rng);
let powers_of_g: Vec =
util::slow_multiscalar_mul_single_base(&powers_of_beta, g);
assert_eq!(powers_of_g.len(), max_degree + 1);
// Normalise all projective points
let mut normalised_g = vec![G1Affine::identity(); max_degree + 1];
G1Projective::batch_normalize(&powers_of_g, &mut normalised_g);
// Compute beta*G2 element and stored cached elements for verifying multiple proofs.
let h: G2Affine = util::random_g2_point(&mut rng).into();
let beta_h: G2Affine = (h * beta).into();
Ok(PublicParameters {
commit_key: CommitKey {
powers_of_g: normalised_g,
},
opening_key: OpeningKey::new(g.into(), h, beta_h),
})
}
/// Commits to a polynomial returning the corresponding `Commitment`.
///
/// Returns an error if the polynomial's degree is more than the max degree of the commit key.
pub fn commit(&self, polynomial: &Polynomial) -> Result {
// Check whether we can safely commit to this polynomial
self.check_commit_degree_is_within_bounds(polynomial.degree())?;
// Compute commitment
let commitment = msm_variable_base(&self.powers_of_g, &polynomial.coeffs);
Ok(Commitment::from_projective(commitment))
}
/// Creates an opening proof that a polynomial `p` was correctly evaluated at p(z) and produced the value
/// `v`. ie v = p(z).
/// Returns an error if the polynomials degree is too large.
pub fn open_single(
&self,
polynomial: &Polynomial,
value: &BlsScalar,
point: &BlsScalar,
) -> Result {
let witness_poly = self.compute_single_witness(polynomial, point);
Ok(Proof {
commitment_to_witness: self.commit(&witness_poly)?,
evaluated_point: *value,
commitment_to_polynomial: self.commit(polynomial)?,
})
}
/// 这段注释有问题。。。。。。
/// For a given polynomial `p` and a point `z`, compute the witness
/// for p(z) using Ruffini's method for simplicity.
/// The Witness is the quotient of f(x) - f(z) / x-z.
/// However we note that the quotient polynomial is invariant under the value f(z)
/// ie. only the remainder changes. We can therefore compute the witness as f(x) / x - z
/// and only use the remainder term f(z) during verification.
pub fn compute_single_witness(&self, polynomial: &Polynomial, point: &BlsScalar) -> Polynomial {
// Computes `f(x) / x-z`, returning it as the witness poly
polynomial.ruffini(*point)
}
/// Checks that a polynomial `p` was evaluated at a point `z` and returned the value specified `v`.
/// ie. v = p(z).
pub fn check(&self, point: BlsScalar, proof: Proof) -> bool {
let inner_a: G1Affine =
(proof.commitment_to_polynomial.0 - (self.g * proof.evaluated_point)).into();
let inner_b: G2Affine = (self.beta_h - (self.h * point)).into();
let prepared_inner_b = G2Prepared::from(-inner_b);
let pairing = dusk_bls12_381::multi_miller_loop(&[
(&inner_a, &self.prepared_h),
(&proof.commitment_to_witness.0, &prepared_inner_b),
])
.final_exponentiation();
pairing == dusk_bls12_381::Gt::identity()
}
[kzg10]单个多项式 polynomial commitment scheme示例为:
#[test]
fn test_basic_commit() {
let degree = 25;
let (proving_key, opening_key) = setup_test(degree);
let point = BlsScalar::from(10);
let poly = Polynomial::rand(degree, &mut rand::thread_rng());
let value = poly.evaluate(&point);
let proof = proving_key.open_single(&poly, &value, &point).unwrap();
let ok = opening_key.check(point, proof);
assert!(ok);
}
proof 个数为 O ( 1 ) O(1) O(1),与points个数无关。
具体测试用例为:
#[test]
fn test_aggregate_witness() {
let max_degree = 27;
let (proving_key, opening_key) = setup_test(max_degree);
let point = BlsScalar::from(10);
// Committer's View
let aggregated_proof = {
// Compute secret polynomials and their evaluations
let poly_a = Polynomial::rand(25, &mut rand::thread_rng());
let poly_a_eval = poly_a.evaluate(&point);
let poly_b = Polynomial::rand(26 + 1, &mut rand::thread_rng());
let poly_b_eval = poly_b.evaluate(&point);
let poly_c = Polynomial::rand(27, &mut rand::thread_rng());
let poly_c_eval = poly_c.evaluate(&point);
proving_key
.open_multiple(
&[poly_a, poly_b, poly_c],
vec![poly_a_eval, poly_b_eval, poly_c_eval],
&point,
&mut Transcript::new(b"agg_flatten"),
)
.unwrap()
};
// Verifier's View
let ok = {
let flattened_proof = aggregated_proof.flatten(&mut Transcript::new(b"agg_flatten"));
opening_key.check(point, flattened_proof)
};
assert!(ok);
}
/// Creates an opening proof that multiple polynomials were evaluated at the same point
/// and that each evaluation produced the correct evaluation point.
/// Returns an error if any of the polynomial's degrees are too large.
pub fn open_multiple(
&self,
polynomials: &[Polynomial],
evaluations: Vec,
point: &BlsScalar,
transcript: &mut Transcript,
) -> Result {
// Commit to polynomials
let mut polynomial_commitments = Vec::with_capacity(polynomials.len());
for poly in polynomials.iter() {
polynomial_commitments.push(self.commit(poly)?)
}
// Compute the aggregate witness for polynomials
let witness_poly = self.compute_aggregate_witness(polynomials, point, transcript);
// Commit to witness polynomial
let witness_commitment = self.commit(&witness_poly)?;
let aggregate_proof = AggregateProof {
commitment_to_witness: witness_commitment,
evaluated_points: evaluations,
commitments_to_polynomials: polynomial_commitments,
};
Ok(aggregate_proof)
}
/// Computes a single witness for multiple polynomials at the same point, by taking
/// a random linear combination of the individual witnesses.
/// We apply the same optimisation mentioned in when computing each witness; removing f(z).
pub(crate) fn compute_aggregate_witness(
&self,
polynomials: &[Polynomial],
point: &BlsScalar,
transcript: &mut Transcript,
) -> Polynomial {
let challenge = transcript.challenge_scalar(b"aggregate_witness");
let powers = util::powers_of(&challenge, polynomials.len() - 1);
assert_eq!(powers.len(), polynomials.len());
let numerator: Polynomial = polynomials
.iter()
.zip(powers.iter())
.map(|(poly, challenge)| poly * challenge)
.sum();
numerator.ruffini(*point)
}
Verifier 进行 flatten
的主要作用为:
flattened_poly_commitments
对应为 F F F,flattened_poly_evaluations
对应为 v v v。 /// Flattens an `AggregateProof` into a `Proof`.
/// The transcript must have the same view as the transcript that was used to aggregate the witness in the proving stage.
pub fn flatten(&self, transcript: &mut Transcript) -> Proof {
let challenge = transcript.challenge_scalar(b"aggregate_witness");
let powers = powers_of(&challenge, self.commitments_to_polynomials.len() - 1);
// Flattened polynomial commitments using challenge
let flattened_poly_commitments: G1Projective = self
.commitments_to_polynomials
.iter()
.zip(powers.iter())
.map(|(poly, challenge)| poly.0 * challenge)
.sum();
// Flattened evaluation points
let flattened_poly_evaluations: BlsScalar = self
.evaluated_points
.iter()
.zip(powers.iter())
.map(|(eval, challenge)| eval * challenge)
.fold(BlsScalar::zero(), |acc, current_val| acc + current_val);
Proof {
commitment_to_witness: self.commitment_to_witness,
evaluated_point: flattened_poly_evaluations,
commitment_to_polynomial: Commitment::from_projective(flattened_poly_commitments),
}
}
当proof 个数与points个数一致时,即为每个point的evaluation生成一个proof,对应示例为:
#[test]
fn test_batch_verification() {
let degree = 25;
let (proving_key, vk) = setup_test(degree);
let point_a = BlsScalar::from(10);
let point_b = BlsScalar::from(11);
// Compute secret polynomial a
let poly_a = Polynomial::rand(degree, &mut rand::thread_rng());
let value_a = poly_a.evaluate(&point_a);
let proof_a = proving_key
.open_single(&poly_a, &value_a, &point_a)
.unwrap();
assert!(vk.check(point_a, proof_a));
// Compute secret polynomial b
let poly_b = Polynomial::rand(degree, &mut rand::thread_rng());
let value_b = poly_b.evaluate(&point_b);
let proof_b = proving_key
.open_single(&poly_b, &value_b, &point_b)
.unwrap();
assert!(vk.check(point_b, proof_b));
assert!(vk
.batch_check(
&[point_a, point_b],
&[proof_a, proof_b],
&mut Transcript::new(b""),
)
.is_ok());
}
相应batch算法实现为:
/// Checks whether a batch of polynomials evaluated at different points, returned their specified value.
pub fn batch_check(
&self,
points: &[BlsScalar],
proofs: &[Proof],
transcript: &mut Transcript,
) -> Result<(), Error> {
let mut total_c = G1Projective::identity();
let mut total_w = G1Projective::identity();
let challenge = transcript.challenge_scalar(b"batch"); // XXX: Verifier can add their own randomness at this point
let powers = util::powers_of(&challenge, proofs.len() - 1);
// Instead of multiplying g and gamma_g in each turn, we simply accumulate
// their coefficients and perform a final multiplication at the end.
let mut g_multiplier = BlsScalar::zero();
for ((proof, challenge), point) in proofs.iter().zip(powers).zip(points) {
let mut c = G1Projective::from(proof.commitment_to_polynomial.0);
let w = proof.commitment_to_witness.0;
c += w * point;
g_multiplier += challenge * proof.evaluated_point;
total_c += c * challenge;
total_w += w * challenge;
}
total_c -= self.g * g_multiplier;
let affine_total_w = G1Affine::from(-total_w);
let affine_total_c = G1Affine::from(total_c);
let pairing = dusk_bls12_381::multi_miller_loop(&[
(&affine_total_w, &self.prepared_beta_h),
(&affine_total_c, &self.prepared_h),
])
.final_exponentiation();
if pairing != dusk_bls12_381::Gt::identity() {
return Err(Error::PairingCheckFailure);
};
Ok(())
}
实际,Plonk中针对的场景为,evaluation points中实际仅有2组不同,分别表示为 z , z ′ z,z' z,z′,具体示例为:【即proof 个数与不同的points个数一致】
#[test]
fn test_batch_with_aggregation() {
let max_degree = 28;
let (proving_key, opening_key) = setup_test(max_degree);
let point_a = BlsScalar::from(10);
let point_b = BlsScalar::from(11);
// Committer's View
let (aggregated_proof, single_proof) = {
// Compute secret polynomial and their evaluations
let poly_a = Polynomial::rand(25, &mut rand::thread_rng());
let poly_a_eval = poly_a.evaluate(&point_a);
let poly_b = Polynomial::rand(26, &mut rand::thread_rng());
let poly_b_eval = poly_b.evaluate(&point_a);
let poly_c = Polynomial::rand(27, &mut rand::thread_rng());
let poly_c_eval = poly_c.evaluate(&point_a);
let poly_d = Polynomial::rand(28, &mut rand::thread_rng());
let poly_d_eval = poly_d.evaluate(&point_b);
let aggregated_proof = proving_key
.open_multiple(
&[poly_a, poly_b, poly_c],
vec![poly_a_eval, poly_b_eval, poly_c_eval],
&point_a,
&mut Transcript::new(b"agg_batch"),
)
.unwrap();
let single_proof = proving_key
.open_single(&poly_d, &poly_d_eval, &point_b)
.unwrap();
(aggregated_proof, single_proof)
};
// Verifier's View
let ok = {
let mut transcript = Transcript::new(b"agg_batch");
let flattened_proof = aggregated_proof.flatten(&mut transcript);
opening_key.batch_check(
&[point_a, point_b],
&[flattened_proof, single_proof],
&mut transcript,
)
};
assert!(ok.is_ok());
}
(I)FFT 仅适于 multiplicative subgroup of size that is a power-of-2。
具体代码见https://github.com/3for/plonk/blob/master/src/fft/
目录:
/// Performs O(nlogn) multiplication of polynomials if F is smooth.
impl<'a, 'b> Mul<&'a Polynomial> for &'b Polynomial {
type Output = Polynomial;
#[inline]
fn mul(self, other: &'a Polynomial) -> Polynomial {
if self.is_zero() || other.is_zero() {
Polynomial::zero()
} else {
let domain = EvaluationDomain::new(self.coeffs.len() + other.coeffs.len())
.expect("field is not smooth enough to construct domain");
let mut self_evals = Evaluations::from_vec_and_domain(domain.fft(&self.coeffs), domain);
let other_evals = Evaluations::from_vec_and_domain(domain.fft(&other.coeffs), domain);
self_evals *= &other_evals;
self_evals.interpolate()
}
}
}
/// Defines a domain over which finite field (I)FFTs can be performed. Works
/// only for fields that have a large multiplicative subgroup of size that is
/// a power-of-2.
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct EvaluationDomain {
/// The size of the domain.
pub size: u64,
/// `log_2(self.size)`.
pub log_size_of_group: u32,
/// Size of the domain as a field element.
pub size_as_field_element: BlsScalar,
/// Inverse of the size in the field.
pub size_inv: BlsScalar,
/// A generator of the subgroup.
pub group_gen: BlsScalar,
/// Inverse of the generator of the subgroup.
pub group_gen_inv: BlsScalar,
/// Multiplicative generator of the finite field.
pub generator_inv: BlsScalar,
}
coset FFT的主要作用是将n域的系数扩展至4n域内,从而加速 点值表示方式下 的求商运算 得 quotient polynomial: t ( X ) = z ( X ) − z H ( X ) z H ( X ) = f ( X ) X n − 1 t(X)=\frac{z(X)-z_H(X)}{z_H(X)}=\frac{f(X)}{X^n-1} t(X)=zH(X)z(X)−zH(X)=Xn−1f(X)。缺点是增加了Prover需要维护的信息量,由n扩充至4n。
假设order为 p p p的有限域内,相应的generator为 g g g,有 g p − 1 ≡ 1 m o d p g^{p-1}\equiv 1\mod p gp−1≡1modp。
以domain_n n = 8 n=8 n=8为例,假设有 w 8 ≡ 1 m o d p w^8\equiv 1\mod p w8≡1modp。
则相应的domain_4n 中有, v 32 ≡ 1 m o d p v^{32}\equiv 1\mod p v32≡1modp。
let q_m_eval_4n =
Evaluations::from_vec_and_domain(domain_4n.coset_fft(&selectors.q_m), domain_4n);
其中domain_n的q_m
多项式:
coset_fft()
函数是将系数扩充至domain_4n,多项式q_m_4n
:
q_m_eval_4n
即为相应的点值表示。】// Compute 4n evaluations for X^n -1
v_h_coset_4n: domain_4n.compute_vanishing_poly_over_coset(domain.size() as u64),
compute_vanishing_poly_over_coset()
中的 evaluations v_h
为:
[ g 8 − 1 g 8 v 8 − 1 g 8 v 16 − 1 g 8 v 24 − 1 g 8 − 1 g 8 v 8 − 1 g 8 v 16 − 1 g 8 v 24 − 1 g 8 − 1 g 8 v 8 − 1 g 8 v 16 − 1 g 8 v 24 − 1 g 8 − 1 g 8 v 8 − 1 g 8 v 16 − 1 g 8 v 24 − 1 g 8 − 1 g 8 v 8 − 1 g 8 v 16 − 1 g 8 v 24 − 1 g 8 − 1 g 8 v 8 − 1 g 8 v 16 − 1 g 8 v 24 − 1 g 8 − 1 g 8 v 8 − 1 g 8 v 16 − 1 g 8 v 24 − 1 g 8 − 1 g 8 v 8 − 1 g 8 v 16 − 1 g 8 v 24 − 1 ] \begin{bmatrix} g^8-1 & g^8v^8-1 & g^8v^{16}-1 & g^8v^{24}-1\\ g^8-1 & g^8v^8-1 & g^8v^{16}-1 & g^8v^{24}-1\\ g^8-1 & g^8v^8-1 & g^8v^{16}-1 & g^8v^{24}-1\\ g^8-1 & g^8v^8-1 & g^8v^{16}-1 & g^8v^{24}-1\\ g^8-1 & g^8v^8-1 & g^8v^{16}-1 & g^8v^{24}-1\\ g^8-1 & g^8v^8-1 & g^8v^{16}-1 & g^8v^{24}-1\\ g^8-1 & g^8v^8-1 & g^8v^{16}-1 & g^8v^{24}-1\\ g^8-1 & g^8v^8-1 & g^8v^{16}-1 & g^8v^{24}-1 \end{bmatrix} ⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡g8−1g8−1g8−1g8−1g8−1g8−1g8−1g8−1g8v8−1g8v8−1g8v8−1g8v8−1g8v8−1g8v8−1g8v8−1g8v8−1g8v16−1g8v16−1g8v16−1g8v16−1g8v16−1g8v16−1g8v16−1g8v16−1g8v24−1g8v24−1g8v24−1g8v24−1g8v24−1g8v24−1g8v24−1g8v24−1⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
相应的插值点为:
[ 1 v v 2 v 3 v 4 v 5 v 6 v 7 v 8 v 9 v 10 v 11 v 12 v 13 v 14 v 15 v 16 v 17 v 18 v 19 v 20 v 21 v 22 v 23 v 24 v 25 v 26 v 27 v 28 v 29 v 30 v 31 ] \begin{bmatrix} 1 & v & v^2 & v^3\\ v^4 & v^5 & v^6 & v^7\\ v^8 & v^9 & v^{10} & v^{11}\\ v^{12} & v^{13} & v^{14} & v^{15}\\ v^{16} & v^{17} & v^{18} & v^{19}\\ v^{20} & v^{21} & v^{22} & v^{23}\\ v^{24} & v^{25} & v^{26} & v^{27}\\ v^{28} & v^{29} & v^{30} & v^{31} \end{bmatrix} ⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡1v4v8v12v16v20v24v28vv5v9v13v17v21v25v29v2v6v10v14v18v22v26v30v3v7v11v15v19v23v27v31⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
具体代码实现见:https://github.com/3for/plonk/blob/master/src/permutation/permutation.rs
。
粒度在Variable
(以此为键值),每个变量可为多个门的左侧输入、右侧输入、输出或者是Fourth值,这些信息维护在相应的Vec
中。
/// Permutation provides the necessary state information and functions
/// to create the permutation polynomial. In the literature, Z(X) is the "accumulator",
/// this is what this codebase calls the permutation polynomial.
#[derive(Debug)]
pub struct Permutation {
// Maps a variable to the wires that it is associated to
pub(crate) variable_map: HashMap>,
}
/// The value is a reference to the actual value that was added to the constraint system
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
pub struct Variable(pub(crate) usize);
impl Into<(BlsScalar, Variable)> for Variable {
fn into(self) -> (BlsScalar, Variable) {
(BlsScalar::one(), self)
}
}
/// Stores the data for a specific wire in an arithmetic circuit
/// This data is the gate index and the type of wire
/// Left(1) signifies that this wire belongs to the first gate and is the left wire
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum WireData {
/// Left Wire of n'th gate
Left(usize),
/// Right Wire of n'th gate
Right(usize),
/// Output Wire of n'th gate
Output(usize),
/// Fourth Wire of n'th gate
Fourth(usize),
}
假设总共有 n n n个gate,令 ω \omega ω为 n n n-th root of unity,即在scalar 域内,满足 ω n = 1 \omega^n=1 ωn=1。
令 H = { 1 , ω , ⋯ , ω n − 1 } H=\{1,\omega,\cdots,\omega^{n-1}\} H={ 1,ω,⋯,ωn−1},取 k 1 , k 2 , k 3 ∈ F k_1,k_2,k_3\in\mathbb{F} k1,k2,k3∈F,满足 H , k 1 ⋅ H , k 2 ⋅ H , k 3 ⋅ H H,k_1\cdot H,k_2\cdot H, k_3\cdot H H,k1⋅H,k2⋅H,k3⋅H 为distinct cosets of H H H in F ∗ \mathbb{F}^* F∗。
/// Constants used in the permutation argument to ensure that the wire subsets are disjoint.
pub(crate) const K1: BlsScalar = BlsScalar::from_raw([7, 0, 0, 0]);
pub(crate) const K2: BlsScalar = BlsScalar::from_raw([13, 0, 0, 0]);
pub(crate) const K3: BlsScalar = BlsScalar::from_raw([17, 0, 0, 0]);
以 n = 4 n=4 n=4为例,具体见test_permutation_compute_sigmas_only_left_wires
,采用lagrange插值,插值点为 ( 1 , ω , ⋯ , ω 3 ) (1,\omega,\cdots,\omega^3) (1,ω,⋯,ω3),使得:
permutation是根据实际各个wire与Variable之间的逻辑关系,进行了调整后再插值:
let num_wire_mappings = 4;
// !此处即为实际各个wire与Variable之间的逻辑关系!
// Add four wire mappings
perm.add_variables_to_map(var_zero, var_zero, var_five, var_nine, 0);
perm.add_variables_to_map(var_zero, var_two, var_six, var_nine, 1);
perm.add_variables_to_map(var_zero, var_three, var_seven, var_nine, 2);
perm.add_variables_to_map(var_zero, var_four, var_eight, var_nine, 3);
/*
var_zero = {L0, R0, L1, L2, L3}
var_two = {R1}
var_three = {R2}
var_four = {R3}
var_five = {O0}
var_six = {O1}
var_seven = {O2}
var_eight = {O3}
var_nine = {F0, F1, F2, F3}
Left_sigma = {R0, L2, L3, L0}
Right_sigma = {L1, R1, R2, R3}
Out_sigma = {O0, O1, O2, O3}
Fourth_sigma = {F1, F2, F3, F0}
*/
let sigmas = perm.compute_sigma_permutations(num_wire_mappings);
let left_sigma = &sigmas[0];
let right_sigma = &sigmas[1];
let out_sigma = &sigmas[2];
let fourth_sigma = &sigmas[3];
// Check the left sigma polynomial
assert_eq!(left_sigma[0], WireData::Right(0));
assert_eq!(left_sigma[1], WireData::Left(2));
assert_eq!(left_sigma[2], WireData::Left(3));
assert_eq!(left_sigma[3], WireData::Left(0));
// Check the right sigma polynomial
assert_eq!(right_sigma[0], WireData::Left(1));
assert_eq!(right_sigma[1], WireData::Right(1));
assert_eq!(right_sigma[2], WireData::Right(2));
assert_eq!(right_sigma[3], WireData::Right(3));
// Check the output sigma polynomial
assert_eq!(out_sigma[0], WireData::Output(0));
assert_eq!(out_sigma[1], WireData::Output(1));
assert_eq!(out_sigma[2], WireData::Output(2));
assert_eq!(out_sigma[3], WireData::Output(3));
// Check the output sigma polynomial
assert_eq!(fourth_sigma[0], WireData::Fourth(1));
assert_eq!(fourth_sigma[1], WireData::Fourth(2));
assert_eq!(fourth_sigma[2], WireData::Fourth(3));
assert_eq!(fourth_sigma[3], WireData::Fourth(0));
let domain = EvaluationDomain::new(num_wire_mappings).unwrap();
let w: Fr = domain.group_gen;
let w_squared = w.pow(&[2, 0, 0, 0]);
let w_cubed = w.pow(&[3, 0, 0, 0]);
// Check the left sigmas have been encoded properly
// Left_sigma = {R0, L2, L3, L0}
// Should turn into {1 * K1, w^2, w^3, 1}
let encoded_left_sigma = perm.compute_permutation_lagrange(left_sigma, &domain);
assert_eq!(encoded_left_sigma[0], Fr::one() * &K1);
assert_eq!(encoded_left_sigma[1], w_squared);
assert_eq!(encoded_left_sigma[2], w_cubed);
assert_eq!(encoded_left_sigma[3], Fr::one());
// Check the right sigmas have been encoded properly
// Right_sigma = {L1, R1, R2, R3}
// Should turn into {w, w * K1, w^2 * K1, w^3 * K1}
let encoded_right_sigma = perm.compute_permutation_lagrange(right_sigma, &domain);
assert_eq!(encoded_right_sigma[0], w);
assert_eq!(encoded_right_sigma[1], w * &K1);
assert_eq!(encoded_right_sigma[2], w_squared * &K1);
assert_eq!(encoded_right_sigma[3], w_cubed * &K1);
// Check the output sigmas have been encoded properly
// Out_sigma = {O0, O1, O2, O3}
// Should turn into {1 * K2, w * K2, w^2 * K2, w^3 * K2}
let encoded_output_sigma = perm.compute_permutation_lagrange(out_sigma, &domain);
assert_eq!(encoded_output_sigma[0], Fr::one() * &K2);
assert_eq!(encoded_output_sigma[1], w * &K2);
assert_eq!(encoded_output_sigma[2], w_squared * &K2);
assert_eq!(encoded_output_sigma[3], w_cubed * &K2);
// Check the fourth sigmas have been encoded properly
// Out_sigma = {F1, F2, F3, F0}
// Should turn into {w * K3, w^2 * K3, w^3 * K3, 1 * K3}
let encoded_fourth_sigma = perm.compute_permutation_lagrange(fourth_sigma, &domain);
assert_eq!(encoded_fourth_sigma[0], w * &K3);
assert_eq!(encoded_fourth_sigma[1], w_squared * &K3);
assert_eq!(encoded_fourth_sigma[2], w_cubed * &K3);
assert_eq!(encoded_fourth_sigma[3], K3);
fn compute_permutation_lagrange(
&self,
sigma_mapping: &[WireData],
domain: &EvaluationDomain,
) -> Vec {
let roots: Vec<_> = domain.elements().collect();
let lagrange_poly: Vec = sigma_mapping
.iter()
.map(|x| match x {
WireData::Left(index) => {
let root = &roots[*index];
*root
}
WireData::Right(index) => {
let root = &roots[*index];
K1 * root
}
WireData::Output(index) => {
let root = &roots[*index];
K2 * root
}
WireData::Fourth(index) => {
let root = &roots[*index];
K3 * root
}
})
.collect();
lagrange_poly
}
test_correct_permutation_poly
中:【仍然以 n = 4 n=4 n=4为例, ω 4 = 1 \omega^4=1 ω4=1】
compute_permutation_poly()
函数实际计算的即为z_poly多项式。】plonk论文中列出的是:
fan-in 2 + 只有加法和乘法门 的circuit。
而在dusk-network的plonk代码实现中,扩展为了:
//! It allows us not only to build Add and Mul constraints but also to build
//! ECC op. gates, Range checks, Logical gates (Bitwise ops) etc.
因此:
zero_var: Variable(0)
。】k==0
: let k = qarith * ((qm * a * b) + (ql * a) + (qr * b) + (qo * c) + (q4 * d) + pi + qc)
+ qlogic
* (((delta(a_next - four * a) - delta(b_next - four * b)) * c)
+ delta(a_next - four * a)
+ delta(b_next - four * b)
+ delta(d_next - four * d)
+ match (qlogic == BlsScalar::one(), qlogic == -BlsScalar::one()) {
(true, false) => (a & b) - d,
(false, true) => (a ^ b) - d,
(false, false) => BlsScalar::zero(),
_ => unreachable!(),
})
+ qrange
* (delta(c - four * d)
+ delta(b - four * c)
+ delta(a - four * b)
+ delta(d_next - four * a));
assert_eq!(k, BlsScalar::zero(), "Check failed at gate {}", i,);
其中delta
函数用于保证f
值仅为0 或 1 或 2 或 3,使得最终delta
函数的输出为0值:【原因在于logic_gate
中是同时对两个bit进行and或xor操作。与ebfull/halo中的xor方案不同,ebfull中是对单个bit进行and或xor操作。】
// Computes f(f-1)(f-2)(f-3)
let delta = |f: BlsScalar| -> BlsScalar {
let f_1 = f - BlsScalar::one();
let f_2 = f - BlsScalar::from(2);
let f_3 = f - BlsScalar::from(3);
f * f_1 * f_2 * f_3
};
具体的结构体设计为:
/// A composer is a circuit builder
/// and will dictate how a circuit is built
/// We will have a default Composer called `StandardComposer`
#[derive(Debug)]
pub struct StandardComposer {
// n represents the number of arithmetic gates in the circuit
pub(crate) n: usize,
// Selector vectors
//
// Multiplier selector
pub(crate) q_m: Vec,
// Left wire selector
pub(crate) q_l: Vec,
// Right wire selector
pub(crate) q_r: Vec,
// Output wire selector
pub(crate) q_o: Vec,
// Fourth wire selector
pub(crate) q_4: Vec,
// Constant wire selector
pub(crate) q_c: Vec,
// Arithmetic wire selector
pub(crate) q_arith: Vec,
// Range selector
pub(crate) q_range: Vec,
// Logic selector
pub(crate) q_logic: Vec,
// Fixed base group addition selector
pub(crate) q_fixed_group_add: Vec,
// Variable base group addition selector
pub(crate) q_variable_group_add: Vec,
/// Public inputs vector
pub public_inputs: Vec,
// Witness vectors
pub(crate) w_l: Vec,
pub(crate) w_r: Vec,
pub(crate) w_o: Vec,
pub(crate) w_4: Vec,
/// A zero variable that is a part of the circuit description.
/// We reserve a variable to be zero in the system
/// This is so that when a gate only uses three wires, we set the fourth wire to be
/// the variable that references zero
pub(crate) zero_var: Variable,
// These are the actual variable values
// N.B. They should not be exposed to the end user once added into the composer
pub(crate) variables: HashMap,
pub(crate) perm: Permutation,
}
对于常规的3-wire门,表示为:【其中 p i pi pi 表示 public inputs。】
/// Adds a width-3 poly gate.
/// This gate gives total freedom to the end user to implement the corresponding
/// circuits in the most optimized way possible because the under has access to the
/// whole set of variables, as well as selector coefficients that take part in the
/// computation of the gate equation.
///
/// The final constraint added will force the following:
/// `(a * b) * q_m + a * q_l + b * q_r + q_c + PI + q_o * c = 0`.
pub fn poly_gate(
&mut self,
a: Variable,
b: Variable,
c: Variable,
q_m: BlsScalar,
q_l: BlsScalar,
q_r: BlsScalar,
q_o: BlsScalar,
q_c: BlsScalar,
pi: BlsScalar,
) -> (Variable, Variable, Variable) {
self.w_l.push(a);
self.w_r.push(b);
self.w_o.push(c);
self.w_4.push(self.zero_var);
self.q_l.push(q_l);
self.q_r.push(q_r);
// Add selector vectors
self.q_m.push(q_m);
self.q_o.push(q_o);
self.q_c.push(q_c);
self.q_4.push(BlsScalar::zero());
self.q_arith.push(BlsScalar::one());
self.q_range.push(BlsScalar::zero());
self.q_logic.push(BlsScalar::zero());
self.q_fixed_group_add.push(BlsScalar::zero());
self.q_variable_group_add.push(BlsScalar::zero());
self.public_inputs.push(pi);
self.perm
.add_variables_to_map(a, b, c, self.zero_var, self.n);
self.n += 1;
(a, b, c)
}
支持的constraint类型有:
constant constraint:即某变量值等于某常数,如a
变量满足 a - constant + pi = 0
。
/// Adds a gate which is designed to constrain a `Variable` to have
/// a specific constant value which is sent as a `BlsScalar`.
pub fn constrain_to_constant(&mut self, a: Variable, constant: BlsScalar, pi: BlsScalar) {
self.poly_gate(
a,
a,
a,
BlsScalar::zero(),
BlsScalar::one(),
BlsScalar::zero(),
BlsScalar::zero(),
-constant,
pi,
);
}
constant constraint可用于约束circuit中的某个point为某个特定的public point:【验证public info】
/// Represents a JubJub point in the circuit
#[derive(Debug, Clone, Copy)]
pub struct Point {
x: Variable,
y: Variable,
}
/// Asserts that a point in the circuit is equal to a known public point
pub fn assert_equal_public_point(
&mut self,
point: Point,
public_point: dusk_jubjub::JubJubAffine,
) {
self.constrain_to_constant(point.x, BlsScalar::zero(), -public_point.get_x());
self.constrain_to_constant(point.y, BlsScalar::zero(), -public_point.get_y());
}
equal constraint:即某两个变量相等,a = b
。
/// Asserts that two variables are the same
// XXX: Instead of wasting a gate, we can use the permutation polynomial to do this
pub fn assert_equal(&mut self, a: Variable, b: Variable) {
self.poly_gate(
a,
b,
self.zero_var,
BlsScalar::zero(),
BlsScalar::one(),
-BlsScalar::one(),
BlsScalar::zero(),
BlsScalar::zero(),
BlsScalar::zero(),
);
}
equal constraint可用于约束circuit中的某两个point是相等的:【验证private info】
/// Asserts that a point in the circuit is equal to another point in the circuit
pub fn assert_equal_point(&mut self, point_a: Point, point_b: Point) {
self.assert_equal(point_a.x, point_b.x);
self.assert_equal(point_b.y, point_b.y);
}
对于有 n n n个gate的circuit,其constraint数量为 n + 3 n+3 n+3,其中那3个为:
// Reserve the first variable to be zero
composer.zero_var = composer.add_witness_to_circuit_description(BlsScalar::zero());
/// This function is used to add a blinding factor to the witness polynomials
/// XXX: Split this into two separate functions and document
/// XXX: We could add another section to add random witness variables, with selector polynomials all zero
pub fn add_dummy_constraints(&mut self) {
// Add a dummy constraint so that we do not have zero polynomials
self.q_m.push(BlsScalar::from(1));
self.q_l.push(BlsScalar::from(2));
self.q_r.push(BlsScalar::from(3));
self.q_o.push(BlsScalar::from(4));
self.q_c.push(BlsScalar::from(4));
self.q_4.push(BlsScalar::one());
self.q_arith.push(BlsScalar::one());
self.q_range.push(BlsScalar::zero());
self.q_logic.push(BlsScalar::zero());
self.q_fixed_group_add.push(BlsScalar::zero());
self.q_variable_group_add.push(BlsScalar::zero());
self.public_inputs.push(BlsScalar::zero());
let var_six = self.add_input(BlsScalar::from(6));
let var_one = self.add_input(BlsScalar::from(1));
let var_seven = self.add_input(BlsScalar::from(7));
let var_min_twenty = self.add_input(-BlsScalar::from(20));
self.w_l.push(var_six);
self.w_r.push(var_seven);
self.w_o.push(var_min_twenty);
self.w_4.push(var_one);
self.perm
.add_variables_to_map(var_six, var_seven, var_min_twenty, var_one, self.n);
self.n += 1;
//Add another dummy constraint so that we do not get the identity permutation
self.q_m.push(BlsScalar::from(1));
self.q_l.push(BlsScalar::from(1));
self.q_r.push(BlsScalar::from(1));
self.q_o.push(BlsScalar::from(1));
self.q_c.push(BlsScalar::from(127));
self.q_4.push(BlsScalar::zero());
self.q_arith.push(BlsScalar::one());
self.q_range.push(BlsScalar::zero());
self.q_logic.push(BlsScalar::zero());
self.q_fixed_group_add.push(BlsScalar::zero());
self.q_variable_group_add.push(BlsScalar::zero());
self.public_inputs.push(BlsScalar::zero());
self.w_l.push(var_min_twenty);
self.w_r.push(var_six);
self.w_o.push(var_seven);
self.w_4.push(self.zero_var);
self.perm
.add_variables_to_map(var_min_twenty, var_six, var_seven, self.zero_var, self.n);
self.n += 1;
}
即对于空circuit,默认3个constraint,相应的测试用例为:
#[test]
/// Tests that a circuit initially has 3 gates
fn test_initial_circuit_size() {
let composer: StandardComposer = StandardComposer::new();
// Circuit size is n+3 because
// - We have an extra gate which forces the first witness to be zero. This is used when the advice wire is not being used.
// - We have two gates which ensure that the permutation polynomial is not the identity and
// - Another gate which ensures that the selector polynomials are not all zeroes
assert_eq!(3, composer.circuit_size());
composer.check_circuit_satisfied(); //打印调试信息。feature中增加"print-trace"
}
相应的circuit gate satisfied 打印信息为:
--------------------------------------------
#Gate Index = 0
#Selector Polynomials:
- qm -> 0000000000000000000000000000000000000000000000000000000000000000
- ql -> 0100000000000000000000000000000000000000000000000000000000000000
- qr -> 0000000000000000000000000000000000000000000000000000000000000000
- q4 -> 0000000000000000000000000000000000000000000000000000000000000000
- qo -> 0000000000000000000000000000000000000000000000000000000000000000
- qc -> 0000000000000000000000000000000000000000000000000000000000000000
- q_arith -> 0100000000000000000000000000000000000000000000000000000000000000
- q_range -> 0000000000000000000000000000000000000000000000000000000000000000
- q_logic -> 0000000000000000000000000000000000000000000000000000000000000000
- q_fixed_group_add -> 0000000000000000000000000000000000000000000000000000000000000000
- q_variable_group_add -> 0000000000000000000000000000000000000000000000000000000000000000
# Witness polynomials:
- w_l -> 0000000000000000000000000000000000000000000000000000000000000000
- w_r -> 0000000000000000000000000000000000000000000000000000000000000000
- w_o -> 0000000000000000000000000000000000000000000000000000000000000000
- w_4 -> 0000000000000000000000000000000000000000000000000000000000000000
--------------------------------------------
#Gate Index = 1
#Selector Polynomials:
- qm -> 0100000000000000000000000000000000000000000000000000000000000000
- ql -> 0200000000000000000000000000000000000000000000000000000000000000
- qr -> 0300000000000000000000000000000000000000000000000000000000000000
- q4 -> 0100000000000000000000000000000000000000000000000000000000000000
- qo -> 0400000000000000000000000000000000000000000000000000000000000000
- qc -> 0400000000000000000000000000000000000000000000000000000000000000
- q_arith -> 0100000000000000000000000000000000000000000000000000000000000000
- q_range -> 0000000000000000000000000000000000000000000000000000000000000000
- q_logic -> 0000000000000000000000000000000000000000000000000000000000000000
- q_fixed_group_add -> 0000000000000000000000000000000000000000000000000000000000000000
- q_variable_group_add -> 0000000000000000000000000000000000000000000000000000000000000000
# Witness polynomials:
- w_l -> 0600000000000000000000000000000000000000000000000000000000000000
- w_r -> 0700000000000000000000000000000000000000000000000000000000000000
- w_o -> edfffffffefffffffe5bfeff02a4bd5305d8a10908d83933487d9d2953a7ed73
- w_4 -> 0100000000000000000000000000000000000000000000000000000000000000
--------------------------------------------
#Gate Index = 2
#Selector Polynomials:
- qm -> 0100000000000000000000000000000000000000000000000000000000000000
- ql -> 0100000000000000000000000000000000000000000000000000000000000000
- qr -> 0100000000000000000000000000000000000000000000000000000000000000
- q4 -> 0000000000000000000000000000000000000000000000000000000000000000
- qo -> 0100000000000000000000000000000000000000000000000000000000000000
- qc -> 7f00000000000000000000000000000000000000000000000000000000000000
- q_arith -> 0100000000000000000000000000000000000000000000000000000000000000
- q_range -> 0000000000000000000000000000000000000000000000000000000000000000
- q_logic -> 0000000000000000000000000000000000000000000000000000000000000000
- q_fixed_group_add -> 0000000000000000000000000000000000000000000000000000000000000000
- q_variable_group_add -> 0000000000000000000000000000000000000000000000000000000000000000
# Witness polynomials:
- w_l -> edfffffffefffffffe5bfeff02a4bd5305d8a10908d83933487d9d2953a7ed73
- w_r -> 0600000000000000000000000000000000000000000000000000000000000000
- w_o -> 0700000000000000000000000000000000000000000000000000000000000000
- w_4 -> 0000000000000000000000000000000000000000000000000000000000000000
支持left wire、right wire、output wire和fourth wire的加法门。
对于mul gate, q l , q r q_l,q_r ql,qr恒为0。
/// Adds a width-3 add gate to the circuit linking the product of the
/// provided inputs scaled by the selector coefficient `q_m` with the output
/// provided scaled by `q_o`.
///
/// Note that this gate requires to provide the actual result of the gate
/// (output wire) since it will just add a `mul constraint` to the circuit.
pub fn mul_gate(
&mut self,
a: Variable,
b: Variable,
c: Variable,
q_m: BlsScalar,
q_o: BlsScalar,
q_c: BlsScalar,
pi: BlsScalar,
) -> Variable {
self.big_mul_gate(a, b, c, None, q_m, q_o, q_c, BlsScalar::zero(), pi)
}
/// Adds a width-4 `big_mul_gate` with the left, right and fourth inputs
/// and it's scaling factors, computing & returning the output (result)
/// `Variable` and adding the corresponding mul constraint.
///
/// This type of gate is usually used when we need to have
/// the largest amount of performance and the minimum circuit-size
/// possible. Since it allows the end-user to setup all of the selector
/// coefficients.
///
/// Forces `q_m * (w_l * w_r) + w_4 * q_4 + q_c + PI = q_o * w_o`.
///
/// `{w_l, w_r, w_o, w_4} = {a, b, c, d}`
// XXX: Maybe make these tuples instead of individual field?
pub fn big_mul_gate(
&mut self,
a: Variable,
b: Variable,
c: Variable,
d: Option,
q_m: BlsScalar,
q_o: BlsScalar,
q_c: BlsScalar,
q_4: BlsScalar,
pi: BlsScalar,
) -> Variable {
// Check if advice wire has a value
let d = match d {
Some(var) => var,
None => self.zero_var,
};
self.w_l.push(a);
self.w_r.push(b);
self.w_o.push(c);
self.w_4.push(d);
// For a mul gate q_L and q_R is zero
self.q_l.push(BlsScalar::zero());
self.q_r.push(BlsScalar::zero());
// Add selector vectors
self.q_m.push(q_m);
self.q_o.push(q_o);
self.q_c.push(q_c);
self.q_4.push(q_4);
self.q_arith.push(BlsScalar::one());
self.q_range.push(BlsScalar::zero());
self.q_logic.push(BlsScalar::zero());
self.q_fixed_group_add.push(BlsScalar::zero());
self.q_variable_group_add.push(BlsScalar::zero());
self.public_inputs.push(pi);
self.perm.add_variables_to_map(a, b, c, d, self.n);
self.n += 1;
c
}
以及
/// Adds a simple and basic addition to the circuit between to `Variable`s
/// returning the resulting `Variable`.
pub fn mul(
&mut self,
q_m: BlsScalar,
a: Variable,
b: Variable,
q_c: BlsScalar,
pi: BlsScalar,
) -> Variable {
self.big_mul(q_m, a, b, None, q_c, pi)
}
/// Adds a width-4 `big_mul_gate` with the left, right and fourth inputs
/// and it's scaling factors, computing & returning the output (result)
/// `Variable` and adding the corresponding mul constraint.
///
/// This type of gate is usually used when we don't need to have
/// the largest amount of performance and the minimum circuit-size
/// possible. Since it defaults some of the selector coeffs = 0 in order
/// to reduce the verbosity and complexity.
///
/// Forces `q_m * (w_l * w_r) + w_4 * q_4 + q_c + PI = w_o(computed by the gate)`.
///
/// `{w_l, w_r, w_4} = {a, b, d}`
// XXX: This API is not consistent. It should use tuples and not individual fields
pub fn big_mul(
&mut self,
q_m: BlsScalar,
a: Variable,
b: Variable,
q_4_d: Option<(BlsScalar, Variable)>,
q_c: BlsScalar,
pi: BlsScalar,
) -> Variable {
let q_o = -BlsScalar::one();
// Check if advice wire is available
let (q_4, d) = match q_4_d {
Some((q_4, var)) => (q_4, var),
None => (BlsScalar::zero(), self.zero_var),
};
// Compute output wire
let a_eval = self.variables[&a];
let b_eval = self.variables[&b];
let d_eval = self.variables[&d];
let c_eval = (q_m * a_eval * b_eval) + (q_4 * d_eval) + q_c + pi;
let c = self.add_input(c_eval);
self.big_mul_gate(a, b, c, Some(d), q_m, q_o, q_c, q_4, pi)
}
支持left wire、right wire、output wire和fourth wire的加法门。
对于add gate, q m q_m qm恒为0。
/// Adds a width-3 add gate to the circuit, linking the addition of the
/// provided inputs, scaled by the selector coefficients with the output
/// provided.
pub fn add_gate(
&mut self,
a: Variable,
b: Variable,
c: Variable,
q_l: BlsScalar,
q_r: BlsScalar,
q_o: BlsScalar,
q_c: BlsScalar,
pi: BlsScalar,
) -> Variable {
self.big_add_gate(a, b, c, None, q_l, q_r, q_o, BlsScalar::zero(), q_c, pi)
}
/// Adds a width-4 add gate to the circuit and it's corresponding
/// constraint.
///
/// This type of gate is usually used when we need to have
/// the largest amount of performance and the minimum circuit-size
/// possible. Since it allows the end-user to set every selector coefficient
/// as scaling value on the gate eq.
pub fn big_add_gate(
&mut self,
a: Variable,
b: Variable,
c: Variable,
d: Option,
q_l: BlsScalar,
q_r: BlsScalar,
q_o: BlsScalar,
q_4: BlsScalar,
q_c: BlsScalar,
pi: BlsScalar,
) -> Variable {
// Check if advice wire has a value
let d = match d { //若为None,则解析为零变量。
Some(var) => var,
None => self.zero_var,
};
self.w_l.push(a);
self.w_r.push(b);
self.w_o.push(c);
self.w_4.push(d);
// For an add gate, q_m is zero
self.q_m.push(BlsScalar::zero());
// Add selector vectors
self.q_l.push(q_l);
self.q_r.push(q_r);
self.q_o.push(q_o);
self.q_c.push(q_c);
self.q_4.push(q_4);
self.q_arith.push(BlsScalar::one());
self.q_range.push(BlsScalar::zero());
self.q_logic.push(BlsScalar::zero());
self.q_fixed_group_add.push(BlsScalar::zero());
self.q_variable_group_add.push(BlsScalar::zero());
self.public_inputs.push(pi);
self.perm.add_variables_to_map(a, b, c, d, self.n);
self.n += 1;
c
}
以及
/// Adds a `big_addition_gate` with the left and right inputs
/// and it's scaling factors, computing & returning the output (result)
/// `Variable`, and adding the corresponding addition constraint.
///
/// This type of gate is usually used when we don't need to have
/// the largest amount of performance as well as the minimum circuit-size
/// possible. Since it defaults some of the selector coeffs = 0 in order
/// to reduce the verbosity and complexity.
///
/// Forces `q_l * w_l + q_r * w_r + q_c + PI = w_o(computed by the gate)`.
pub fn add(
&mut self,
q_l_a: (BlsScalar, Variable),
q_r_b: (BlsScalar, Variable),
q_c: BlsScalar,
pi: BlsScalar,
) -> Variable {
self.big_add(q_l_a, q_r_b, None, q_c, pi)
}
/// Adds a `big_addition_gate` with the left, right and fourth inputs
/// and it's scaling factors, computing & returning the output (result)
/// `Variable` and adding the corresponding addition constraint.
///
/// This type of gate is usually used when we don't need to have
/// the largest amount of performance and the minimum circuit-size
/// possible. Since it defaults some of the selector coeffs = 0 in order
/// to reduce the verbosity and complexity.
///
/// Forces `q_l * w_l + q_r * w_r + q_4 * w_4 + q_c + PI = w_o(computed by the gate)`.
pub fn big_add(
&mut self,
q_l_a: (BlsScalar, Variable),
q_r_b: (BlsScalar, Variable),
q_4_d: Option<(BlsScalar, Variable)>,
q_c: BlsScalar,
pi: BlsScalar,
) -> Variable {
// Check if advice wire is available
let (q_4, d) = match q_4_d {
Some((q_4, var)) => (q_4, var),
None => (BlsScalar::zero(), self.zero_var),
};
let (q_l, a) = q_l_a;
let (q_r, b) = q_r_b;
let q_o = -BlsScalar::one();
// Compute the output wire
let a_eval = self.variables[&a];
let b_eval = self.variables[&b];
let d_eval = self.variables[&d];
let c_eval = (q_l * a_eval) + (q_r * b_eval) + (q_4 * d_eval) + q_c + pi;
let c = self.add_input(c_eval);
self.big_add_gate(a, b, c, Some(d), q_l, q_r, q_o, q_4, q_c, pi)
}
boolean constraint 又名 binary constraint,是指约束变量值要么为“0”,要么为“1”。
核心思想就是:若 a ( 1 − a ) = 0 a(1-a)=0 a(1−a)=0,即 a a a为0或1。
/// Adds a boolean constraint (also known as binary constraint) where
/// the gate eq. will enforce that the `Variable` received is either `0`
/// or `1` by adding a constraint in the circuit.
///
/// Note that using this constraint with whatever `Variable` that is not
/// representing a value equalling 0 or 1, will always force the equation to fail.
pub fn boolean_gate(&mut self, a: Variable) -> Variable {
self.w_l.push(a);
self.w_r.push(a);
self.w_o.push(a);
self.w_4.push(self.zero_var);
self.q_m.push(BlsScalar::one());
self.q_l.push(BlsScalar::zero());
self.q_r.push(BlsScalar::zero());
self.q_o.push(-BlsScalar::one());
self.q_c.push(BlsScalar::zero());
self.q_4.push(BlsScalar::zero());
self.q_arith.push(BlsScalar::one());
self.q_range.push(BlsScalar::zero());
self.q_logic.push(BlsScalar::zero());
self.q_fixed_group_add.push(BlsScalar::zero());
self.q_variable_group_add.push(BlsScalar::zero());
self.public_inputs.push(BlsScalar::zero());
self.perm
.add_variables_to_map(a, a, a, self.zero_var, self.n);
self.n += 1;
a
}
#[derive(Debug, Clone, Copy)]
/// Contains all of the components needed to verify that a bit scalar multiplication was computed correctly
pub(crate) struct WnafRound {
/// This is the accumulated x coordinate point that we wish to add (so far.. depends on where you are in the scalar mul)
/// it is linked to the wnaf entry, so must not be revealed
pub acc_x: Variable,
/// This is the accumulated y coordinate
pub acc_y: Variable,
/// This is the wnaf accumulated entry
/// For all intents and purposes, you can think of this as the secret bit
pub accumulated_bit: Variable,
/// This is the multiplication of x_\alpha * y_\alpha
/// we need this as a distinct wire, so that the degree of the polynomial does not go over 4
pub xy_alpha: Variable,
/// This is the possible x co-ordinate of the wnaf point we are going to add
/// Actual x-co-ordinate = b_i * x_\beta
pub x_beta: BlsScalar,
/// This is the possible y co-ordinate of the wnaf point we are going to add
/// Actual y coordinate = (b_i)^2 [y_\beta -1] + 1
pub y_beta: BlsScalar,
/// This is the multiplication of x_\beta * y_\beta
pub xy_beta: BlsScalar,
}
/// Fixed group addition of a jubjub point
pub(crate) fn fixed_group_add(&mut self, wnaf_round: WnafRound) {
self.w_l.push(wnaf_round.acc_x);
self.w_r.push(wnaf_round.acc_y);
self.w_o.push(wnaf_round.xy_alpha);
self.w_4.push(wnaf_round.accumulated_bit);
self.q_l.push(wnaf_round.x_beta);
self.q_r.push(wnaf_round.y_beta);
self.q_c.push(wnaf_round.xy_beta);
self.q_o.push(BlsScalar::zero());
self.q_fixed_group_add.push(BlsScalar::one());
self.q_variable_group_add.push(BlsScalar::zero());
self.q_m.push(BlsScalar::zero());
self.q_4.push(BlsScalar::zero());
self.q_arith.push(BlsScalar::zero());
self.q_range.push(BlsScalar::zero());
self.q_logic.push(BlsScalar::zero());
self.public_inputs.push(BlsScalar::zero());
self.perm.add_variables_to_map(
wnaf_round.acc_x,
wnaf_round.acc_y,
wnaf_round.xy_alpha,
wnaf_round.accumulated_bit,
self.n,
);
self.n += 1;
}
logic_gate
中是同时对两个bit进行and或xor操作。具体见https://github.com/dusk-network/plonk/blob/master/src/proof_system/widget/logic/proverkey.rs
中 compute_quotient_i()
函数:
pub(crate) fn compute_quotient_i(
&self,
index: usize,
logic_separation_challenge: &BlsScalar,
w_l_i: &BlsScalar,
w_l_i_next: &BlsScalar,
w_r_i: &BlsScalar,
w_r_i_next: &BlsScalar,
w_o_i: &BlsScalar,
w_4_i: &BlsScalar,
w_4_i_next: &BlsScalar,
) -> BlsScalar {
let four = BlsScalar::from(4);
let q_logic_i = &self.q_logic.1[index];
let q_c_i = &self.q_c.1[index];
let kappa = logic_separation_challenge.square();
let kappa_sq = kappa.square();
let kappa_cu = kappa_sq * kappa;
let kappa_qu = kappa_cu * kappa;
let a = w_l_i_next - four * w_l_i;
let c_0 = delta(a);
let b = w_r_i_next - four * w_r_i;
let c_1 = delta(b) * kappa;
let d = w_4_i_next - four * w_4_i;
let c_2 = delta(d) * kappa_sq;
let w = w_o_i;
let c_3 = (w - a * b) * kappa_cu;
let c_4 = delta_xor_and(&a, &b, w, &d, &q_c_i) * kappa_qu;
q_logic_i * (c_3 + c_0 + c_1 + c_2 + c_4) * logic_separation_challenge
}
其中的:
c_0,c_1,c_2,c_3
constraint:是约束wire的相应取值仅允许为 [ 0 , 1 , 2 , 3 ] [0, 1, 2, 3] [0,1,2,3] 中之一。// Computes f(f-1)(f-2)(f-3)
fn delta(f: BlsScalar) -> BlsScalar {
let f_1 = f - BlsScalar::one();
let f_2 = f - BlsScalar::from(2);
let f_3 = f - BlsScalar::from(3);
f * f_1 * f_2 * f_3
}
c_4
constraint:// The identity we want to check is q_logic * A = 0
// A = B + E
// B = q_c * [9c - 3(a+b)]
// E = 3(a+b+c) - 2F
// F = w[w(4w - 18(a+b) + 81) + 18(a^2 + b^2) - 81(a+b) + 83]
#[allow(non_snake_case)]
fn delta_xor_and(
a: &BlsScalar,
b: &BlsScalar,
w: &BlsScalar,
c: &BlsScalar,
q_c: &BlsScalar,
) -> BlsScalar {
let nine = BlsScalar::from(9);
let two = BlsScalar::from(2);
let three = BlsScalar::from(3);
let four = BlsScalar::from(4);
let eighteen = BlsScalar::from(18);
let eighty_one = BlsScalar::from(81);
let eighty_three = BlsScalar::from(83);
let F = w
* (w * (four * w - eighteen * (a + b) + eighty_one) + eighteen * (a.square() + b.square())
- eighty_one * (a + b)
+ eighty_three);
let E = three * (a + b + c) - (two * F);
let B = q_c * ((nine * c) - three * (a + b));
B + E
}
/// Adds a logical XOR gate that performs the XOR between two values for the
/// specified first `num_bits` returning a `Variable` holding the result.
///
/// # Panics
///
/// If the `num_bits` specified in the fn params is odd.
pub fn xor_gate(&mut self, a: Variable, b: Variable, num_bits: usize) -> Variable {
self.logic_gate(a, b, num_bits, true)
}
logic_gate
中是同时对两个bit进行and或xor操作。 and logic constraint 具体与 xor logic constraint 类似,主要差别在于:
w_o
表示的逻辑分别为and或xor的结果值。 // The `out_quad` is the result of the bitwise ops `&` or `^` between
// the left and right quads. The op is decided with a boolean flag set
// as input of the function.
let out_quad_fr = match is_xor_gate {
true => BlsScalar::from((left_quad ^ right_quad) as u64),
false => BlsScalar::from((left_quad & right_quad) as u64),
};
q_c
和q_logic
的取值不同: match is_xor_gate {
true => {
self.q_c.push(-BlsScalar::one());
self.q_logic.push(-BlsScalar::one());
}
false => {
self.q_c.push(BlsScalar::one());
self.q_logic.push(BlsScalar::one());
}
};
/// Adds a logical AND gate that performs the bitwise AND between two values
/// for the specified first `num_bits` returning a `Variable` holding the result.
///
/// # Panics
///
/// If the `num_bits` specified in the fn params is odd.
pub fn and_gate(&mut self, a: Variable, b: Variable, num_bits: usize) -> Variable {
self.logic_gate(a, b, num_bits, false)
}
具体见https://github.com/dusk-network/plonk/blob/master/src/constraint_system/range.rs
中 pub fn range_gate(&mut self, witness: Variable, num_bits: usize)
函数,对应的constraints数量为:
num_bits/8+1
以32bit为例,相应的constraint数量为32/8+1=5
。
实际代码中,设计了六种不同类型的gate,分别为:
pad
函数,通过附加零变量和零值的方式,使得circuit的constraint数量为power of two。/// Prover composes a circuit and builds a proof
#[allow(missing_debug_implementations)]
pub struct Prover {
/// ProverKey which is used to create proofs about a specific PLONK circuit
pub prover_key: Option,
pub(crate) cs: StandardComposer,
/// Store the messages exchanged during the preprocessing stage
/// This is copied each time, we make a proof
pub preprocessed_transcript: Transcript,
}
/// PLONK circuit proving key
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ProverKey {
/// Circuit size
pub n: usize,
/// ProverKey for arithmetic gate
pub arithmetic: arithmetic::ProverKey,
/// ProverKey for logic gate
pub logic: logic::ProverKey,
/// ProverKey for range gate
pub range: range::ProverKey,
/// ProverKey for fixed base curve addition gates
pub fixed_base: ecc::scalar_mul::fixed_base::ProverKey,
/// ProverKey for permutation checks
pub permutation: permutation::ProverKey,
/// ProverKey for variable base curve addition gates
pub variable_base: ecc::curve_addition::ProverKey,
// Pre-processes the 4n Evaluations for the vanishing polynomial, so they do not
// need to be computed at the proving stage.
// Note: With this, we can combine all parts of the quotient polynomial in their evaluation phase and
// divide by the quotient polynomial without having to perform IFFT
pub(crate) v_h_coset_4n: Evaluations,
}
实际实现时,根据circuit中gate类型分类不同,分别实现了不同的ProverKey
:
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ProverKey {
pub q_m: (Polynomial, Evaluations),
pub q_l: (Polynomial, Evaluations),
pub q_r: (Polynomial, Evaluations),
pub q_o: (Polynomial, Evaluations),
pub q_c: (Polynomial, Evaluations),
pub q_4: (Polynomial, Evaluations),
pub q_arith: (Polynomial, Evaluations),
}
Round 3 第三轮 quotient polynomial 计算对应表示为:
(a(x)b(x)q_M(x) + a(x)q_L(x) + b(X)q_R(x) + c(X)q_O(X) + d(x)q_4(X) + Q_C(X)) * Q_Arith(X)
Round 4 第四轮 linearisation polynomial 计算对应表示为:
(a_eval * b_eval * q_m_poly + a_eval * q_l + b_eval * q_r + c_eval * q_o + d_eval * q_4 + q_c) * q_arith_eval
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ProverKey {
pub q_c: (Polynomial, Evaluations),
pub q_logic: (Polynomial, Evaluations),
}
Round 3 第三轮 quotient polynomial 计算对应表示为:
Round 4 第四轮 linearisation polynomial 计算对应表示为:
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ProverKey {
pub q_range: (Polynomial, Evaluations),
}
Round 3 第三轮 quotient polynomial 计算对应表示为:
Delta([c(X) - 4 * d(X)]) + Delta([b(X) - 4 * c(X)]) + Delta([a(X) - 4 * b(X)]) + Delta([d(Xg) - 4 * a(X)]) * Q_Range(X)
Round 4 第四轮 linearisation polynomial 计算对应表示为:
Delta([c_eval - 4 * d_eval]) + Delta([b_eval - 4 * c_eval]) + Delta([a_eval - 4 * b_eval]) + Delta([d_next_eval - 4 * a_eval]) * Q_Range(X)
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ProverKey {
pub q_l: (Polynomial, Evaluations),
pub q_r: (Polynomial, Evaluations),
pub q_c: (Polynomial, Evaluations),
pub q_fixed_group_add: (Polynomial, Evaluations),
}
Round 3 第三轮 quotient polynomial 计算对应表示为:
Round 4 第四轮 linearisation polynomial 计算对应表示为:
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ProverKey {
pub q_variable_group_add: (Polynomial, Evaluations),
}
Round 3 第三轮 quotient polynomial 计算对应表示为:
Round 4 第四轮 linearisation polynomial 计算对应表示为:
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ProverKey {
pub left_sigma: (Polynomial, Evaluations),
pub right_sigma: (Polynomial, Evaluations),
pub out_sigma: (Polynomial, Evaluations),
pub fourth_sigma: (Polynomial, Evaluations),
pub linear_evaluations: Evaluations, // Evaluations of f(x) = X [XXX: Remove this and benchmark if it makes a considerable difference -- These are just the domain elements]
}
Round 3 第三轮 quotient polynomial 计算对应表示为:
// 分子
// (a(x) + beta * X + gamma) (b(X) + beta * k1 * X + gamma) (c(X) + beta * k2 * X + gamma)(d(X) + beta * k3 * X + gamma)z(X) * alpha
// 分母
// (a(x) + beta* Sigma1(X) + gamma) (b(X) + beta * Sigma2(X) + gamma) (c(X) + beta * Sigma3(X) + gamma)(d(X) + beta * Sigma4(X) + gamma) Z(X.omega) * alpha
// 常量
// L_1(X)[Z(X) - 1]
Round 4 第四轮 linearisation polynomial 计算对应表示为:
// 分子
// (a_eval + beta * z_challenge + gamma)(b_eval + beta * K1 * z_challenge + gamma)(c_eval + beta * K2 * z_challenge + gamma) * alpha* z(X)
// 分母
// -(a_eval + beta * sigma_1 + gamma)(b_eval + beta * sigma_2 + gamma) (c_eval + beta * sigma_3 + gamma) * beta *z_eval * alpha^2 * Sigma_4(X)
// 常量
// Evaluate l_1(z)
/// Verifier verifies a proof
#[allow(missing_debug_implementations)]
pub struct Verifier {
/// VerificationKey which is used to verify a specific PLONK circuit
pub verifier_key: Option,
pub(crate) cs: StandardComposer,
/// Store the messages exchanged during the preprocessing stage
/// This is copied each time, we make a proof, so that we can use the same verifier to
/// Verify multiple proofs from the same circuit. If this is not copied, then the verification procedure will modify
/// the transcript, making it unusable for future proofs.
pub preprocessed_transcript: Transcript,
}
/// PLONK circuit verification key
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct VerifierKey {
/// Circuit size
pub n: usize,
/// VerifierKey for arithmetic gates
pub arithmetic: arithmetic::VerifierKey,
/// VerifierKey for logic gates
pub logic: logic::VerifierKey,
/// VerifierKey for range gates
pub range: range::VerifierKey,
/// VerifierKey for fixed base curve addition gates
pub fixed_base: ecc::scalar_mul::fixed_base::VerifierKey,
/// VerifierKey for variable base curve addition gates
pub variable_base: ecc::curve_addition::VerifierKey,
/// VerifierKey for permutation checks
pub permutation: permutation::VerifierKey,
}
实际实现时,根据circuit中gate类型分类不同,分别实现了不同的VerifierKey
:
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct VerifierKey {
pub q_m: Commitment,
pub q_l: Commitment,
pub q_r: Commitment,
pub q_o: Commitment,
pub q_c: Commitment,
pub q_4: Commitment,
pub q_arith: Commitment,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct VerifierKey {
pub q_c: Commitment,
pub q_logic: Commitment,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct VerifierKey {
pub q_range: Commitment,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct VerifierKey {
pub q_l: Commitment,
pub q_r: Commitment,
pub q_fixed_group_add: Commitment,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct VerifierKey {
pub q_variable_group_add: Commitment,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct VerifierKey {
pub left_sigma: Commitment,
pub right_sigma: Commitment,
pub out_sigma: Commitment,
pub fourth_sigma: Commitment,
}
proof的组成有:【根本目的是使得Verifier可 verify()
通过。】
/// A Proof is a composition of `Commitments` to the witness, permutation,
/// quotient, shifted and opening polynomials as well as the
/// `ProofEvaluations`.
///
/// It's main goal is to have a `verify()` method attached which contains the
/// logic of the operations that the `Verifier` will need to do in order to
/// formally verify the `Proof`.
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Proof {
/// Commitment to the witness polynomial for the left wires.
pub a_comm: Commitment,
/// Commitment to the witness polynomial for the right wires.
pub b_comm: Commitment,
/// Commitment to the witness polynomial for the output wires.
pub c_comm: Commitment,
/// Commitment to the witness polynomial for the fourth wires.
pub d_comm: Commitment,
/// Commitment to the permutation polynomial.
pub z_comm: Commitment,
/// Commitment to the quotient polynomial.
pub t_1_comm: Commitment,
/// Commitment to the quotient polynomial.
pub t_2_comm: Commitment,
/// Commitment to the quotient polynomial.
pub t_3_comm: Commitment,
/// Commitment to the quotient polynomial.
pub t_4_comm: Commitment,
/// Commitment to the opening polynomial.
pub w_z_comm: Commitment,
/// Commitment to the shifted opening polynomial.
pub w_zw_comm: Commitment,
/// Subset of all of the evaluations added to the proof.
pub evaluations: ProofEvaluations,
}
/// Proof Evaluations is a subset of all of the evaluations. These evaluations will be added to the proof
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ProofEvaluations {
// Evaluation of the witness polynomial for the left wire at `z`
pub a_eval: BlsScalar,
// Evaluation of the witness polynomial for the right wire at `z`
pub b_eval: BlsScalar,
// Evaluation of the witness polynomial for the output wire at `z`
pub c_eval: BlsScalar,
// Evaluation of the witness polynomial for the fourth wire at `z`
pub d_eval: BlsScalar,
//
pub a_next_eval: BlsScalar,
//
pub b_next_eval: BlsScalar,
// Evaluation of the witness polynomial for the fourth wire at `z * root of unity`
pub d_next_eval: BlsScalar,
// Evaluation of the arithmetic selector polynomial at `z`
pub q_arith_eval: BlsScalar,
//
pub q_c_eval: BlsScalar,
//
pub q_l_eval: BlsScalar,
//
pub q_r_eval: BlsScalar,
// Evaluation of the left sigma polynomial at `z`
pub left_sigma_eval: BlsScalar,
// Evaluation of the right sigma polynomial at `z`
pub right_sigma_eval: BlsScalar,
// Evaluation of the out sigma polynomial at `z`
pub out_sigma_eval: BlsScalar,
// Evaluation of the linearisation sigma polynomial at `z`
pub lin_poly_eval: BlsScalar,
// (Shifted) Evaluation of the permutation polynomial at `z * root of unity`
pub perm_eval: BlsScalar,
}