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