From 3463e430c863f5711d698373c991c297ee3a3a17 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 15 Feb 2024 09:51:10 -0500 Subject: [PATCH] Unify shapes. --- supernova/src/ccs/lccs.rs | 448 -------------------------------------- supernova/src/ccs/mod.rs | 372 ++++++++++++++++++++++++++++++- 2 files changed, 370 insertions(+), 450 deletions(-) delete mode 100644 supernova/src/ccs/lccs.rs diff --git a/supernova/src/ccs/lccs.rs b/supernova/src/ccs/lccs.rs deleted file mode 100644 index 0e853f7d0..000000000 --- a/supernova/src/ccs/lccs.rs +++ /dev/null @@ -1,448 +0,0 @@ -use ark_crypto_primitives::sponge::CryptographicSponge; -use ark_ec::{AdditiveGroup, CurveGroup}; -use ark_ff::Field; -use ark_poly::{ - DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension, -}; -use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; - -use ark_std::ops::Index; -use std::ops::Neg; - -#[cfg(feature = "parallel")] -use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; - -use super::super::r1cs::R1CSShape; -pub use super::super::sparse::{MatrixRef, SparseMatrix}; -use super::mle::{fold_vec_to_mle_low, matrix_to_mle, mle_to_mvp, vec_to_mle}; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Error { - InvalidWitnessLength, - InvalidInputLength, - InvalidEvaluationPoint, - InvalidTargets, - FailedWitnessCommitting, - NotSatisfied, -} - -pub struct LCCSShape { - /// `m` in the CCS/HyperNova papers. - pub num_constraints: usize, - /// Witness length. - /// - /// `m - l - 1` in the CCS/HyperNova papers. - pub num_vars: usize, - /// Length of the public input `X`. It is expected to have a leading - /// `ScalarField` element (`u`), thus this field must be non-zero. - /// - /// `l + 1`, w.r.t. the CCS/HyperNova papers. - pub num_io: usize, - /// Number of matrices. - /// - /// `t` in the CCS/HyperNova papers. - pub num_matrices: usize, - /// Number of multisets. - /// - /// `q` in the CCS/HyperNova papers. - pub num_multisets: usize, - /// Max cardinality of the multisets. - /// - /// `d` in the CCS/HyperNova papers. - pub max_cardinality: usize, - /// Set of constraint matrices. - pub Ms: Vec>, - /// Multisets of selector indices, each paired with a constant multiplier. - pub cSs: Vec<(G::ScalarField, Vec)>, -} - -impl LCCSShape { - pub fn is_satisfied< - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - >( - &self, - U: &LCCSInstance, - W: &LCCSWitness, - ck: &P::CommitterKey, - ) -> Result<(), Error> - where - P::Commitment: PartialEq, - { - assert_eq!(U.X.len(), self.num_io); - - let z: DenseMultilinearExtension = fold_vec_to_mle_low(&U.X, &W.W); - - let Mrs: Vec> = ark_std::cfg_iter!(&self.Ms) - .map(|M| M.fix_variables(U.rs.as_slice())) - .collect(); - - let n = (self.num_io + self.num_vars).next_power_of_two(); - - let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); // for fixing endianness - - let Mzs: Vec = ark_std::cfg_iter!(Mrs) - .map(|M| { - (0..n) - .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) - .sum() - }) - .collect(); - - if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { - return Err(Error::NotSatisfied); - } - - let mvp_W: M = mle_to_mvp::(&W.W); - - let lab_W = LabeledPolynomial::::new( - "witness".to_string(), - mvp_W, - Some(W.W.num_vars), - None, - ); - - if let Ok(commit) = P::commit(ck, &[lab_W], None) { - if U.commitment_W != *commit.0[0].commitment() { - return Err(Error::NotSatisfied); - } - - Ok(()) - } else { - Err(Error::FailedWitnessCommitting) - } - } -} - -/// Create an object of type `LCCSShape` from the specified R1CS shape -impl From> for LCCSShape { - fn from(shape: R1CSShape) -> Self { - let rows = shape.num_constraints; - let columns = shape.num_io + shape.num_vars; - - Self { - num_constraints: shape.num_constraints, - num_io: shape.num_io, - num_vars: shape.num_vars, - num_matrices: 3, - num_multisets: 2, - max_cardinality: 2, - Ms: vec![ - matrix_to_mle(rows, columns, &shape.A), - matrix_to_mle(rows, columns, &shape.B), - matrix_to_mle(rows, columns, &shape.C), - ], - cSs: vec![ - (G::ScalarField::ONE, vec![0, 1]), - (G::ScalarField::ONE.neg(), vec![2]), - ], - } - } -} - -/// A type that holds a witness for a given LCCS instance. -#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] -pub struct LCCSWitness { - pub W: DenseMultilinearExtension, -} - -/// A type that holds an LCCS instance. -#[derive(CanonicalSerialize, CanonicalDeserialize)] -pub struct LCCSInstance< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, -> { - /// Commitment to MLE of witness. - /// - /// C in HyperNova/CCS papers. - pub commitment_W: P::Commitment, - /// X is assumed to start with a `ScalarField` field element `u`. - pub X: Vec, - /// (Random) evaluation point - pub rs: Vec, - /// Evaluation targets - pub vs: Vec, -} - -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > Clone for LCCSInstance -{ - fn clone(&self) -> Self { - Self { - commitment_W: self.commitment_W.clone(), - X: self.X.clone(), - rs: self.rs.clone(), - vs: self.vs.clone(), - } - } -} - -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > PartialEq for LCCSInstance -where - P::Commitment: PartialEq, -{ - fn eq(&self, other: &Self) -> bool - where - P::Commitment: PartialEq, - { - self.commitment_W == other.commitment_W && self.X == other.X - } -} - -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > Eq for LCCSInstance -where - P::Commitment: Eq, -{ -} - -impl LCCSWitness { - /// A method to create a witness object using a vector of scalars. - pub fn new(shape: &LCCSShape, W: &[G::ScalarField]) -> Result { - if shape.num_vars != W.len() { - Err(Error::InvalidWitnessLength) - } else { - Ok(Self { W: vec_to_mle(W) }) - } - } - - pub fn zero(shape: &LCCSShape) -> Self { - Self { - W: vec_to_mle(vec![G::ScalarField::ZERO; shape.num_vars].as_slice()), - } - } - - /// Commits to the witness using the supplied key - pub fn commit< - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - >( - &self, - ck: &P::CommitterKey, - ) -> P::Commitment { - let mvp_W: M = mle_to_mvp::(&self.W); - - let lab_W = LabeledPolynomial::::new( - "witness".to_string(), - mvp_W, - Some(self.W.num_vars), - None, - ); - - let wc = P::commit(ck, &[lab_W], None).unwrap(); - wc.0[0].commitment().clone() - } -} - -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > LCCSInstance -{ - /// A method to create an instance object using constituent elements. - pub fn new( - shape: &LCCSShape, - commitment_W: &P::Commitment, - X: &[G::ScalarField], - rs: &[G::ScalarField], - vs: &[G::ScalarField], - ) -> Result { - if X.is_empty() || shape.num_io != X.len() { - Err(Error::InvalidInputLength) - } else if ((shape.num_constraints - 1).checked_ilog2().unwrap_or(0) + 1) != rs.len() as u32 - { - Err(Error::InvalidEvaluationPoint) - } else if shape.num_matrices != vs.len() { - Err(Error::InvalidTargets) - } else { - Ok(Self { - commitment_W: commitment_W.clone(), - X: X.to_owned(), - rs: rs.to_owned(), - vs: vs.to_owned(), - }) - } - } -} - -#[cfg(test)] -mod tests { - #![allow(non_upper_case_globals)] - #![allow(clippy::needless_range_loop)] - - use super::*; - - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_poly::polynomial::multivariate::{SparsePolynomial, SparseTerm}; - use ark_poly_commit::marlin_pst13_pc::MarlinPST13; - use ark_std::UniformRand; - - use ark_test_curves::bls12_381::{Bls12_381, G1Projective as G}; - - type F = as ark_ec::PrimeGroup>::ScalarField; - - type M = SparsePolynomial; // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial - type S = PoseidonSponge; - type P = MarlinPST13; - - use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; - - #[test] - fn zero_instance_is_satisfied() -> Result<(), Error> { - #[rustfmt::skip] - let a = { - let a: &[&[u64]] = &[ - &[1, 2, 3], - &[3, 4, 5], - &[6, 7, 8], - ]; - to_field_sparse::(a) - }; - - const NUM_CONSTRAINTS: usize = 3; - const NUM_WITNESS: usize = 1; - const NUM_PUBLIC: usize = 2; - - let mut rng = ark_std::test_rng(); - - let r1cs_shape: R1CSShape = - R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); - - let lccs_shape = LCCSShape::from(r1cs_shape); - - let X = to_field_elements::(&[0, 0]); - let W = to_field_elements::(&[0]); - let witness = LCCSWitness::::new(&lccs_shape, &W)?; - - let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); - let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); - - let commitment_W = witness.commit::(&ck); - - let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; - let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); - - let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); - - let Mrs: Vec> = ark_std::cfg_iter!(lccs_shape.Ms) - .map(|M| M.fix_variables(rs.as_slice())) - .collect(); - - let n = (NUM_WITNESS + NUM_PUBLIC).next_power_of_two(); - - let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); - - let vs: Vec = ark_std::cfg_iter!(Mrs) - .map(|M| { - (0..n) - .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) - .sum() - }) - .collect(); - - let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; - - lccs_shape.is_satisfied::(&instance, &witness, &ck)?; - - Ok(()) - } - - #[test] - fn is_satisfied() -> Result<(), Error> { - let (a, b, c) = { - ( - to_field_sparse::(A), - to_field_sparse::(B), - to_field_sparse::(C), - ) - }; - - const NUM_CONSTRAINTS: usize = 4; - const NUM_WITNESS: usize = 4; - const NUM_PUBLIC: usize = 2; - - let mut rng = ark_std::test_rng(); - - let r1cs_shape: R1CSShape = - R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); - - let lccs_shape = LCCSShape::from(r1cs_shape); - - let X = to_field_elements::(&[1, 35]); - let W = to_field_elements::(&[3, 9, 27, 30]); - let witness = LCCSWitness::::new(&lccs_shape, &W)?; - - let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); - let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); - - let commitment_W = witness.commit::(&ck); - - let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; - let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); - - let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); - - let Mrs: Vec> = ark_std::cfg_iter!(lccs_shape.Ms) - .map(|M| M.fix_variables(rs.as_slice())) - .collect(); - - let n = (NUM_WITNESS + NUM_PUBLIC).next_power_of_two(); - - let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); - - let vs: Vec = ark_std::cfg_iter!(Mrs) - .map(|M| { - (0..n) - .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) - .sum() - }) - .collect(); - - let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; - - lccs_shape.is_satisfied::(&instance, &witness, &ck)?; - - // Provide invalid witness. - let invalid_W = to_field_elements::(&[4, 9, 27, 30]); - let invalid_witness = LCCSWitness::::new(&lccs_shape, &invalid_W)?; - let commitment_invalid_W = invalid_witness.commit::(&ck); - - let instance = - LCCSInstance::::new(&lccs_shape, &commitment_invalid_W, &X, &rs, &vs)?; - assert_eq!( - lccs_shape.is_satisfied(&instance, &invalid_witness, &ck), - Err(Error::NotSatisfied) - ); - - // Provide invalid public input. - let invalid_X = to_field_elements::(&[1, 36]); - let instance = - LCCSInstance::::new(&lccs_shape, &commitment_W, &invalid_X, &rs, &vs)?; - assert_eq!( - lccs_shape.is_satisfied(&instance, &witness, &ck), - Err(Error::NotSatisfied) - ); - - Ok(()) - } -} diff --git a/supernova/src/ccs/mod.rs b/supernova/src/ccs/mod.rs index 8b03c28f4..a0ac21a31 100644 --- a/supernova/src/ccs/mod.rs +++ b/supernova/src/ccs/mod.rs @@ -1,13 +1,20 @@ +use ark_crypto_primitives::sponge::CryptographicSponge; use ark_ec::{AdditiveGroup, CurveGroup}; use ark_ff::Field; +use ark_poly::{ + DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension, +}; +use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::ops::Index; use ark_std::Zero; use std::ops::Neg; #[cfg(feature = "parallel")] use rayon::iter::{ - IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator, + IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, + IntoParallelRefMutIterator, ParallelIterator, }; use super::commitment::CommitmentScheme; @@ -15,13 +22,16 @@ use super::commitment::CommitmentScheme; use super::r1cs::R1CSShape; pub use super::sparse::{MatrixRef, SparseMatrix}; -pub mod lccs; pub mod mle; +use mle::{fold_vec_to_mle_low, matrix_to_mle, mle_to_mvp, vec_to_mle}; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { InvalidWitnessLength, InvalidInputLength, + InvalidEvaluationPoint, + InvalidTargets, + FailedWitnessCommitting, NotSatisfied, } @@ -98,6 +108,65 @@ impl CCSShape { Ok(()) } + + pub fn is_satisfied_linearized< + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + >( + &self, + U: &LCCSInstance, + W: &LCCSWitness, + ck: &P::CommitterKey, + ) -> Result<(), Error> + where + P::Commitment: PartialEq, + { + assert_eq!(U.X.len(), self.num_io); + + let z: DenseMultilinearExtension = fold_vec_to_mle_low(&U.X, &W.W); + + let rows = self.num_constraints; + let columns = self.num_io + self.num_vars; + + let Mrs: Vec> = ark_std::cfg_iter!(&self.Ms) + .map(|M| matrix_to_mle(rows, columns, M).fix_variables(U.rs.as_slice())) + .collect(); + + let n = columns.next_power_of_two(); + let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); // for fixing endianness + + let Mzs: Vec = ark_std::cfg_iter!(Mrs) + .map(|M| { + (0..n) + .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .sum() + }) + .collect(); + + if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { + return Err(Error::NotSatisfied); + } + + let mvp_W: M = mle_to_mvp::(&W.W); + + let lab_W = LabeledPolynomial::::new( + "witness".to_string(), + mvp_W, + Some(W.W.num_vars), + None, + ); + + if let Ok(commit) = P::commit(ck, &[lab_W], None) { + if U.commitment_W != *commit.0[0].commitment() { + return Err(Error::NotSatisfied); + } + + Ok(()) + } else { + Err(Error::FailedWitnessCommitting) + } + } } /// Create an object of type `CCSShape` from the specified R1CS shape @@ -194,6 +263,149 @@ impl> CCSInstance { } } +/// A type that holds a witness for a given LCCS instance. +#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] +pub struct LCCSWitness { + pub W: DenseMultilinearExtension, +} + +/// A type that holds an LCCS instance. +#[derive(CanonicalSerialize, CanonicalDeserialize)] +pub struct LCCSInstance< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, +> { + /// Commitment to MLE of witness. + /// + /// C in HyperNova/CCS papers. + pub commitment_W: P::Commitment, + /// X is assumed to start with a `ScalarField` field element `u`. + pub X: Vec, + /// (Random) evaluation point + pub rs: Vec, + /// Evaluation targets + pub vs: Vec, +} + +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > Clone for LCCSInstance +{ + fn clone(&self) -> Self { + Self { + commitment_W: self.commitment_W.clone(), + X: self.X.clone(), + rs: self.rs.clone(), + vs: self.vs.clone(), + } + } +} + +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > PartialEq for LCCSInstance +where + P::Commitment: PartialEq, +{ + fn eq(&self, other: &Self) -> bool + where + P::Commitment: PartialEq, + { + self.commitment_W == other.commitment_W && self.X == other.X + } +} + +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > Eq for LCCSInstance +where + P::Commitment: Eq, +{ +} + +impl LCCSWitness { + /// A method to create a witness object using a vector of scalars. + pub fn new(shape: &CCSShape, W: &[G::ScalarField]) -> Result { + if shape.num_vars != W.len() { + Err(Error::InvalidWitnessLength) + } else { + Ok(Self { W: vec_to_mle(W) }) + } + } + + pub fn zero(shape: &CCSShape) -> Self { + Self { + W: vec_to_mle(vec![G::ScalarField::ZERO; shape.num_vars].as_slice()), + } + } + + /// Commits to the witness using the supplied key + pub fn commit< + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + >( + &self, + ck: &P::CommitterKey, + ) -> P::Commitment { + let mvp_W: M = mle_to_mvp::(&self.W); + + let lab_W = LabeledPolynomial::::new( + "witness".to_string(), + mvp_W, + Some(self.W.num_vars), + None, + ); + + let wc = P::commit(ck, &[lab_W], None).unwrap(); + wc.0[0].commitment().clone() + } +} + +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > LCCSInstance +{ + /// A method to create an instance object using constituent elements. + pub fn new( + shape: &CCSShape, + commitment_W: &P::Commitment, + X: &[G::ScalarField], + rs: &[G::ScalarField], + vs: &[G::ScalarField], + ) -> Result { + if X.is_empty() || shape.num_io != X.len() { + Err(Error::InvalidInputLength) + } else if ((shape.num_constraints - 1).checked_ilog2().unwrap_or(0) + 1) != rs.len() as u32 + { + Err(Error::InvalidEvaluationPoint) + } else if shape.num_matrices != vs.len() { + Err(Error::InvalidTargets) + } else { + Ok(Self { + commitment_W: commitment_W.clone(), + X: X.to_owned(), + rs: rs.to_owned(), + vs: vs.to_owned(), + }) + } + } +} + #[cfg(test)] mod tests { #![allow(non_upper_case_globals)] @@ -298,4 +510,160 @@ mod tests { ); Ok(()) } + + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_poly::polynomial::multivariate::{SparsePolynomial, SparseTerm}; + use ark_poly_commit::marlin_pst13_pc::MarlinPST13; + use ark_std::UniformRand; + + use ark_test_curves::bls12_381::Bls12_381; + + type F = as ark_ec::PrimeGroup>::ScalarField; + + type M = SparsePolynomial; // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial + type S = PoseidonSponge; + type P = MarlinPST13; + + #[test] + fn zero_instance_is_satisfied_linearized() -> Result<(), Error> { + #[rustfmt::skip] + let a = { + let a: &[&[u64]] = &[ + &[1, 2, 3], + &[3, 4, 5], + &[6, 7, 8], + ]; + to_field_sparse::(a) + }; + + const NUM_CONSTRAINTS: usize = 3; + const NUM_WITNESS: usize = 1; + const NUM_PUBLIC: usize = 2; + + let mut rng = ark_std::test_rng(); + + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + + let X = to_field_elements::(&[0, 0]); + let W = to_field_elements::(&[0]); + let witness = LCCSWitness::::new(&ccs_shape, &W)?; + + let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); + let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); + + let commitment_W = witness.commit::(&ck); + + let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; + let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); + + let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); + + let rows = NUM_CONSTRAINTS; + let columns = NUM_WITNESS + NUM_PUBLIC; + + let Mrs: Vec> = ark_std::cfg_iter!(ccs_shape.Ms) + .map(|M| matrix_to_mle(rows, columns, M).fix_variables(rs.as_slice())) + .collect(); + + let n = columns.next_power_of_two(); + let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); + + let vs: Vec = ark_std::cfg_iter!(Mrs) + .map(|M| { + (0..n) + .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .sum() + }) + .collect(); + + let instance = LCCSInstance::::new(&ccs_shape, &commitment_W, &X, &rs, &vs)?; + + ccs_shape.is_satisfied_linearized::(&instance, &witness, &ck)?; + + Ok(()) + } + + #[test] + fn is_satisfied_linearized() -> Result<(), Error> { + let (a, b, c) = { + ( + to_field_sparse::(A), + to_field_sparse::(B), + to_field_sparse::(C), + ) + }; + + const NUM_CONSTRAINTS: usize = 4; + const NUM_WITNESS: usize = 4; + const NUM_PUBLIC: usize = 2; + + let mut rng = ark_std::test_rng(); + + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + + let X = to_field_elements::(&[1, 35]); + let W = to_field_elements::(&[3, 9, 27, 30]); + let witness = LCCSWitness::::new(&ccs_shape, &W)?; + + let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); + let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); + + let commitment_W = witness.commit::(&ck); + + let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; + let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); + + let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); + + let rows = NUM_CONSTRAINTS; + let columns = NUM_WITNESS + NUM_PUBLIC; + + let Mrs: Vec> = ark_std::cfg_iter!(ccs_shape.Ms) + .map(|M| matrix_to_mle(rows, columns, M).fix_variables(rs.as_slice())) + .collect(); + + let n = columns.next_power_of_two(); + let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); + + let vs: Vec = ark_std::cfg_iter!(Mrs) + .map(|M| { + (0..n) + .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .sum() + }) + .collect(); + + let instance = LCCSInstance::::new(&ccs_shape, &commitment_W, &X, &rs, &vs)?; + + ccs_shape.is_satisfied_linearized::(&instance, &witness, &ck)?; + + // Provide invalid witness. + let invalid_W = to_field_elements::(&[4, 9, 27, 30]); + let invalid_witness = LCCSWitness::::new(&ccs_shape, &invalid_W)?; + let commitment_invalid_W = invalid_witness.commit::(&ck); + + let instance = + LCCSInstance::::new(&ccs_shape, &commitment_invalid_W, &X, &rs, &vs)?; + assert_eq!( + ccs_shape.is_satisfied_linearized(&instance, &invalid_witness, &ck), + Err(Error::NotSatisfied) + ); + + // Provide invalid public input. + let invalid_X = to_field_elements::(&[1, 36]); + let instance = + LCCSInstance::::new(&ccs_shape, &commitment_W, &invalid_X, &rs, &vs)?; + assert_eq!( + ccs_shape.is_satisfied_linearized(&instance, &witness, &ck), + Err(Error::NotSatisfied) + ); + + Ok(()) + } }