use ark_crypto_primitives::{
|
|
crh::{poseidon::CRH, CRHScheme},
|
|
sponge::{poseidon::PoseidonConfig, Absorb},
|
|
};
|
|
use ark_ec::{CurveGroup, Group};
|
|
use ark_ff::PrimeField;
|
|
use ark_poly::DenseMultilinearExtension;
|
|
use ark_poly::MultilinearExtension;
|
|
use ark_std::rand::Rng;
|
|
use ark_std::Zero;
|
|
|
|
use super::Witness;
|
|
use crate::arith::ccs::CCS;
|
|
use crate::commitment::CommitmentScheme;
|
|
use crate::folding::circuits::nonnative::affine::nonnative_affine_to_field_elements;
|
|
use crate::utils::mle::dense_vec_to_dense_mle;
|
|
use crate::utils::vec::mat_vec_mul;
|
|
use crate::Error;
|
|
|
|
/// Linearized Committed CCS instance
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
pub struct LCCCS<C: CurveGroup> {
|
|
// Commitment to witness
|
|
pub C: C,
|
|
// Relaxation factor of z for folded LCCCS
|
|
pub u: C::ScalarField,
|
|
// Public input/output
|
|
pub x: Vec<C::ScalarField>,
|
|
// Random evaluation point for the v_i
|
|
pub r_x: Vec<C::ScalarField>,
|
|
// Vector of v_i
|
|
pub v: Vec<C::ScalarField>,
|
|
}
|
|
|
|
impl<F: PrimeField> CCS<F> {
|
|
pub fn to_lcccs<R: Rng, C: CurveGroup, CS: CommitmentScheme<C>>(
|
|
&self,
|
|
rng: &mut R,
|
|
cs_params: &CS::ProverParams,
|
|
z: &[C::ScalarField],
|
|
) -> Result<(LCCCS<C>, Witness<C::ScalarField>), Error>
|
|
where
|
|
// enforce that CCS's F is the C::ScalarField
|
|
C: CurveGroup<ScalarField = F>,
|
|
{
|
|
let w: Vec<C::ScalarField> = z[(1 + self.l)..].to_vec();
|
|
// if the commitment scheme is set to be hiding, set the random blinding parameter
|
|
let r_w = if CS::is_hiding() {
|
|
C::ScalarField::rand(rng)
|
|
} else {
|
|
C::ScalarField::zero()
|
|
};
|
|
let C = CS::commit(cs_params, &w, &r_w)?;
|
|
|
|
let r_x: Vec<C::ScalarField> = (0..self.s).map(|_| C::ScalarField::rand(rng)).collect();
|
|
|
|
let Mzs: Vec<DenseMultilinearExtension<F>> = self
|
|
.M
|
|
.iter()
|
|
.map(|M_j| Ok(dense_vec_to_dense_mle(self.s, &mat_vec_mul(M_j, z)?)))
|
|
.collect::<Result<_, Error>>()?;
|
|
|
|
// compute v_j
|
|
let v: Vec<F> = Mzs
|
|
.iter()
|
|
.map(|Mz| Mz.evaluate(&r_x).ok_or(Error::EvaluationFail))
|
|
.collect::<Result<_, Error>>()?;
|
|
|
|
Ok((
|
|
LCCCS::<C> {
|
|
C,
|
|
u: C::ScalarField::one(),
|
|
x: z[1..(1 + self.l)].to_vec(),
|
|
r_x,
|
|
v,
|
|
},
|
|
Witness::<C::ScalarField> { w, r_w },
|
|
))
|
|
}
|
|
}
|
|
|
|
impl<C: CurveGroup> LCCCS<C> {
|
|
pub fn dummy(l: usize, t: usize, s: usize) -> LCCCS<C>
|
|
where
|
|
C::ScalarField: PrimeField,
|
|
{
|
|
LCCCS::<C> {
|
|
C: C::zero(),
|
|
u: C::ScalarField::zero(),
|
|
x: vec![C::ScalarField::zero(); l],
|
|
r_x: vec![C::ScalarField::zero(); s],
|
|
v: vec![C::ScalarField::zero(); t],
|
|
}
|
|
}
|
|
|
|
/// Perform the check of the LCCCS instance described at section 4.2,
|
|
/// notice that this method does not check the commitment correctness
|
|
pub fn check_relation(
|
|
&self,
|
|
ccs: &CCS<C::ScalarField>,
|
|
w: &Witness<C::ScalarField>,
|
|
) -> Result<(), Error> {
|
|
// check CCS relation
|
|
let z: Vec<C::ScalarField> = [vec![self.u], self.x.clone(), w.w.to_vec()].concat();
|
|
|
|
let computed_v: Vec<C::ScalarField> = ccs
|
|
.M
|
|
.iter()
|
|
.map(|M_j| {
|
|
let Mz_mle = dense_vec_to_dense_mle(ccs.s, &mat_vec_mul(M_j, &z)?);
|
|
Mz_mle.evaluate(&self.r_x).ok_or(Error::EvaluationFail)
|
|
})
|
|
.collect::<Result<_, Error>>()?;
|
|
if computed_v != self.v {
|
|
return Err(Error::NotSatisfied);
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<C: CurveGroup> LCCCS<C>
|
|
where
|
|
<C as Group>::ScalarField: Absorb,
|
|
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
|
{
|
|
/// [`LCCCS`].hash implements the committed instance hash compatible with the gadget
|
|
/// implemented in nova/circuits.rs::CommittedInstanceVar.hash.
|
|
/// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U_i` is the LCCCS.
|
|
pub fn hash(
|
|
&self,
|
|
poseidon_config: &PoseidonConfig<C::ScalarField>,
|
|
pp_hash: C::ScalarField,
|
|
i: C::ScalarField,
|
|
z_0: Vec<C::ScalarField>,
|
|
z_i: Vec<C::ScalarField>,
|
|
) -> Result<C::ScalarField, Error> {
|
|
let (C_x, C_y) = nonnative_affine_to_field_elements::<C>(self.C)?;
|
|
|
|
CRH::<C::ScalarField>::evaluate(
|
|
poseidon_config,
|
|
vec![
|
|
vec![pp_hash, i],
|
|
z_0,
|
|
z_i,
|
|
C_x,
|
|
C_y,
|
|
vec![self.u],
|
|
self.x.clone(),
|
|
self.r_x.clone(),
|
|
self.v.clone(),
|
|
]
|
|
.concat(),
|
|
)
|
|
.map_err(|e| Error::Other(e.to_string()))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod tests {
|
|
use ark_pallas::{Fr, Projective};
|
|
use ark_std::test_rng;
|
|
use ark_std::One;
|
|
use ark_std::UniformRand;
|
|
use ark_std::Zero;
|
|
use std::sync::Arc;
|
|
|
|
use super::*;
|
|
use crate::arith::{
|
|
ccs::tests::{get_test_ccs, get_test_z},
|
|
r1cs::R1CS,
|
|
Arith,
|
|
};
|
|
use crate::commitment::pedersen::Pedersen;
|
|
use crate::utils::hypercube::BooleanHypercube;
|
|
use crate::utils::virtual_polynomial::{build_eq_x_r_vec, VirtualPolynomial};
|
|
|
|
// method for testing
|
|
pub fn compute_Ls<C: CurveGroup>(
|
|
ccs: &CCS<C::ScalarField>,
|
|
lcccs: &LCCCS<C>,
|
|
z: &[C::ScalarField],
|
|
) -> Vec<VirtualPolynomial<C::ScalarField>> {
|
|
let eq_rx = build_eq_x_r_vec(&lcccs.r_x).unwrap();
|
|
let eq_rx_mle = dense_vec_to_dense_mle(ccs.s, &eq_rx);
|
|
|
|
let mut Ls = Vec::with_capacity(ccs.t);
|
|
for M_j in ccs.M.iter() {
|
|
let mut L = VirtualPolynomial::<C::ScalarField>::new(ccs.s);
|
|
let mut Mz = vec![dense_vec_to_dense_mle(ccs.s, &mat_vec_mul(M_j, z).unwrap())];
|
|
Mz.push(eq_rx_mle.clone());
|
|
L.add_mle_list(
|
|
Mz.iter().map(|v| Arc::new(v.clone())),
|
|
C::ScalarField::one(),
|
|
)
|
|
.unwrap();
|
|
Ls.push(L);
|
|
}
|
|
Ls
|
|
}
|
|
|
|
#[test]
|
|
/// Test linearized CCCS v_j against the L_j(x)
|
|
fn test_lcccs_v_j() {
|
|
let mut rng = test_rng();
|
|
|
|
let n_rows = 2_u32.pow(5) as usize;
|
|
let n_cols = 2_u32.pow(5) as usize;
|
|
let r1cs = R1CS::<Fr>::rand(&mut rng, n_rows, n_cols);
|
|
let ccs = CCS::from_r1cs(r1cs);
|
|
let z: Vec<Fr> = (0..n_cols).map(|_| Fr::rand(&mut rng)).collect();
|
|
|
|
let (pedersen_params, _) =
|
|
Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
|
|
|
|
let (lcccs, _) = ccs
|
|
.to_lcccs::<_, Projective, Pedersen<Projective>>(&mut rng, &pedersen_params, &z)
|
|
.unwrap();
|
|
// with our test vector coming from R1CS, v should have length 3
|
|
assert_eq!(lcccs.v.len(), 3);
|
|
|
|
let vec_L_j_x = compute_Ls(&ccs, &lcccs, &z);
|
|
assert_eq!(vec_L_j_x.len(), lcccs.v.len());
|
|
|
|
for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) {
|
|
let sum_L_j_x = BooleanHypercube::new(ccs.s)
|
|
.map(|y| L_j_x.evaluate(&y).unwrap())
|
|
.fold(Fr::zero(), |acc, result| acc + result);
|
|
assert_eq!(v_i, sum_L_j_x);
|
|
}
|
|
}
|
|
|
|
/// Given a bad z, check that the v_j should not match with the L_j(x)
|
|
#[test]
|
|
fn test_bad_v_j() {
|
|
let mut rng = test_rng();
|
|
|
|
let ccs = get_test_ccs();
|
|
let z = get_test_z(3);
|
|
ccs.check_relation(&z.clone()).unwrap();
|
|
|
|
// Mutate z so that the relation does not hold
|
|
let mut bad_z = z.clone();
|
|
bad_z[3] = Fr::zero();
|
|
assert!(ccs.check_relation(&bad_z.clone()).is_err());
|
|
|
|
let (pedersen_params, _) =
|
|
Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
|
|
// Compute v_j with the right z
|
|
let (lcccs, _) = ccs
|
|
.to_lcccs::<_, Projective, Pedersen<Projective>>(&mut rng, &pedersen_params, &z)
|
|
.unwrap();
|
|
// with our test vector coming from R1CS, v should have length 3
|
|
assert_eq!(lcccs.v.len(), 3);
|
|
|
|
// Bad compute L_j(x) with the bad z
|
|
let vec_L_j_x = compute_Ls(&ccs, &lcccs, &bad_z);
|
|
assert_eq!(vec_L_j_x.len(), lcccs.v.len());
|
|
|
|
// Make sure that the LCCCS is not satisfied given these L_j(x)
|
|
// i.e. summing L_j(x) over the hypercube should not give v_j for all j
|
|
let mut satisfied = true;
|
|
for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) {
|
|
let sum_L_j_x = BooleanHypercube::new(ccs.s)
|
|
.map(|y| L_j_x.evaluate(&y).unwrap())
|
|
.fold(Fr::zero(), |acc, result| acc + result);
|
|
if v_i != sum_L_j_x {
|
|
satisfied = false;
|
|
}
|
|
}
|
|
assert!(!satisfied);
|
|
}
|
|
}
|