Lookup Argument (Logup) and Code Analysis
Share on
  • 1. Introduction
  • 2. Logup Protocol
    • 2.1 CQ Protocol
    • 2.2 Logup Protocol
  • 3. Code Explanation
    • 3.1 Lookup Struct Definition
    • 3.2 Lookup_helper_columns Function
    • 3.3 eval_packed_Lookups_generic Function
    • 3.4 eval_ext_Lookups_circuit Function

1. Introduction

When representing computations as circuits, there are often complex operations such as bitwise XOR and bitwise AND. For example, performing a bitwise XOR operation on two 32-bit bitstrings usually requires hundreds of addition and multiplication gates, significantly increasing circuit size. Introducing the Lookup argument protocol effectively reduces circuit complexity.

To prove that two 32-bit bitstrings and their corresponding outputs satisfy a bitwise XOR relationship, one can encode the inputs and outputs into a lookup table of size \(2^{32}\), then demonstrate that the inputs and outputs align with a specific row in the table. To reduce table size, smaller tables such as \(2^{16}\) or \(2^{8}\) can be used, along with constraints between bits to balance table size and proof speed.

The Lookup argument is used to verify whether elements of one set appear in another set without revealing additional information. In circuit design, Lookup arguments are frequently applied to verify complex constraints, especially in cases involving non-gate or non-scalar multiplication operations. They are mainly used in proving systems like Plonkish and Halo2. This method significantly reduces circuit constraints and enhances proof efficiency. The specific construction steps are as follows:

  • Constructing the Lookup Table: Build a lookup table T, which is a set represented as a polynomial containing all valid values to be verified.
  • Constructing Lookup Items: The prover maps certain values in the circuit to lookup items (e.g., via hashing) and uses algorithms to prove that these items are in T.
  • Polynomial Mapping: The prover converts both the lookup table T and lookup items into polynomials and verifies their consistency using polynomial equivalence and interpolation. Permutation or interpolation polynomials are often employed to ensure the values in T and lookup items match at specific points.
  • Consistency Check: The verifier checks the consistency of the lookup table and items by validating the roots of their polynomials, typically introducing a random scalar to perform the verification.

2. Logup Protocol

2.1 CQ Protocol

The Logup protocol originates from the CQ protocol, which uses logarithms and derivatives from higher mathematics to reduce set membership proofs to simpler problems. Its workflow, as shown in Figure 1, involves the following steps:

1. Construct Lookup Items and Table:

  • Build a set of lookup items (Values), where each item has the form \((\beta + f_i)\), with \(f_i\) being a specific lookup value.
  • Construct a lookup table T, where each entry has the form \((\beta + t_i)\), with \(t_i\) being a specific table value.

2. Formulate LHS and RHS Expressions:

  • Left-Hand Side (LHS): Multiply all lookup items to form the expression\(\prod_{i=1}^{n}(\beta+f_{i})\).
  • Right-Hand Side (RHS): Multiply table entries based on their frequencies, forming \(\prod_{i=1}^{N}(\beta+t_{i})^{m_{i}}\), where \(m_i\) is the frequency of each table value.

3. Verify Logarithmic Equality: Convert the multiplicative forms into logarithmic sums:

\[\sum_{j=1}^{n}log(\beta+f_{j})=\sum_{i=1}^{N}m_{i}\cdot log(\beta+t_{i})\]

4. Check Fractional Equality: Validate the equality:

\[\sum_{j=1}^{n}\frac{1}{\beta+f_{j}}=\sum_{i=1}^{N}\frac{m_{i}}{\beta+r_{i}}\]

If the equation holds, the lookup values are confirmed to be in the table.

Figure 1: CQ Lookup Argument

2.2 Logup Protocol

The Logup protocol extends the CQ protocol from univariate polynomials to multivariate polynomials, adapting to zkSNARK algorithms with hypercube implementations. The zkSNARK prover uses the univariate version.

The core of the Logup protocol’s Lookup argument relies on the following mathematical theorem:

