PLOOKUP代码解析

1. 引言

Aztec团队Gabizon和Williamson 2020年论文 plookup: A simplified polynomial protocol for lookup tables。

代码实现参见:

  • https://github.com/kevaundray/plookup 【Rust语言实现】
  • https://github.com/neidis/plonk-plookup 【C/C++实现】

本文主要关注的是 https://github.com/kevaundray/plookup 库代码,由于其中scipr/zexe库已重构为arkworks-rs系列库,本人已fork修改为可兼容最新的arkworks-rs系列库,具体见:

  • https://github.com/3for/plookup

代码基本结构为:

  • kzg10 polynomial commitment scheme:具体见src/kzg10.rs目录,详细实现也可参看https://github.com/arkworks-rs/algebra/src/poly/。相比于 Dusk network的Plonk代码解析,arkworks-rs/algebra/src/poly/中增加了blinding属性,引入了random polynomial。
  • multiset 定义及证明:具体见src/multiset目录。
  • lookup table 定义及证明:具体见src/lookup目录。

2. multiset 证明

multiset 定义为:

/// A MultiSet is a variation of a set, where we allow duplicate members
/// This can be emulated in Rust by using vectors
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct MultiSet(pub Vec);

其中:【令 d = n + 1 d=n+1 d=n+1

  • s = ( f , t ) ∈ F 2 n + 1 s=(f,t)\in\mathbb{F}^{2n+1} s=(f,t)F2n+1 sort by t t t 的实现为:【若 f = ( 1 , 2 ) , t = ( 2 , 1 , 2 ) f=(1,2), t=(2,1,2) f=(1,2),t=(2,1,2),则 s = ( 2 , 2 , 1 , 1 , 2 ) s=(2,2,1,1,2) s=(2,2,1,1,2)。】
	/// Performs a element-wise insertion into the second multiset
    /// Example: f {1,2,3,1} t : {3,1,2,3} => 结果为$(3,3,1,1,1,2,2,3)$
    /// We now take each element from f and find the element in `t` then insert the element from `f` into right next to it's duplicate
    /// We are assuming that `f` is contained in `t`
    pub fn concatenate_and_sort(&self, t: &MultiSet) -> MultiSet {
        assert!(self.is_subset_of(t));
        let mut result = t.clone();

        for element in self.0.iter() {
            let index = result.position(element);
            result.0.insert(index, *element);
        }

        result
    }
  • s s s 平分,以便于多项式 h 1 h_1 h1表示前半部分,多项式 h 2 h_2 h2表示后半部分。由于 s s s的元素个数 2 n + 1 2n+1 2n+1为奇数,因此第 n + 1 n+1 n+1个元素为 h 1 h_1 h1的最后一个元素,为 h 2 h_2 h2的第一个元素。
	/// Splits a multiset into halves as specified by the paper
    /// If s = [1,2,3,4,5,6,7], we can deduce n using |s| = 2 * n + 1 = 7
    /// n is therefore 3
    /// We split s into two MultiSets of size n+1 each
    /// s_0 = [1,2,3,4] ,|s_0| = n+1 = 4
    /// s_1 = [4,5,6,7] , |s_1| = n+1 = 4
    /// Notice that the last element of the first half equals the first element in the second half
    /// This is specified in the paper
    pub fn halve(&self) -> (MultiSet, MultiSet) {
        let length = self.0.len();

        let first_half = MultiSet::from_slice(&self.0[0..=length / 2]);
        let second_half = MultiSet::from_slice(&self.0[length / 2..]);

        (first_half, second_half)
    }
  • 将FFT点值表示法 转换为 系数表示法:【将multiset中的元素看成是evaluations,转换为多项式系数表示。】
	/// Treats each element in the multiset as evaluation points
    /// Computes IFFT of the set of evaluation points
    /// and returns the coefficients as a Polynomial data structure
    pub fn to_polynomial(&self, domain: &GeneralEvaluationDomain) -> Polynomial {
        Polynomial::from_coefficients_vec(domain.ifft(&self.0))
    }
  • Lagrange base 表示为:对 i ∈ [ n + 1 ] i\in[n+1] i[n+1],有 L i ( g i ) = 1 L_i(\mathbf{g}^i)=1 Li(gi)=1;对任意的 j ≠ i j\neq i j=i,有 L i ( g j ) = 0 L_i(\mathbf{g}^j)=0 Li(gj)=0,对应代码表示为:
// Computes the n'th lagrange poly for a particular domain
// Easiest way is to compute the evaluation points, which will be zero at every position except for n
// Then IFFT to get the coefficient form
// Note: n=0 is the first lagrange polynomial and n = domain.size() -1 is the last lagrange polynomial
pub fn compute_n_lagrange_poly(domain: &GeneralEvaluationDomain, n: usize) -> DensePoly {
    assert!(n <= domain.size() - 1);
    let mut evaluations = compute_n_lagrange_evaluations(domain.size(), n);
    domain.ifft_in_place(&mut evaluations);
    DensePoly::from_coefficients_vec(evaluations)
}
fn compute_n_lagrange_evaluations(domain_size: usize, n: usize) -> Vec {
    let mut lagrange_evaluations = vec![Fr::zero(); domain_size];
    lagrange_evaluations[n] = Fr::one();
    lagrange_evaluations
}
  • “单个多项式 f ∈ F < n [ X ] f\in\mathbb{F}_{fF<n[X]的lookup table 关系证明” 中的
    PLOOKUP代码解析_第1张图片
    Prover 生成的 proof 对应为:
    【相应的测试用例为test_manually_compute_z,做了如下说明:
 		// This test manually computes the values of the accumulator Z(x)
        // Notice that the test will fail if:
        // - You add a value to 'f' that is not in 't'
        // - 't' is unordered
        // Now notice that the test will pass if:
        // - (1) len(t) != len(f) + 1
        // - (2) len(t) is not a power of two
        // The reason why the tests pass for these values is :
        // (1) We made this restriction, so that it would be easier to check that h_1 and h_2 are continuous. This should not affect the outcome of Z(X) if h_1 and h_2 when merged
        // indeed form 's'
        // (2) This is a restriction that is placed upon the protocol due to using roots of unity. It would not affect the computation of Z(X)

/// Computes the values for Z(X)
pub fn compute_accumulator_values(
    f: &MultiSet,
    t: &MultiSet,
    h_1: &MultiSet,
    h_2: &MultiSet,
    beta: Fr,
    gamma: Fr,
) -> Vec {
    let n = f.len();

    // F(beta, gamma)
    let mut numerator: Vec = Vec::with_capacity(n + 1);
    // G(beta, gamma)
    let mut denominator: Vec = Vec::with_capacity(n + 1);

    // Z evaluated at the first root of unity is 1
    numerator.push(Fr::one());
    denominator.push(Fr::one());

    let beta_one = Fr::one() + beta;

    // Compute values for Z(X)
    for i in 0..n {
        let f_i = beta_one * compute_f_i(i, f, t, beta, gamma);
        let g_i = compute_g_i(i, h_1, h_2, beta, gamma);

        let last_numerator = *numerator.last().unwrap();
        let last_denominator = *denominator.last().unwrap();

        numerator.push(f_i * last_numerator);
        denominator.push(g_i * last_denominator);
    }

    // Check that Z(g^{n+1}) = 1
    let last_numerator = *numerator.last().unwrap();
    let last_denominator = *denominator.last().unwrap();

    assert_eq!(last_numerator / last_denominator, Fr::one());

    // Combine numerator and denominator
    assert_eq!(numerator.len(), denominator.len());
    assert_eq!(numerator.len(), n + 1);
    let mut evaluations = Vec::with_capacity(numerator.len());
    for (n, d) in numerator.into_iter().zip(denominator) {
        evaluations.push(n / d)
    }
    evaluations
}
  • “单个多项式 f ∈ F < n [ X ] f\in\mathbb{F}_{fF<n[X]的lookup table 关系证明” 中的
    PLOOKUP代码解析_第2张图片
    Verifier的验证操作描述如下:【将其中的 6.b)验证称为 term check。】
// The quotient polynomial will encode the four checks for the multiset equality argument
// These checks are:
// 1) Z(X) evaluated at the first root of unity is 1
// 2) Z(X) is correct accumulated. z_(Xg) * g(X) = (1+beta)^n * z(X) * f(X)
// 3) The last element of h_1(x) is equal to the first element of h_2(x)
// 4) Z(x) evaluated at the last root of unity is 1
//
// We can denote check 1 and check 4 as point checks because they are checking the evaluation of Z(x) at a specific point
// We can denote check 3 as an interval check because it checks whether h_1 and h_2 combined form 's' without any gaps. See paper for more details on 's'
// We can denote check 2 as the term check
//
// Notice that the term check equation will determine the degree of the quotient polynomial
// We can compute it by adding the degrees of Z(x), f(x) and t(x).
// deg(Z(x)) = n because it has n + 1 elements
// deg(f(x)) = n  although it has n elements, we must zero pad to ensure that f(x) evaluated on the n+1'th element is zero
// deg(t(x)) = n because we define it to have n + 1 elements.
// Summing the degrees gives us n + n + n = 3n
// However, similar to [GWC19](PLONK) we must divide by the vanishing polynomial
// So the degree of the quotient polynomial Q(x) is 3n - n = 2n
// Significance: Adding this protocol into PLONK will not "blow up" the degree of the quotient polynomial
// Where "blow up" denotes increasing the overall degree past 4n for standard plonk
pub fn compute(
    domain: &GeneralEvaluationDomain,
    z_poly: &DensePoly,
    f_poly: &DensePoly,
    t_poly: &DensePoly,
    h_1_poly: &DensePoly,
    h_2_poly: &DensePoly,
    beta: Fr,
    gamma: Fr,
) -> (DensePoly, DensePoly) {
    // 1. Compute Point check polynomial
    let point_check = compute_point_checks(z_poly, domain);
    //2. Compute interval check polynomial
    let interval_check = compute_interval_check(h_1_poly, h_2_poly, domain);
    //3. Compute term check polynomial
    let term_check = compute_term_check(
        domain, z_poly, f_poly, t_poly, h_1_poly, h_2_poly, beta, gamma,
    );
    // Compute quotient polynomial
    let sum = &(&interval_check + &point_check) + &term_check;

    sum.divide_by_vanishing_poly(*domain).unwrap()
}

L n + 1 ( x ) ( h 1 ( x ) − h 2 ( g ⋅ x ) ) = 0 L_{n+1}(\mathbf{x})(h_1(\mathbf{x})-h_2(\mathbf{g}\cdot\mathbf{x}))=0 Ln+1(x)(h1(x)h2(gx))=0【因为之前有约定 h 1 ( 1 ) = h 2 ( g ) = s n + 1 h_1(1)=h_2(\mathbf{g})=s_{n+1} h1(1)=h2(g)=sn+1】表示为:【由domain_n,提升至了 domain_2n 域。】

	// Compute [L_n(x)](h_1(x) - h_2(x * g))
    let i_evals: Vec<_> = (0..domain_2n.size())
        .into_iter()
        .map(|i| {
            let ln_i = ln_2n_evals[i];
            let h_1_i = h_1_evals[i];
            let h_2_i_next = h_2_evals[i + 2];
            ln_i * (h_1_i - h_2_i_next)
        })
        .collect();

( x − g n + 1 ) Z ( x ) ( 1 + β ) ⋅ ( γ + f ( x ) ) ( γ ( 1 + β ) + t ( x ) + β t ( g ⋅ x ) ) = ( x − g n + 1 ) Z ( g ⋅ x ) ( γ ( 1 + β ) + h 2 ( x ) + β h 2 ( g ⋅ x ) ) ( γ ( 1 + β ) + h 1 ( x ) + β h 1 ( g ⋅ x ) ) (\mathbf{x}-\mathbf{g}^{n+1})Z(\mathbf{x})(1+\beta)\cdot(\gamma+f(\mathbf{x}))(\gamma(1+\beta)+t(\mathbf{x})+\beta t(\mathbf{g}\cdot \mathbf{x}))=(\mathbf{x}-\mathbf{g}^{n+1})Z(\mathbf{g}\cdot\mathbf{x})(\gamma(1+\beta)+h_2(\mathbf{x})+\beta h_2(\mathbf{g}\cdot\mathbf{x}))(\gamma(1+\beta)+h_1(\mathbf{x})+\beta h_1(\mathbf{g}\cdot\mathbf{x})) (xgn+1)Z(x)(1+β)(γ+f(x))(γ(1+β)+t(x)+βt(gx))=(xgn+1)Z(gx)(γ(1+β)+h2(x)+βh2(gx))(γ(1+β)+h1(x)+βh1(gx)) 表示为:【由domain_n,提升至了domain_4n域。】

pub fn compute_term_check(
    domain: &GeneralEvaluationDomain,
    z_poly: &DensePoly,
    f_poly: &DensePoly,
    t_poly: &DensePoly,
    h_1_poly: &DensePoly,
    h_2_poly: &DensePoly,
    beta: Fr,
    gamma: Fr,
) -> DensePoly {
    // The equation for this is quite big. Similar to PLONK, we can split the point check into two.
    // The first part will compute the grand product Z(X) term
    // The second part will compute the grand product Z(Xg) term

    // First Part
    let part_a = compute_term_check_a(domain, z_poly, f_poly, t_poly, beta, gamma);
    // Second part
    let part_b = compute_term_check_b(domain, z_poly, h_1_poly, h_2_poly, beta, gamma);

    &part_a - &part_b
}
  • multiset equality 证明:
// Evaluations store the evaluations of different polynomial.
// `t` denotes that the polynomial was evaluated at t(z) for some random evaluation challenge `z`
// `t_omega` denotes the polynomial was evaluated at t(z * omega) where omega is the group generator
// In the FFT context, the normal terminology is that t(z*omega) means to evaluate a polynomial at the next root of unity from `z`.
pub struct Evaluations {
   pub f: Fr,
   pub t: Fr,
   pub t_omega: Fr,
   pub h_1: Fr,
   pub h_1_omega: Fr,
   pub h_2: Fr,
   pub h_2_omega: Fr,
   pub z: Fr,
   pub z_omega: Fr,
}
// Commitments of different polynomials
pub struct Commitments {
   pub f: Commitment,
   pub q: Commitment,
   pub h_1: Commitment,
   pub h_2: Commitment,
   pub z: Commitment,
}

// In the best case, this protocol requires 3 extra G1 elements (Commitment)
// These are: h_1_commit,h_2_commit, f_commit
//
// The commitment to the accumulator and the quotient polynomial would ideally be joined into the existing ones in PLONK
//
// We would require 7 extra Scalar elements (Evaluations)
// These are: h_1_eval, h_1_omega_eval, h_2_eval, h_2_omega_eval, f_eval, t_eval, t_omega_eval
//
// We would ideally be able to combine the accumulator, Z(X) with the permutation accumulator in plonk, and the quotient polynomial with the quotient polynomial in PLONK
// Which would save us 2 evaluation points: z_eval and z_omega_eval
// q_eval which is the quotient evaluation is usually created from the prover messages
//
// Lastly, the Witness commitments can also be batched with the PLONK opening Proof.
pub struct EqualityProof {
   pub aggregate_witness_comm: Commitment,
   pub shifted_aggregate_witness_comm: Commitment,

   pub evaluations: Evaluations,

   pub commitments: Commitments,
}

3. circuit lookup table证明

src\lookup目录下,简单实现了2-fan-in 1-out 的加法门和异或门证明。

  • 其中BIT_RANGE=16,通过preprocess生成的lookuptable为 ( 0 & 0 , 0 & 1 , ⋯   , 15 & 15 ) (0\&0,0\&1,\cdots,15\&15) (0&0,0&1,,15&15) ( 0 ∧ 0 , 0 ∧ 1 , ⋯   , 15 ∧ 15 ) (0\wedge0,0\wedge1,\cdots,15\wedge15) (00,01,,1515)
/// Construct a Generic lookup table over a bi-variate function
pub struct Generic(HashMap<(Fr, Fr), Fr>); //以两个输入组合为Key,以输出结果为value
// 其中的输入 索引表 $t_1,t_2$, 输出索引表$t_3$均为public info。
pub struct PreProcessedTable {
    pub n: usize,
    pub t_1: (MultiSet, Commitment, DensePoly),
    pub t_2: (MultiSet, Commitment, DensePoly),
    pub t_3: (MultiSet, Commitment, DensePoly),
}
  • 扩展为多个多项式 f 1 , ⋯   , f w ∈ F < n [ X ] f_1,\cdots,f_w\in\mathbb{F}_{f1,,fwF<n[X] 一一对应 多个 lookup tables 关系证明 相应实现为:【借助challenge来aggregate】
        // Aggregates the table and witness values into one multiset
        // and pads the witness to be the correct size
        //
        // Aggregate our table values into one multiset
        let merged_table = MultiSet::aggregate(
            vec![
                &preprocessed_table.t_1.0,
                &preprocessed_table.t_2.0,
                &preprocessed_table.t_3.0,
            ],
            alpha,
        );

        // Aggregate witness values into one multiset
        let mut merged_witness = MultiSet::aggregate(vec![f_1, f_2, f_3], alpha);

        // Pad merged Witness to be one less than `n`
        assert!(merged_witness.len() < preprocessed_table.n);
        let pad_by = preprocessed_table.n - 1 - merged_witness.len();
        merged_witness.extend(pad_by, merged_witness.last());

  • witness 为:
pub struct LookUp {
    table: T,
    // This is the set of values which we want to prove is a subset of the
    // table values. This may or may not be equal to the whole witness.
    left_wires: MultiSet,
    right_wires: MultiSet,
    output_wires: MultiSet,
}
  • proof组成为:
pub struct LookUpProof {
    pub multiset_equality_proof: EqualityProof,
}
  • 具体测试用例参见src/lookup/lookup.rs中的test_proof

你可能感兴趣的:(零知识证明)