Skip to main content

ark_groth16/
r1cs_to_qap.rs

1use ark_ff::PrimeField;
2use ark_poly::EvaluationDomain;
3use ark_std::{cfg_iter_mut, vec};
4
5use crate::Vec;
6use ark_relations::gr1cs::{
7    ConstraintSystemRef, Matrix, Result as R1CSResult, SynthesisError, R1CS_PREDICATE_LABEL,
8};
9use core::ops::Deref;
10
11#[cfg(feature = "parallel")]
12use rayon::prelude::*;
13
14#[inline]
15/// Computes the inner product of `terms` with `assignment`.
16///
17/// This implementation is optimized for both parallel and sequential execution:
18/// - In parallel mode, it uses Rayon's parallel iterator for efficient
19///   multi-threading
20/// - In sequential mode, it processes elements in chunks for better
21///   vectorization
22///
23/// # Performance characteristics
24/// - Time complexity: O(n) where n is the number of terms
25/// - Space complexity: O(1) in sequential mode, O(log n) in parallel mode due
26///   to work splitting
27///
28/// # Arguments
29/// * `terms` - Slice of tuples containing coefficients and their indices
30/// * `assignment` - Slice of values to be multiplied with coefficients
31pub fn evaluate_constraint<F: PrimeField>(terms: &[(F, usize)], assignment: &[F]) -> F {
32    #[cfg(feature = "parallel")]
33    if terms.len() < 100 {
34        serial_evaluate_constraint(terms, assignment)
35    } else {
36        terms
37            .par_iter()
38            .map(|(coeff, index)| {
39                let val = assignment[*index];
40                if coeff.is_one() {
41                    val
42                } else {
43                    val * coeff
44                }
45            })
46            .sum()
47    }
48    #[cfg(not(feature = "parallel"))]
49    serial_evaluate_constraint(terms, assignment)
50}
51
52fn serial_evaluate_constraint<F: PrimeField>(terms: &[(F, usize)], assignment: &[F]) -> F {
53    let mut sum = F::zero();
54    // Process elements in chunks for better CPU vectorization
55    for chunk in terms.chunks(4) {
56        let chunk_sum = chunk
57            .iter()
58            .map(|(coeff, index)| {
59                let val = assignment[*index];
60                if coeff.is_one() {
61                    val
62                } else {
63                    val * coeff
64                }
65            })
66            .sum::<F>();
67        sum += chunk_sum;
68    }
69    sum
70}
71
72/// Computes instance and witness reductions from R1CS to
73/// Quadratic Arithmetic Programs (QAPs).
74pub trait R1CSToQAP {
75    /// Computes a QAP instance corresponding to the R1CS instance defined by
76    /// `cs`.
77    fn instance_map_with_evaluation<F: PrimeField, D: EvaluationDomain<F>>(
78        cs: ConstraintSystemRef<F>,
79        t: &F,
80    ) -> Result<(Vec<F>, Vec<F>, Vec<F>, F, usize, usize), SynthesisError>;
81
82    #[inline]
83    /// Computes a QAP witness corresponding to the R1CS witness defined by
84    /// `cs`.
85    fn witness_map<F: PrimeField, D: EvaluationDomain<F>>(
86        prover: ConstraintSystemRef<F>,
87    ) -> Result<Vec<F>, SynthesisError> {
88        let matrices = &prover.to_matrices().unwrap()[R1CS_PREDICATE_LABEL];
89        let num_inputs = prover.num_instance_variables();
90        let num_constraints = prover.num_constraints();
91
92        let cs = prover.borrow().unwrap();
93        let prover = cs.deref();
94
95        let full_assignment = [
96            prover.instance_assignment().unwrap(),
97            prover.witness_assignment().unwrap(),
98        ]
99        .concat();
100
101        Self::witness_map_from_matrices::<F, D>(
102            &matrices,
103            num_inputs,
104            num_constraints,
105            &full_assignment,
106        )
107    }
108
109    /// Computes a QAP witness corresponding to the R1CS witness defined by
110    /// `cs`.
111    fn witness_map_from_matrices<F: PrimeField, D: EvaluationDomain<F>>(
112        matrices: &[Matrix<F>],
113        num_inputs: usize,
114        num_constraints: usize,
115        full_assignment: &[F],
116    ) -> R1CSResult<Vec<F>>;
117
118    /// Computes the exponents that the generator uses to calculate base
119    /// elements which the prover later uses to compute `h(x)t(x)/delta`.
120    fn h_query_scalars<F: PrimeField, D: EvaluationDomain<F>>(
121        max_power: usize,
122        t: F,
123        zt: F,
124        delta_inverse: F,
125    ) -> Result<Vec<F>, SynthesisError>;
126}
127
128/// Computes the R1CS-to-QAP reduction defined in [`libsnark`](https://github.com/scipr-lab/libsnark/blob/2af440246fa2c3d0b1b0a425fb6abd8cc8b9c54d/libsnark/reductions/r1cs_to_qap/r1cs_to_qap.tcc).
129pub struct LibsnarkReduction;
130
131impl R1CSToQAP for LibsnarkReduction {
132    #[inline]
133    #[allow(clippy::type_complexity)]
134    fn instance_map_with_evaluation<F: PrimeField, D: EvaluationDomain<F>>(
135        cs: ConstraintSystemRef<F>,
136        t: &F,
137    ) -> R1CSResult<(Vec<F>, Vec<F>, Vec<F>, F, usize, usize)> {
138        let matrices = &cs.to_matrices().unwrap()[R1CS_PREDICATE_LABEL];
139        let domain_size = cs.num_constraints() + cs.num_instance_variables();
140        let domain = D::new(domain_size).ok_or(SynthesisError::PolynomialDegreeTooLarge)?;
141        let domain_size = domain.size();
142
143        let zt = domain.evaluate_vanishing_polynomial(*t);
144
145        // Evaluate all Lagrange polynomials
146        let coefficients_time = start_timer!(|| "Evaluate Lagrange coefficients");
147        let u = domain.evaluate_all_lagrange_coefficients(*t);
148        end_timer!(coefficients_time);
149
150        let qap_num_variables = (cs.num_instance_variables() - 1) + cs.num_witness_variables();
151
152        let mut a = vec![F::zero(); qap_num_variables + 1];
153        let mut b = vec![F::zero(); qap_num_variables + 1];
154        let mut c = vec![F::zero(); qap_num_variables + 1];
155
156        {
157            let start = 0;
158            let end = cs.num_instance_variables();
159            let num_constraints = cs.num_constraints();
160            a[start..end].copy_from_slice(&u[(start + num_constraints)..(end + num_constraints)]);
161        }
162
163        for (i, u_i) in u.iter().enumerate().take(cs.num_constraints()) {
164            for &(ref coeff, index) in &matrices[0][i] {
165                a[index] += &(*u_i * coeff);
166            }
167            for &(ref coeff, index) in &matrices[1][i] {
168                b[index] += &(*u_i * coeff);
169            }
170            for &(ref coeff, index) in &matrices[2][i] {
171                c[index] += &(*u_i * coeff);
172            }
173        }
174
175        Ok((a, b, c, zt, qap_num_variables, domain_size))
176    }
177
178    fn witness_map_from_matrices<F: PrimeField, D: EvaluationDomain<F>>(
179        matrices: &[Matrix<F>],
180        num_inputs: usize,
181        num_constraints: usize,
182        full_assignment: &[F],
183    ) -> R1CSResult<Vec<F>> {
184        let domain =
185            D::new(num_constraints + num_inputs).ok_or(SynthesisError::PolynomialDegreeTooLarge)?;
186        let domain_size = domain.size();
187        let zero = F::zero();
188
189        let mut a = vec![zero; domain_size];
190        let mut b = vec![zero; domain_size];
191
192        cfg_iter_mut!(a[..num_constraints])
193            .zip(&mut b[..num_constraints])
194            .zip(&matrices[0])
195            .zip(&matrices[1])
196            .for_each(|(((a, b), at_i), bt_i)| {
197                *a = evaluate_constraint(&at_i, &full_assignment);
198                *b = evaluate_constraint(&bt_i, &full_assignment);
199            });
200
201        {
202            let start = num_constraints;
203            let end = start + num_inputs;
204            a[start..end].clone_from_slice(&full_assignment[..num_inputs]);
205        }
206
207        domain.ifft_in_place(&mut a);
208        domain.ifft_in_place(&mut b);
209
210        let coset_domain = domain.get_coset(F::GENERATOR).unwrap();
211
212        coset_domain.fft_in_place(&mut a);
213        coset_domain.fft_in_place(&mut b);
214
215        let mut ab = domain.mul_polynomials_in_evaluation_domain(&a, &b);
216        drop(a);
217        drop(b);
218
219        let mut c = vec![zero; domain_size];
220        cfg_iter_mut!(c[..num_constraints])
221            .enumerate()
222            .for_each(|(i, c)| {
223                *c = evaluate_constraint(&matrices[2][i], &full_assignment);
224            });
225
226        domain.ifft_in_place(&mut c);
227        coset_domain.fft_in_place(&mut c);
228
229        let vanishing_polynomial_over_coset = domain
230            .evaluate_vanishing_polynomial(F::GENERATOR)
231            .inverse()
232            .unwrap();
233        cfg_iter_mut!(ab).zip(c).for_each(|(ab_i, c_i)| {
234            *ab_i -= &c_i;
235            *ab_i *= &vanishing_polynomial_over_coset;
236        });
237
238        coset_domain.ifft_in_place(&mut ab);
239
240        Ok(ab)
241    }
242
243    fn h_query_scalars<F: PrimeField, D: EvaluationDomain<F>>(
244        max_power: usize,
245        t: F,
246        zt: F,
247        delta_inverse: F,
248    ) -> Result<Vec<F>, SynthesisError> {
249        let scalars = cfg_into_iter!(0..max_power)
250            .map(|i| zt * &delta_inverse * &t.pow([i as u64]))
251            .collect::<Vec<_>>();
252        Ok(scalars)
253    }
254}