Lemma (Set Inclusion): Let \(F\) be a field with characteristic \(p > N\), and suppose \(\{a_i\}_{i=1}^{N}\) and \(\{b_i\}_{i=1}^{N}\) are arbitrary sequences of elements from F. The set\(\{a_i\} \subseteq \{b_i\}\) (with duplicate values removed) if and only if there exists a sequence \(\{m_i\}_{i=1}^{N}\) from \(F_q \subseteq F\) such that:

\[\sum_{i=1}^{N}\frac{1}{X+a_{i}}=\sum_{i=1}^{N}\frac{m_{i}}{X+b_{i}}\]

in the function field \(F(X)\). Furthermore, \(\{a_i\} = \{b_i\}\) (as sets) if and only if \(m_i \neq 0\) for all \(t\).

The Logup protocol utilizes the sum-check protocol for verifying Lookup equations, including the following components:

  • Auxiliary Column \(h_i(x)\): Constructed from the reciprocals of lookup items in batches.
  • Lookup Table Polynomial \(g(x)\): Represents table entries as reciprocals.
  • Z Polynomial Recursion: Ensures consistency between lookup items and the table.

Logup Workflow:

1. Compute Auxiliary Columns: Define auxiliary columns \(h_i(x)\) for batches of lookup items \(\{f_i(x)\}\) using a challenge value x:

\[h_i(x) = \frac{1}{x+f_{2i}} + \frac{1}{x+f_{2i+1}}\]

2. Construct Lookup Table Polynomial: For table entries \(t(x)\), define:

\[g(x) = \frac{1}{x+t(x)}\]

3. Define\(z(x)\) Polynomial Recursion: The definition of the polynomial \(z(x)\) is used to verify the relationship between lookup items and the lookup table. Its recursive relationship is as follows:

\[Z(gx) = Z(x) + \sum h_i(x) - m(x) \cdot g(x)\]

Where:

  • \(z(x)\) is the value of the Z polynomial at the current step. 
  • \(z(gx)\) is the value of the Z polynomial at the next step. 
  • \(\sum h_i(x)\) represents the sum of all auxiliary columns. 
  • \(m(x)\) is the frequency column, representing the occurrence frequency of the lookup item in the lookup table.

The recursive relationship of the \(z(x)\) polynomial ensures that the sum of the auxiliary columns across the entire lookup table range is zero. This property can help verify whether a lookup item is present in the lookup table.

The initial condition of the Z polynomial is:

\[Z(1) = 0\]

Finally, the Lookup argument computation is completed using the PIOP+PCS protocol.

3. Code Explanation

The code corresponding to the Logup argument in the zkm prover implements the Lookup verification of the Logup protocol through the following steps:

  1. Define the Lookup Struct: This includes columns, the Lookup table, and frequency columns.
  2. Generate Auxiliary Columns \(h_i(x)\) and 1/(table + challenge), used for recursive validation.
  3. Construct the Recursive Validation Polynomial \(z(x)\) to ensure consistency of column value distributions.
  4. Verify the Correctness of Auxiliary Columns and \(z(x)\) using the eval_packed_Lookups_generic function to perform general packed Lookup checks.

Below is an explanation of the code structure:

  1. Lookup Struct: Defines the columns to be verified, the Lookup table, frequency columns, and other filtering columns.
  2. Lookup_helper_columns Function: Generates the auxiliary columns \(h_i(x)\) and \(g(x)\), and computes the recursive verification polynomial \(z(x)\).
  3. eval_packed_Lookups_generic Function: Performs general packed Lookup checks for the Logup protocol to verify the correctness of auxiliary columns \(h_i(x)\) and \(z(x)\).
  4. eval_ext_Lookups_circuit Function: Implements Lookup verification at the circuit level to support constraint construction in recursive proofs.

Now, let’s go over each method corresponding to the Logup protocol.

3.1 Lookup Struct Definition

The Lookup struct defines the basic information to be verified, including the columns, Lookup table, and frequency columns:

