sonobe/folding-schemes/src/folding/hypernova/lcccs.rs
folding-schemes/src/folding/hypernova/lcccs.rsRaw
1use ark_crypto_primitives::sponge::Absorb;
2use ark_ec::CurveGroup;
3use ark_ff::PrimeField;
4use ark_poly::DenseMultilinearExtension;
5use ark_poly::MultilinearExtension;
6use ark_serialize::CanonicalDeserialize;
7use ark_serialize::CanonicalSerialize;
8use ark_std::rand::Rng;
9use ark_std::Zero;
10
11use super::circuits::LCCCSVar;
12use super::Witness;
13use crate::arith::ccs::CCS;
14use crate::arith::Arith;
15use crate::commitment::CommitmentScheme;
16use crate::folding::circuits::CF1;
17use crate::folding::traits::{CommittedInstanceOps, Dummy};
18use crate::transcript::AbsorbNonNative;
19use crate::utils::mle::dense_vec_to_dense_mle;
20use crate::utils::vec::mat_vec_mul;
21use crate::Error;
22
23/// Linearized Committed CCS instance
24#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
25pub struct LCCCS<C: CurveGroup> {
26 // Commitment to witness
27 pub C: C,
28 // Relaxation factor of z for folded LCCCS
29 pub u: C::ScalarField,
30 // Public input/output
31 pub x: Vec<C::ScalarField>,
32 // Random evaluation point for the v_i
33 pub r_x: Vec<C::ScalarField>,
34 // Vector of v_i
35 pub v: Vec<C::ScalarField>,
36}
37
38impl<F: PrimeField> CCS<F> {
39 pub fn to_lcccs<R: Rng, C, CS: CommitmentScheme<C, H>, const H: bool>(
40 &self,
41 rng: &mut R,
42 cs_params: &CS::ProverParams,
43 z: &[C::ScalarField],
44 ) -> Result<(LCCCS<C>, Witness<C::ScalarField>), Error>
45 where
46 // enforce that CCS's F is the C::ScalarField
47 C: CurveGroup<ScalarField = F>,
48 {
49 let w: Vec<C::ScalarField> = z[(1 + self.l)..].to_vec();
50 // if the commitment scheme is set to be hiding, set the random blinding parameter
51 let r_w = if CS::is_hiding() {
52 C::ScalarField::rand(rng)
53 } else {
54 C::ScalarField::zero()
55 };
56 let C = CS::commit(cs_params, &w, &r_w)?;
57
58 let r_x: Vec<C::ScalarField> = (0..self.s).map(|_| C::ScalarField::rand(rng)).collect();
59
60 let Mzs: Vec<DenseMultilinearExtension<F>> = self
61 .M
62 .iter()
63 .map(|M_j| Ok(dense_vec_to_dense_mle(self.s, &mat_vec_mul(M_j, z)?)))
64 .collect::<Result<_, Error>>()?;
65
66 // compute v_j
67 let v: Vec<F> = Mzs
68 .iter()
69 .map(|Mz| Mz.evaluate(&r_x).ok_or(Error::EvaluationFail))
70 .collect::<Result<_, Error>>()?;
71
72 Ok((
73 LCCCS::<C> {
74 C,
75 u: C::ScalarField::one(),
76 x: z[1..(1 + self.l)].to_vec(),
77 r_x,
78 v,
79 },
80 Witness::<C::ScalarField> { w, r_w },
81 ))
82 }
83}
84
85impl<C: CurveGroup> Dummy<&CCS<CF1<C>>> for LCCCS<C> {
86 fn dummy(ccs: &CCS<CF1<C>>) -> Self {
87 Self {
88 C: C::zero(),
89 u: CF1::<C>::zero(),
90 x: vec![CF1::<C>::zero(); ccs.l],
91 r_x: vec![CF1::<C>::zero(); ccs.s],
92 v: vec![CF1::<C>::zero(); ccs.t],
93 }
94 }
95}
96
97impl<C: CurveGroup> Arith<Witness<CF1<C>>, LCCCS<C>> for CCS<CF1<C>> {
98 type Evaluation = Vec<CF1<C>>;
99
100 /// Perform the check of the LCCCS instance described at section 4.2,
101 /// notice that this method does not check the commitment correctness
102 fn eval_relation(&self, w: &Witness<CF1<C>>, u: &LCCCS<C>) -> Result<Self::Evaluation, Error> {
103 let z = [&[u.u][..], &u.x, &w.w].concat();
104
105 self.M
106 .iter()
107 .map(|M_j| {
108 let Mz_mle = dense_vec_to_dense_mle(self.s, &mat_vec_mul(M_j, &z)?);
109 Mz_mle.evaluate(&u.r_x).ok_or(Error::EvaluationFail)
110 })
111 .collect()
112 }
113
114 fn check_evaluation(
115 _w: &Witness<CF1<C>>,
116 u: &LCCCS<C>,
117 e: Self::Evaluation,
118 ) -> Result<(), Error> {
119 (u.v == e).then_some(()).ok_or(Error::NotSatisfied)
120 }
121}
122
123impl<C: CurveGroup> Absorb for LCCCS<C>
124where
125 C::ScalarField: Absorb,
126{
127 fn to_sponge_bytes(&self, dest: &mut Vec<u8>) {
128 C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest);
129 }
130
131 fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) {
132 // We cannot call `to_native_sponge_field_elements(dest)` directly, as
133 // `to_native_sponge_field_elements` needs `F` to be `C::ScalarField`,
134 // but here `F` is a generic `PrimeField`.
135 self.C
136 .to_native_sponge_field_elements_as_vec()
137 .to_sponge_field_elements(dest);
138 self.u.to_sponge_field_elements(dest);
139 self.x.to_sponge_field_elements(dest);
140 self.r_x.to_sponge_field_elements(dest);
141 self.v.to_sponge_field_elements(dest);
142 }
143}
144
145impl<C: CurveGroup> CommittedInstanceOps<C> for LCCCS<C> {
146 type Var = LCCCSVar<C>;
147
148 fn get_commitments(&self) -> Vec<C> {
149 vec![self.C]
150 }
151
152 fn is_incoming(&self) -> bool {
153 false
154 }
155}
156
157#[cfg(test)]
158pub mod tests {
159 use ark_pallas::{Fr, Projective};
160 use ark_std::test_rng;
161 use ark_std::One;
162 use ark_std::UniformRand;
163 use std::sync::Arc;
164
165 use super::*;
166 use crate::arith::{
167 ccs::tests::{get_test_ccs, get_test_z},
168 r1cs::R1CS,
169 Arith,
170 };
171 use crate::commitment::pedersen::Pedersen;
172 use crate::utils::hypercube::BooleanHypercube;
173 use crate::utils::virtual_polynomial::{build_eq_x_r_vec, VirtualPolynomial};
174
175 // method for testing
176 pub fn compute_Ls<C: CurveGroup>(
177 ccs: &CCS<C::ScalarField>,
178 lcccs: &LCCCS<C>,
179 z: &[C::ScalarField],
180 ) -> Vec<VirtualPolynomial<C::ScalarField>> {
181 let eq_rx = build_eq_x_r_vec(&lcccs.r_x).unwrap();
182 let eq_rx_mle = dense_vec_to_dense_mle(ccs.s, &eq_rx);
183
184 let mut Ls = Vec::with_capacity(ccs.t);
185 for M_j in ccs.M.iter() {
186 let mut L = VirtualPolynomial::<C::ScalarField>::new(ccs.s);
187 let mut Mz = vec![dense_vec_to_dense_mle(ccs.s, &mat_vec_mul(M_j, z).unwrap())];
188 Mz.push(eq_rx_mle.clone());
189 L.add_mle_list(
190 Mz.iter().map(|v| Arc::new(v.clone())),
191 C::ScalarField::one(),
192 )
193 .unwrap();
194 Ls.push(L);
195 }
196 Ls
197 }
198
199 #[test]
200 /// Test linearized CCCS v_j against the L_j(x)
201 fn test_lcccs_v_j() {
202 let mut rng = test_rng();
203
204 let n_rows = 2_u32.pow(5) as usize;
205 let n_cols = 2_u32.pow(5) as usize;
206 let r1cs = R1CS::<Fr>::rand(&mut rng, n_rows, n_cols);
207 let ccs = CCS::from(r1cs);
208 let z: Vec<Fr> = (0..n_cols).map(|_| Fr::rand(&mut rng)).collect();
209
210 let (pedersen_params, _) =
211 Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
212
213 let (lcccs, _) = ccs
214 .to_lcccs::<_, Projective, Pedersen<Projective, false>, false>(
215 &mut rng,
216 &pedersen_params,
217 &z,
218 )
219 .unwrap();
220 // with our test vector coming from R1CS, v should have length 3
221 assert_eq!(lcccs.v.len(), 3);
222
223 let vec_L_j_x = compute_Ls(&ccs, &lcccs, &z);
224 assert_eq!(vec_L_j_x.len(), lcccs.v.len());
225
226 for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) {
227 let sum_L_j_x = BooleanHypercube::new(ccs.s)
228 .map(|y| L_j_x.evaluate(&y).unwrap())
229 .fold(Fr::zero(), |acc, result| acc + result);
230 assert_eq!(v_i, sum_L_j_x);
231 }
232 }
233
234 /// Given a bad z, check that the v_j should not match with the L_j(x)
235 #[test]
236 fn test_bad_v_j() {
237 let mut rng = test_rng();
238
239 let ccs = get_test_ccs();
240 let z = get_test_z(3);
241 let (w, x) = ccs.split_z(&z);
242 ccs.check_relation(&w, &x).unwrap();
243
244 // Mutate z so that the relation does not hold
245 let mut bad_z = z.clone();
246 bad_z[3] = Fr::zero();
247 let (bad_w, bad_x) = ccs.split_z(&bad_z);
248 assert!(ccs.check_relation(&bad_w, &bad_x).is_err());
249
250 let (pedersen_params, _) =
251 Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
252 // Compute v_j with the right z
253 let (lcccs, _) = ccs
254 .to_lcccs::<_, Projective, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z)
255 .unwrap();
256 // with our test vector coming from R1CS, v should have length 3
257 assert_eq!(lcccs.v.len(), 3);
258
259 // Bad compute L_j(x) with the bad z
260 let vec_L_j_x = compute_Ls(&ccs, &lcccs, &bad_z);
261 assert_eq!(vec_L_j_x.len(), lcccs.v.len());
262
263 // Make sure that the LCCCS is not satisfied given these L_j(x)
264 // i.e. summing L_j(x) over the hypercube should not give v_j for all j
265 let mut satisfied = true;
266 for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) {
267 let sum_L_j_x = BooleanHypercube::new(ccs.s)
268 .map(|y| L_j_x.evaluate(&y).unwrap())
269 .fold(Fr::zero(), |acc, result| acc + result);
270 if v_i != sum_L_j_x {
271 satisfied = false;
272 }
273 }
274 assert!(!satisfied);
275 }
276}