Skip to main content

sonobe_primitives/arithmetizations/ccs/
mod.rs

1//! This module implements the Customizable Constraint System (CCS) and its
2//! relation checks against plain witnesses and instances.
3//!
4//! Proposed in the CCS [paper], it is a generalization of R1CS as well as many
5//! other constraint systems.
6//! A CCS structure is defined by the following components:
7//! - The number of constraints `m`, the number of variables `n`, and the number
8//!   of public inputs `l`.
9//! - The degree `d`.
10//! - A sequence of `t` matrices `M`.
11//! - A sequence of `q` multisets `S`, where each multiset `S_i` has at most `d`
12//!   elements and each element is an index in `[0, t - 1]` pointing to a matrix
13//!   `M_j`.
14//! - A sequence of `q` coefficients `c`.
15//!
16//! A vector of assignments `z` satisfies the CCS if its evaluation
17//! `Σ_{i ∈ {0, q-1}} (c_i · 〇_{j ∈ S_i} (M_j · z))` is zero, where `〇` denotes
18//! the Hadamard product among all `M_j · z`.
19//!
20//! [paper]: https://eprint.iacr.org/2023/552.pdf
21
22use ark_ff::Field;
23use ark_poly::DenseMultilinearExtension;
24use ark_relations::gr1cs::{ConstraintSystem, Matrix};
25use ark_std::{cfg_into_iter, cfg_iter};
26#[cfg(feature = "parallel")]
27use rayon::prelude::*;
28
29use super::{Arith, Error};
30use crate::{algebra::ops::poly::MLEHelper, circuits::Assignments};
31
32/// [`CCS`] is an abstract trait that defines the behavior of all CCS variants,
33/// including but not limited to R1CS.
34pub trait CCS:
35    Arith + for<'a> From<&'a ConstraintSystem<Self::Field>> + From<ConstraintSystem<Self::Field>>
36{
37    /// [`CCS::Field`] specifies the underlying field of a CCS instance
38    type Field: Field;
39
40    /// [`CCS::matrices`] returns the matrices contained in a concrete CCS
41    /// instance `self`.
42    fn matrices(&self) -> &[Matrix<Self::Field>];
43
44    /// [`CCS::evaluate_ccs`] evaluates the CCS relation at a given vector of
45    /// assignments, multisets, and coefficients.
46    fn evaluate_ccs<const Q: usize>(
47        &self,
48        z: Assignments<Self::Field, impl AsRef<[Self::Field]> + Sync>,
49        multisets: [Vec<usize>; Q],
50        coefficients: [Self::Field; Q],
51    ) -> Result<Vec<Self::Field>, Error> {
52        let cfg = self.config();
53        let matrices = self.matrices();
54
55        let public_len = z.public.as_ref().len();
56        let private_len = z.private.as_ref().len();
57        if public_len != cfg.n_public_inputs {
58            return Err(Error::MalformedAssignments(format!(
59                "The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).",
60                cfg.n_public_inputs, public_len
61            )));
62        }
63        if private_len != cfg.n_witnesses {
64            return Err(Error::MalformedAssignments(format!(
65                "The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).",
66                cfg.n_witnesses, private_len
67            )));
68        }
69
70        // Recall that the evaluation of CCS at z is defined as:
71        // `Σ_{i ∈ {0, q-1}} (c_i · 〇_{j ∈ S_i} (M_j · z))`,
72        // where $\prod$ denotes the Hadamard product.
73        //
74        // Below, we manually expand the vector and matrix operations for less
75        // allocations and better efficiency.
76        // Specifically, we independently compute each entry of the resulting
77        // vector, and collect them at the end.
78        // We parallelize the outer loop over rows (when the `parallel` feature
79        // is enabled), since the number of constraints in the CCS is typically
80        // large in practice.
81        Ok(cfg_into_iter!(0..cfg.n_constraints)
82            .map(|row| {
83                // The `row`-th entry of the resulting vector is:
84                // `Σ_{i ∈ {0, q-1}} (c_i · 〇_{j ∈ S_i} (M_j[row] · z))`
85                multisets
86                    .iter()
87                    .zip(coefficients)
88                    .map(|(s, c)| {
89                        // Each term in the sum is:
90                        // `c_i · 〇_{j ∈ S_i} (M_j[row] · z)`
91                        c * s
92                            .iter()
93                            .map(|&i| {
94                                // Each factor in the product is `M_j[row] · z`,
95                                // i.e., the dot product of `M_j[row]` and `z`.
96                                matrices[i][row]
97                                    .iter()
98                                    .map(|(val, col)| z[*col] * val)
99                                    .sum::<Self::Field>()
100                            })
101                            .product::<Self::Field>()
102                    })
103                    .sum()
104            })
105            .collect())
106    }
107
108    /// [`CCS::mles`] returns the multilinear extensions of all CCS matrices
109    /// `M_i` evaluated over the assignments `z`.
110    fn mles(
111        &self,
112        z: Assignments<Self::Field, impl AsRef<[Self::Field]> + Sync>,
113    ) -> Vec<DenseMultilinearExtension<Self::Field>> {
114        self.matrices()
115            .iter()
116            .map(|matrix| {
117                DenseMultilinearExtension::from_evaluations(
118                    &cfg_iter!(matrix)
119                        .map(|row| row.iter().map(|(val, col)| z[*col] * val).sum())
120                        .collect::<Vec<_>>(),
121                )
122            })
123            .collect()
124    }
125}