pub struct Lookup<F: Field> {
    pub(crate) columns: Vec<Column<F>>,          // Set of columns to be verified, representing the f_i(x) polynomial
    pub(crate) table_column: Column<F>,          // Lookup table column, representing the t(x) polynomial
    pub(crate) frequencies_column: Column<F>,    // Frequency column for each column, representing the m(x) polynomial
    pub filter_columns: Vec<Option<Filter<F>>>,  // Filtering columns, optionally verifying certain rows
}

  • columns: Contains the columns to be verified, ensuring their values are included in table_column via the Lookup operation.
  • table_column: Represents the Lookup table column \(t(x)\), used as a baseline for verification.
  • frequencies_column: Represents the frequency of each column in the Lookup table, used for the calculation of \(Z(x)\).
  • filter_columns: Selectively filters certain rows for specific verification.

3.2 Lookup_helper_columns Function

The Lookup_helper_columns function is the core of the code, generating auxiliary columns \(h_i(x)\) and \(g(x)\), and computing the recursive verification polynomial \(Z(x)\).

Key Flow and Code Analysis

1. Initialization and Degree Constraint Check:


assert_eq!(constraint_degree, 3, "TODO: Allow other constraint degrees.");

The constraint degree constraint_degree is fixed at 3 to ensure the formula calculations conform to the predefined structure. Currently, the code supports only this degree.

2. Get the Number of Auxiliary Columns:


let num_helper_columns = Lookup.num_helper_columns(constraint_degree);

num_helper_columns calculates the number of auxiliary columns required, including \(h_i(x)\), 1 / (table + challenge), and \(Z(x)\).

3. Generate h_i(x) Auxiliary Columns:


let mut helper_columns = get_helper_cols(
    trace_poly_values,
    trace_poly_values[0].len(),
    &columns_filters,
    grand_challenge,
    constraint_degree,
);

\(h_i(x)\) Calculation: The get_helper_cols function divides the columns into batches, with each batch containing constraint_degree - 1 elements. For each batch, an auxiliary column \(h_i(x)\) is generated:

\[h_i(x) = \frac{1}{x+f_{2i}} + \frac{1}{x+f_{2(i+1)}}\]

Purpose: \(h_i(x)\) helps verify, during recursion, whether column values are present in table_column.

4. Compute the Inverse of the Table Column 1/(table + challenge):

let mut table = Lookup.table_column.eval_all_rows(trace_poly_values);
 for x in table.iter_mut() {
     *x = challenge + *x;
 }
 let table_inverse: Vec<F> = F::batch_multiplicative_inverse(&table);

Table Column Inverse  \(g(x)\): After adding the challenge value challenge to each \(t(x)\), the reciprocal is taken, forming the auxiliary column

\[g(x) = \frac{1}{x+t(x)}\]

Purpose: This auxiliary column ensures that  \(t(x)\) participates in verification through its reciprocal.

5. Compute the Recursive Polynomial \(Z(x)\):

let frequencies = &Lookup.frequencies_column.eval_all_rows(trace_poly_values);
 let mut z = Vec::with_capacity(frequencies.len());
 z.push(F::ZERO);
 for i in 0..frequencies.len() - 1 {
     let x = helper_columns[..num_helper_columns - 1]
         .iter()
         .map(|col| col.values[i])
         .sum::<F>()
         - frequencies[i] * table_inverse[i];
     z.push(z[i] + x);
 }
 helper_columns.push(z.into());

\(Z(x)\) Recursion Formula: The recursion formula for \(Z(x)\) is:

\[Z(gx) = Z(x) + \sum h_i(x) - m(x) \cdot g(x)\]

Where frequencies represent  \(m(x)\), and table_inverse represents  \(g(x)\).

Purpose: Through recursion, this formula verifies the consistency of the distributions of \(f_i(x)\) and \(t(x)\), ensuring that all columns satisfy the Lookup constraints.

3.3 eval_packed_Lookups_generic Function

The eval_packed_Lookups_generic function checks the correctness of the auxiliary columns and \(Z(x)\) using the recursive formula to validate whether column values are included in the table_column.

1. Verify the Correctness of Auxiliary Columns h_i(x):

eval_helper_columns(
    &Lookup.filter_columns,
    &Lookup_columns,
    local_values,
    next_values,
    &Lookup_vars.local_values[start..start + num_helper_columns - 1],
    degree,
    &grand_challenge,
    yield_constr,
);

Here, the eval_helper_columns function is called to check each row of h_i(x) and ensure that the auxiliary columns are correctly generated.

2. Verify the Recursion of \(Z(x)\):

let z = Lookup_vars.local_values[start + num_helper_columns - 1];
let next_z = Lookup_vars.next_values[start + num_helper_columns - 1];
let table_with_challenge = Lookup.table_column.eval(local_values) + challenge;
let y = Lookup_vars.local_values[start..start + num_helper_columns - 1]
    .iter()
    .fold(P::ZEROS, |acc, x| acc + *x)
    * table_with_challenge
    - Lookup.frequencies_column.eval(local_values);
yield_constr.constraint_first_row(z);
yield_constr.constraint((next_z - z) * table_with_challenge - y);

Check the Recursion of  \(Z(x)\): Ensures that the initial value of \(Z(x)\) is 0 and that every row satisfies:

\[Z(gx) = Z(x) + \sum h_i(x) - m(x) \cdot g(x)\]

3.4 eval_ext_Lookups_circuit Function

This function builds recursive constraints at the circuit level, ensuring that the Lookup process remains consistent within the recursive proof circuit. It verifies the auxiliary columns and \(Z(x)\) to support the recursive proof.

Subscribe to the ZKM Blog to stay updated with the latest research by ZKM. If you have any questions about the article, you can contact the ZKM team on discord: discord.com/zkm

More articles
zkMIPS: A High-Level Specification
Here, we announce the publication of the latest paper from ZKM Research: ‘zkMIPS: A High-Level Specification’. This substantial update of the previous version corrects the discrepancies between the original documentation and the present state of the zkMIPS codebase, providing a more accurate foundation of information to aid the ZKM developer community with their contributions.
Hello World February Newsletter
January was a month of intense preparation — not only are we on the verge of our long-awaited testnet launch, but Eth Denver is also on the horizon and our ‘House of ZK’ hacker house is lining up to be something very special.
Lookup Argument (Logup) and Code Analysis
  • 1. Introduction
  • 2. Logup Protocol
    • 2.1 CQ Protocol
    • 2.2 Logup Protocol
  • 3. Code Explanation
    • 3.1 Lookup Struct Definition
    • 3.2 Lookup_helper_columns Function
    • 3.3 eval_packed_Lookups_generic Function
    • 3.4 eval_ext_Lookups_circuit Function

1. Introduction

When representing computations as circuits, there are often complex operations such as bitwise XOR and bitwise AND. For example, performing a bitwise XOR operation on two 32-bit bitstrings usually requires hundreds of addition and multiplication gates, significantly increasing circuit size. Introducing the Lookup argument protocol effectively reduces circuit complexity.

To prove that two 32-bit bitstrings and their corresponding outputs satisfy a bitwise XOR relationship, one can encode the inputs and outputs into a lookup table of size \(2^{32}\), then demonstrate that the inputs and outputs align with a specific row in the table. To reduce table size, smaller tables such as \(2^{16}\) or \(2^{8}\) can be used, along with constraints between bits to balance table size and proof speed.

The Lookup argument is used to verify whether elements of one set appear in another set without revealing additional information. In circuit design, Lookup arguments are frequently applied to verify complex constraints, especially in cases involving non-gate or non-scalar multiplication operations. They are mainly used in proving systems like Plonkish and Halo2. This method significantly reduces circuit constraints and enhances proof efficiency. The specific construction steps are as follows:

  • Constructing the Lookup Table: Build a lookup table T, which is a set represented as a polynomial containing all valid values to be verified.
  • Constructing Lookup Items: The prover maps certain values in the circuit to lookup items (e.g., via hashing) and uses algorithms to prove that these items are in T.
  • Polynomial Mapping: The prover converts both the lookup table T and lookup items into polynomials and verifies their consistency using polynomial equivalence and interpolation. Permutation or interpolation polynomials are often employed to ensure the values in T and lookup items match at specific points.
  • Consistency Check: The verifier checks the consistency of the lookup table and items by validating the roots of their polynomials, typically introducing a random scalar to perform the verification.

2. Logup Protocol

2.1 CQ Protocol

The Logup protocol originates from the CQ protocol, which uses logarithms and derivatives from higher mathematics to reduce set membership proofs to simpler problems. Its workflow, as shown in Figure 1, involves the following steps:

1. Construct Lookup Items and Table:

  • Build a set of lookup items (Values), where each item has the form \((\beta + f_i)\), with \(f_i\) being a specific lookup value.
  • Construct a lookup table T, where each entry has the form \((\beta + t_i)\), with \(t_i\) being a specific table value.

2. Formulate LHS and RHS Expressions:

  • Left-Hand Side (LHS): Multiply all lookup items to form the expression\(\prod_{i=1}^{n}(\beta+f_{i})\).
  • Right-Hand Side (RHS): Multiply table entries based on their frequencies, forming \(\prod_{i=1}^{N}(\beta+t_{i})^{m_{i}}\), where \(m_i\) is the frequency of each table value.

3. Verify Logarithmic Equality: Convert the multiplicative forms into logarithmic sums:

\[\sum_{j=1}^{n}log(\beta+f_{j})=\sum_{i=1}^{N}m_{i}\cdot log(\beta+t_{i})\]

4. Check Fractional Equality: Validate the equality:

\[\sum_{j=1}^{n}\frac{1}{\beta+f_{j}}=\sum_{i=1}^{N}\frac{m_{i}}{\beta+r_{i}}\]

If the equation holds, the lookup values are confirmed to be in the table.

Figure 1: CQ Lookup Argument

2.2 Logup Protocol

The Logup protocol extends the CQ protocol from univariate polynomials to multivariate polynomials, adapting to zkSNARK algorithms with hypercube implementations. The zkSNARK prover uses the univariate version.

The core of the Logup protocol’s Lookup argument relies on the following mathematical theorem:

Lemma (Set Inclusion): Let \(F\) be a field with characteristic \(p > N\), and suppose \(\{a_i\}_{i=1}^{N}\) and \(\{b_i\}_{i=1}^{N}\) are arbitrary sequences of elements from F. The set\(\{a_i\} \subseteq \{b_i\}\) (with duplicate values removed) if and only if there exists a sequence \(\{m_i\}_{i=1}^{N}\) from \(F_q \subseteq F\) such that:

\[\sum_{i=1}^{N}\frac{1}{X+a_{i}}=\sum_{i=1}^{N}\frac{m_{i}}{X+b_{i}}\]

in the function field \(F(X)\). Furthermore, \(\{a_i\} = \{b_i\}\) (as sets) if and only if \(m_i \neq 0\) for all \(t\).

The Logup protocol utilizes the sum-check protocol for verifying Lookup equations, including the following components:

  • Auxiliary Column \(h_i(x)\): Constructed from the reciprocals of lookup items in batches.
  • Lookup Table Polynomial \(g(x)\): Represents table entries as reciprocals.
  • Z Polynomial Recursion: Ensures consistency between lookup items and the table.

Logup Workflow:

1. Compute Auxiliary Columns: Define auxiliary columns \(h_i(x)\) for batches of lookup items \(\{f_i(x)\}\) using a challenge value x:

\[h_i(x) = \frac{1}{x+f_{2i}} + \frac{1}{x+f_{2i+1}}\]

2. Construct Lookup Table Polynomial: For table entries \(t(x)\), define:

\[g(x) = \frac{1}{x+t(x)}\]

3. Define\(z(x)\) Polynomial Recursion: The definition of the polynomial \(z(x)\) is used to verify the relationship between lookup items and the lookup table. Its recursive relationship is as follows:

\[Z(gx) = Z(x) + \sum h_i(x) - m(x) \cdot g(x)\]

Where:

  • \(z(x)\) is the value of the Z polynomial at the current step. 
  • \(z(gx)\) is the value of the Z polynomial at the next step. 
  • \(\sum h_i(x)\) represents the sum of all auxiliary columns. 
  • \(m(x)\) is the frequency column, representing the occurrence frequency of the lookup item in the lookup table.

The recursive relationship of the \(z(x)\) polynomial ensures that the sum of the auxiliary columns across the entire lookup table range is zero. This property can help verify whether a lookup item is present in the lookup table.

The initial condition of the Z polynomial is:

\[Z(1) = 0\]

Finally, the Lookup argument computation is completed using the PIOP+PCS protocol.

3. Code Explanation

The code corresponding to the Logup argument in the zkm prover implements the Lookup verification of the Logup protocol through the following steps:

  1. Define the Lookup Struct: This includes columns, the Lookup table, and frequency columns.
  2. Generate Auxiliary Columns \(h_i(x)\) and 1/(table + challenge), used for recursive validation.
  3. Construct the Recursive Validation Polynomial \(z(x)\) to ensure consistency of column value distributions.
  4. Verify the Correctness of Auxiliary Columns and \(z(x)\) using the eval_packed_Lookups_generic function to perform general packed Lookup checks.

Below is an explanation of the code structure:

  1. Lookup Struct: Defines the columns to be verified, the Lookup table, frequency columns, and other filtering columns.
  2. Lookup_helper_columns Function: Generates the auxiliary columns \(h_i(x)\) and \(g(x)\), and computes the recursive verification polynomial \(z(x)\).
  3. eval_packed_Lookups_generic Function: Performs general packed Lookup checks for the Logup protocol to verify the correctness of auxiliary columns \(h_i(x)\) and \(z(x)\).
  4. eval_ext_Lookups_circuit Function: Implements Lookup verification at the circuit level to support constraint construction in recursive proofs.

Now, let’s go over each method corresponding to the Logup protocol.

3.1 Lookup Struct Definition

The Lookup struct defines the basic information to be verified, including the columns, Lookup table, and frequency columns:

pub struct Lookup<F: Field> {
    pub(crate) columns: Vec<Column<F>>,          // Set of columns to be verified, representing the f_i(x) polynomial
    pub(crate) table_column: Column<F>,          // Lookup table column, representing the t(x) polynomial
    pub(crate) frequencies_column: Column<F>,    // Frequency column for each column, representing the m(x) polynomial
    pub filter_columns: Vec<Option<Filter<F>>>,  // Filtering columns, optionally verifying certain rows
}

  • columns: Contains the columns to be verified, ensuring their values are included in table_column via the Lookup operation.
  • table_column: Represents the Lookup table column \(t(x)\), used as a baseline for verification.
  • frequencies_column: Represents the frequency of each column in the Lookup table, used for the calculation of \(Z(x)\).
  • filter_columns: Selectively filters certain rows for specific verification.

3.2 Lookup_helper_columns Function

The Lookup_helper_columns function is the core of the code, generating auxiliary columns \(h_i(x)\) and \(g(x)\), and computing the recursive verification polynomial \(Z(x)\).

Key Flow and Code Analysis

1. Initialization and Degree Constraint Check:


assert_eq!(constraint_degree, 3, "TODO: Allow other constraint degrees.");

The constraint degree constraint_degree is fixed at 3 to ensure the formula calculations conform to the predefined structure. Currently, the code supports only this degree.

2. Get the Number of Auxiliary Columns:


let num_helper_columns = Lookup.num_helper_columns(constraint_degree);

num_helper_columns calculates the number of auxiliary columns required, including \(h_i(x)\), 1 / (table + challenge), and \(Z(x)\).

3. Generate h_i(x) Auxiliary Columns:


let mut helper_columns = get_helper_cols(
    trace_poly_values,
    trace_poly_values[0].len(),
    &columns_filters,
    grand_challenge,
    constraint_degree,
);

\(h_i(x)\) Calculation: The get_helper_cols function divides the columns into batches, with each batch containing constraint_degree - 1 elements. For each batch, an auxiliary column \(h_i(x)\) is generated:

\[h_i(x) = \frac{1}{x+f_{2i}} + \frac{1}{x+f_{2(i+1)}}\]

Purpose: \(h_i(x)\) helps verify, during recursion, whether column values are present in table_column.

4. Compute the Inverse of the Table Column 1/(table + challenge):

let mut table = Lookup.table_column.eval_all_rows(trace_poly_values);
 for x in table.iter_mut() {
     *x = challenge + *x;
 }
 let table_inverse: Vec<F> = F::batch_multiplicative_inverse(&table);

Table Column Inverse  \(g(x)\): After adding the challenge value challenge to each \(t(x)\), the reciprocal is taken, forming the auxiliary column

\[g(x) = \frac{1}{x+t(x)}\]

Purpose: This auxiliary column ensures that  \(t(x)\) participates in verification through its reciprocal.

5. Compute the Recursive Polynomial \(Z(x)\):

let frequencies = &Lookup.frequencies_column.eval_all_rows(trace_poly_values);
 let mut z = Vec::with_capacity(frequencies.len());
 z.push(F::ZERO);
 for i in 0..frequencies.len() - 1 {
     let x = helper_columns[..num_helper_columns - 1]
         .iter()
         .map(|col| col.values[i])
         .sum::<F>()
         - frequencies[i] * table_inverse[i];
     z.push(z[i] + x);
 }
 helper_columns.push(z.into());

\(Z(x)\) Recursion Formula: The recursion formula for \(Z(x)\) is:

\[Z(gx) = Z(x) + \sum h_i(x) - m(x) \cdot g(x)\]

Where frequencies represent  \(m(x)\), and table_inverse represents  \(g(x)\).

Purpose: Through recursion, this formula verifies the consistency of the distributions of \(f_i(x)\) and \(t(x)\), ensuring that all columns satisfy the Lookup constraints.

3.3 eval_packed_Lookups_generic Function

The eval_packed_Lookups_generic function checks the correctness of the auxiliary columns and \(Z(x)\) using the recursive formula to validate whether column values are included in the table_column.

1. Verify the Correctness of Auxiliary Columns h_i(x):

eval_helper_columns(
    &Lookup.filter_columns,
    &Lookup_columns,
    local_values,
    next_values,
    &Lookup_vars.local_values[start..start + num_helper_columns - 1],
    degree,
    &grand_challenge,
    yield_constr,
);

Here, the eval_helper_columns function is called to check each row of h_i(x) and ensure that the auxiliary columns are correctly generated.

2. Verify the Recursion of \(Z(x)\):

let z = Lookup_vars.local_values[start + num_helper_columns - 1];
let next_z = Lookup_vars.next_values[start + num_helper_columns - 1];
let table_with_challenge = Lookup.table_column.eval(local_values) + challenge;
let y = Lookup_vars.local_values[start..start + num_helper_columns - 1]
    .iter()
    .fold(P::ZEROS, |acc, x| acc + *x)
    * table_with_challenge
    - Lookup.frequencies_column.eval(local_values);
yield_constr.constraint_first_row(z);
yield_constr.constraint((next_z - z) * table_with_challenge - y);

Check the Recursion of  \(Z(x)\): Ensures that the initial value of \(Z(x)\) is 0 and that every row satisfies:

\[Z(gx) = Z(x) + \sum h_i(x) - m(x) \cdot g(x)\]

3.4 eval_ext_Lookups_circuit Function

This function builds recursive constraints at the circuit level, ensuring that the Lookup process remains consistent within the recursive proof circuit. It verifies the auxiliary columns and \(Z(x)\) to support the recursive proof.

Subscribe to the ZKM Blog to stay updated with the latest research by ZKM. If you have any questions about the article, you can contact the ZKM team on discord: discord.com/zkm