mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-02-06 03:06:44 +01:00
In CycleFold we want to compute
$P_{folded} = P_0 + r ⋅ P_1 + r^2 ⋅ P_2 + r^3 ⋅ P_3 + ... + r^{n-2} ⋅ P_{n-2} + r^{n-1} ⋅ P_{n-1}$,
since the scalars follow the pattern r^i Youssef El Housni (@yelhousni)
proposed to update the approach of the CycleFold circuit to reduce the
number of constraints needed, by computing
$P_{folded} = (((P_{n-1} ⋅ r + P_{n-2}) ⋅ r + P_{n-3})... ) ⋅ r + P_0$.
By itself, this update reduces the number of constraints as the number
of points being folded in the CycleFold circuit grows. But it also has
impact at the HyperNova circuit, where it removes the need of using the
bit representations of the powers of the random value, substancially
reducing the amount of constraints used by the HyperNova
AugmentedFCircuit.
The number of constraints difference in the CycleFold circuit and in
the HyperNova's AugmentedFCircuit:
- CycleFold circuit:
| num points* | old | new | diff |
|-------------|-----------|-----------|----------|
| 2 | 1_354 | 1_354 | 0 |
| 3 | 2_683 | 2_554 | -129 |
| 4 | 4_012 | 3_754 | -258 |
| 8 | 9_328 | 8_554 | -744 |
| 16 | 19_960 | 18_154 | -1_806 |
| 32 | 41_224 | 37_354 | -3_870 |
| 64 | 83_752 | 75_754 | -7_998 |
| 128 | 168_808 | 152_554 | -16_254 |
| 1024 | 1_359_592 | 1_227_754 | -131_838 |
*num points: number of points being folded by the CycleFold circuit.
- HyperNova AugmentedFCircuit circuit
| folded instances* | old | new | diff |
|-------------------|---------|---------|----------|
| 5 | 90_285 | 80_150 | -10_135 |
| 10 | 144_894 | 117_655 | -27_239 |
| 20 | 249_839 | 192_949 | -56_890 |
| 40 | 463_078 | 344_448 | -118_630 |
*folded instances: folded instances per step, half of them being LCCCS
and the other half CCCS.
Co-authored-by: Youssef El Housni <youssef.housni21@gmail.com>
747 lines
26 KiB
Rust
747 lines
26 KiB
Rust
use ark_crypto_primitives::sponge::Absorb;
|
||
use ark_ec::{CurveGroup, Group};
|
||
use ark_ff::{BigInteger, Field, PrimeField};
|
||
use ark_poly::univariate::DensePolynomial;
|
||
use ark_poly::{DenseUVPolynomial, Polynomial};
|
||
use ark_std::{One, Zero};
|
||
|
||
use super::{
|
||
cccs::CCCS,
|
||
lcccs::LCCCS,
|
||
utils::{compute_c, compute_g, compute_sigmas_thetas},
|
||
Witness,
|
||
};
|
||
use crate::arith::ccs::CCS;
|
||
use crate::constants::NOVA_N_BITS_RO;
|
||
use crate::transcript::Transcript;
|
||
use crate::utils::sum_check::structs::{IOPProof as SumCheckProof, IOPProverMessage};
|
||
use crate::utils::sum_check::{IOPSumCheck, SumCheck};
|
||
use crate::utils::virtual_polynomial::VPAuxInfo;
|
||
use crate::Error;
|
||
|
||
use std::fmt::Debug;
|
||
use std::marker::PhantomData;
|
||
|
||
/// NIMFSProof defines a multifolding proof
|
||
#[derive(Clone, Debug)]
|
||
pub struct NIMFSProof<C: CurveGroup> {
|
||
pub sc_proof: SumCheckProof<C::ScalarField>,
|
||
pub sigmas_thetas: SigmasThetas<C::ScalarField>,
|
||
}
|
||
|
||
impl<C: CurveGroup> NIMFSProof<C> {
|
||
pub fn dummy(ccs: &CCS<C::ScalarField>, mu: usize, nu: usize) -> Self {
|
||
// use 'C::ScalarField::one()' instead of 'zero()' to enforce the NIMFSProof to have the
|
||
// same in-circuit representation to match the number of constraints of an actual proof.
|
||
NIMFSProof::<C> {
|
||
sc_proof: SumCheckProof::<C::ScalarField> {
|
||
point: vec![C::ScalarField::one(); ccs.s],
|
||
proofs: vec![
|
||
IOPProverMessage {
|
||
coeffs: vec![C::ScalarField::one(); ccs.t + 1]
|
||
};
|
||
ccs.s
|
||
],
|
||
},
|
||
sigmas_thetas: SigmasThetas(
|
||
vec![vec![C::ScalarField::one(); ccs.t]; mu],
|
||
vec![vec![C::ScalarField::one(); ccs.t]; nu],
|
||
),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Debug)]
|
||
pub struct SigmasThetas<F: PrimeField>(pub Vec<Vec<F>>, pub Vec<Vec<F>>);
|
||
|
||
#[derive(Debug)]
|
||
/// Implements the Non-Interactive Multi Folding Scheme described in section 5 of
|
||
/// [HyperNova](https://eprint.iacr.org/2023/573.pdf)
|
||
pub struct NIMFS<C: CurveGroup, T: Transcript<C::ScalarField>> {
|
||
pub _c: PhantomData<C>,
|
||
pub _t: PhantomData<T>,
|
||
}
|
||
|
||
impl<C: CurveGroup, T: Transcript<C::ScalarField>> NIMFS<C, T>
|
||
where
|
||
<C as Group>::ScalarField: Absorb,
|
||
C::BaseField: PrimeField,
|
||
{
|
||
pub fn fold(
|
||
lcccs: &[LCCCS<C>],
|
||
cccs: &[CCCS<C>],
|
||
sigmas_thetas: &SigmasThetas<C::ScalarField>,
|
||
r_x_prime: Vec<C::ScalarField>,
|
||
rho: C::ScalarField,
|
||
) -> LCCCS<C> {
|
||
let (sigmas, thetas) = (sigmas_thetas.0.clone(), sigmas_thetas.1.clone());
|
||
let mut C_folded = C::zero();
|
||
let mut u_folded = C::ScalarField::zero();
|
||
let mut x_folded: Vec<C::ScalarField> = vec![C::ScalarField::zero(); lcccs[0].x.len()];
|
||
let mut v_folded: Vec<C::ScalarField> = vec![C::ScalarField::zero(); sigmas[0].len()];
|
||
|
||
let mut rho_i = C::ScalarField::one();
|
||
for i in 0..(lcccs.len() + cccs.len()) {
|
||
let c: C;
|
||
let u: C::ScalarField;
|
||
let x: Vec<C::ScalarField>;
|
||
let v: Vec<C::ScalarField>;
|
||
if i < lcccs.len() {
|
||
c = lcccs[i].C;
|
||
u = lcccs[i].u;
|
||
x = lcccs[i].x.clone();
|
||
v = sigmas[i].clone();
|
||
} else {
|
||
c = cccs[i - lcccs.len()].C;
|
||
u = C::ScalarField::one();
|
||
x = cccs[i - lcccs.len()].x.clone();
|
||
v = thetas[i - lcccs.len()].clone();
|
||
}
|
||
|
||
C_folded += c.mul(rho_i);
|
||
u_folded += rho_i * u;
|
||
x_folded = x_folded
|
||
.iter()
|
||
.zip(
|
||
x.iter()
|
||
.map(|x_i| *x_i * rho_i)
|
||
.collect::<Vec<C::ScalarField>>(),
|
||
)
|
||
.map(|(a_i, b_i)| *a_i + b_i)
|
||
.collect();
|
||
|
||
v_folded = v_folded
|
||
.iter()
|
||
.zip(
|
||
v.iter()
|
||
.map(|x_i| *x_i * rho_i)
|
||
.collect::<Vec<C::ScalarField>>(),
|
||
)
|
||
.map(|(a_i, b_i)| *a_i + b_i)
|
||
.collect();
|
||
|
||
// compute the next power of rho
|
||
rho_i *= rho;
|
||
}
|
||
|
||
LCCCS::<C> {
|
||
C: C_folded,
|
||
u: u_folded,
|
||
x: x_folded,
|
||
r_x: r_x_prime,
|
||
v: v_folded,
|
||
}
|
||
}
|
||
|
||
pub fn fold_witness(
|
||
w_lcccs: &[Witness<C::ScalarField>],
|
||
w_cccs: &[Witness<C::ScalarField>],
|
||
rho: C::ScalarField,
|
||
) -> Witness<C::ScalarField> {
|
||
let mut w_folded: Vec<C::ScalarField> = vec![C::ScalarField::zero(); w_lcccs[0].w.len()];
|
||
let mut r_w_folded = C::ScalarField::zero();
|
||
|
||
let mut rho_i = C::ScalarField::one();
|
||
for i in 0..(w_lcccs.len() + w_cccs.len()) {
|
||
// let rho_i = rho.pow([i as u64]);
|
||
let w: Vec<C::ScalarField>;
|
||
let r_w: C::ScalarField;
|
||
|
||
if i < w_lcccs.len() {
|
||
w = w_lcccs[i].w.clone();
|
||
r_w = w_lcccs[i].r_w;
|
||
} else {
|
||
w = w_cccs[i - w_lcccs.len()].w.clone();
|
||
r_w = w_cccs[i - w_lcccs.len()].r_w;
|
||
}
|
||
|
||
w_folded = w_folded
|
||
.iter()
|
||
.zip(
|
||
w.iter()
|
||
.map(|x_i| *x_i * rho_i)
|
||
.collect::<Vec<C::ScalarField>>(),
|
||
)
|
||
.map(|(a_i, b_i)| *a_i + b_i)
|
||
.collect();
|
||
|
||
r_w_folded += rho_i * r_w;
|
||
|
||
// compute the next power of rho
|
||
rho_i *= rho;
|
||
}
|
||
Witness {
|
||
w: w_folded,
|
||
r_w: r_w_folded,
|
||
}
|
||
}
|
||
|
||
/// Performs the multifolding prover. Given μ LCCCS instances and ν CCS instances, fold them
|
||
/// into a single LCCCS instance. Since this is the prover, also fold their witness.
|
||
/// Returns the final folded LCCCS, the folded witness, and the multifolding proof, which
|
||
/// contains the sumcheck proof and the helper sumcheck claim sigmas and thetas.
|
||
#[allow(clippy::type_complexity)]
|
||
pub fn prove(
|
||
transcript: &mut impl Transcript<C::ScalarField>,
|
||
ccs: &CCS<C::ScalarField>,
|
||
running_instances: &[LCCCS<C>],
|
||
new_instances: &[CCCS<C>],
|
||
w_lcccs: &[Witness<C::ScalarField>],
|
||
w_cccs: &[Witness<C::ScalarField>],
|
||
) -> Result<
|
||
(
|
||
NIMFSProof<C>,
|
||
LCCCS<C>,
|
||
Witness<C::ScalarField>,
|
||
C::ScalarField, // rho
|
||
),
|
||
Error,
|
||
> {
|
||
// absorb instances to transcript
|
||
transcript.absorb(&running_instances);
|
||
transcript.absorb(&new_instances);
|
||
|
||
if running_instances.is_empty() {
|
||
return Err(Error::Empty);
|
||
}
|
||
if new_instances.is_empty() {
|
||
return Err(Error::Empty);
|
||
}
|
||
|
||
// construct the LCCCS z vector from the relaxation factor, public IO and witness
|
||
let mut z_lcccs = Vec::new();
|
||
for (i, running_instance) in running_instances.iter().enumerate() {
|
||
let z_1: Vec<C::ScalarField> = [
|
||
vec![running_instance.u],
|
||
running_instance.x.clone(),
|
||
w_lcccs[i].w.to_vec(),
|
||
]
|
||
.concat();
|
||
z_lcccs.push(z_1);
|
||
}
|
||
// construct the CCCS z vector from the public IO and witness
|
||
let mut z_cccs = Vec::new();
|
||
for (i, new_instance) in new_instances.iter().enumerate() {
|
||
let z_2: Vec<C::ScalarField> = [
|
||
vec![C::ScalarField::one()],
|
||
new_instance.x.clone(),
|
||
w_cccs[i].w.to_vec(),
|
||
]
|
||
.concat();
|
||
z_cccs.push(z_2);
|
||
}
|
||
|
||
// Step 1: Get some challenges
|
||
let gamma_scalar = C::ScalarField::from_le_bytes_mod_order(b"gamma");
|
||
let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta");
|
||
transcript.absorb(&gamma_scalar);
|
||
let gamma: C::ScalarField = transcript.get_challenge();
|
||
transcript.absorb(&beta_scalar);
|
||
let beta: Vec<C::ScalarField> = transcript.get_challenges(ccs.s);
|
||
|
||
// Compute g(x)
|
||
let g = compute_g(ccs, running_instances, &z_lcccs, &z_cccs, gamma, &beta)?;
|
||
|
||
// Step 3: Run the sumcheck prover
|
||
let sumcheck_proof = IOPSumCheck::<C::ScalarField, T>::prove(&g, transcript)
|
||
.map_err(|err| Error::SumCheckProveError(err.to_string()))?;
|
||
|
||
// Step 2: dig into the sumcheck and extract r_x_prime
|
||
let r_x_prime = sumcheck_proof.point.clone();
|
||
|
||
// Step 4: compute sigmas and thetas
|
||
let sigmas_thetas = compute_sigmas_thetas(ccs, &z_lcccs, &z_cccs, &r_x_prime)?;
|
||
|
||
// Step 6: Get the folding challenge
|
||
let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho");
|
||
transcript.absorb(&rho_scalar);
|
||
let rho_bits: Vec<bool> = transcript.get_challenge_nbits(NOVA_N_BITS_RO);
|
||
let rho: C::ScalarField =
|
||
C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();
|
||
|
||
// Step 7: Create the folded instance
|
||
let folded_lcccs = Self::fold(
|
||
running_instances,
|
||
new_instances,
|
||
&sigmas_thetas,
|
||
r_x_prime,
|
||
rho,
|
||
);
|
||
|
||
// Step 8: Fold the witnesses
|
||
let folded_witness = Self::fold_witness(w_lcccs, w_cccs, rho);
|
||
|
||
Ok((
|
||
NIMFSProof::<C> {
|
||
sc_proof: sumcheck_proof,
|
||
sigmas_thetas,
|
||
},
|
||
folded_lcccs,
|
||
folded_witness,
|
||
rho,
|
||
))
|
||
}
|
||
|
||
/// Performs the multifolding verifier. Given μ LCCCS instances and ν CCS instances, fold them
|
||
/// into a single LCCCS instance.
|
||
/// Returns the folded LCCCS instance.
|
||
pub fn verify(
|
||
transcript: &mut impl Transcript<C::ScalarField>,
|
||
ccs: &CCS<C::ScalarField>,
|
||
running_instances: &[LCCCS<C>],
|
||
new_instances: &[CCCS<C>],
|
||
proof: NIMFSProof<C>,
|
||
) -> Result<LCCCS<C>, Error> {
|
||
// absorb instances to transcript
|
||
transcript.absorb(&running_instances);
|
||
transcript.absorb(&new_instances);
|
||
|
||
if running_instances.is_empty() {
|
||
return Err(Error::Empty);
|
||
}
|
||
if new_instances.is_empty() {
|
||
return Err(Error::Empty);
|
||
}
|
||
|
||
// Step 1: Get some challenges
|
||
let gamma_scalar = C::ScalarField::from_le_bytes_mod_order(b"gamma");
|
||
transcript.absorb(&gamma_scalar);
|
||
let gamma: C::ScalarField = transcript.get_challenge();
|
||
|
||
let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta");
|
||
transcript.absorb(&beta_scalar);
|
||
let beta: Vec<C::ScalarField> = transcript.get_challenges(ccs.s);
|
||
|
||
let vp_aux_info = VPAuxInfo::<C::ScalarField> {
|
||
max_degree: ccs.d + 1,
|
||
num_variables: ccs.s,
|
||
phantom: PhantomData::<C::ScalarField>,
|
||
};
|
||
|
||
// Step 3: Start verifying the sumcheck
|
||
// First, compute the expected sumcheck sum: \sum gamma^j v_j
|
||
let mut sum_v_j_gamma = C::ScalarField::zero();
|
||
for (i, running_instance) in running_instances.iter().enumerate() {
|
||
for j in 0..running_instance.v.len() {
|
||
let gamma_j = gamma.pow([(i * ccs.t + j) as u64]);
|
||
sum_v_j_gamma += running_instance.v[j] * gamma_j;
|
||
}
|
||
}
|
||
|
||
// Verify the interactive part of the sumcheck
|
||
let sumcheck_subclaim = IOPSumCheck::<C::ScalarField, T>::verify(
|
||
sum_v_j_gamma,
|
||
&proof.sc_proof,
|
||
&vp_aux_info,
|
||
transcript,
|
||
)
|
||
.map_err(|err| Error::SumCheckVerifyError(err.to_string()))?;
|
||
|
||
// Step 2: Dig into the sumcheck claim and extract the randomness used
|
||
let r_x_prime = sumcheck_subclaim.point.clone();
|
||
|
||
// Step 5: Finish verifying sumcheck (verify the claim c)
|
||
let c = compute_c(
|
||
ccs,
|
||
&proof.sigmas_thetas,
|
||
gamma,
|
||
&beta,
|
||
&running_instances
|
||
.iter()
|
||
.map(|lcccs| lcccs.r_x.clone())
|
||
.collect(),
|
||
&r_x_prime,
|
||
)?;
|
||
|
||
// check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas
|
||
if c != sumcheck_subclaim.expected_evaluation {
|
||
return Err(Error::NotEqual);
|
||
}
|
||
|
||
// Sanity check: we can also compute g(r_x') from the proof last evaluation value, and
|
||
// should be equal to the previously obtained values.
|
||
let g_on_rxprime_from_sumcheck_last_eval = DensePolynomial::from_coefficients_slice(
|
||
&proof.sc_proof.proofs.last().ok_or(Error::Empty)?.coeffs,
|
||
)
|
||
.evaluate(r_x_prime.last().ok_or(Error::Empty)?);
|
||
if g_on_rxprime_from_sumcheck_last_eval != c {
|
||
return Err(Error::NotEqual);
|
||
}
|
||
if g_on_rxprime_from_sumcheck_last_eval != sumcheck_subclaim.expected_evaluation {
|
||
return Err(Error::NotEqual);
|
||
}
|
||
|
||
// Step 6: Get the folding challenge
|
||
let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho");
|
||
transcript.absorb(&rho_scalar);
|
||
let rho_bits: Vec<bool> = transcript.get_challenge_nbits(NOVA_N_BITS_RO);
|
||
let rho: C::ScalarField =
|
||
C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();
|
||
|
||
// Step 7: Compute the folded instance
|
||
Ok(Self::fold(
|
||
running_instances,
|
||
new_instances,
|
||
&proof.sigmas_thetas,
|
||
r_x_prime,
|
||
rho,
|
||
))
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
pub mod tests {
|
||
use super::*;
|
||
use crate::arith::{
|
||
ccs::tests::{get_test_ccs, get_test_z},
|
||
Arith,
|
||
};
|
||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||
use ark_crypto_primitives::sponge::poseidon::PoseidonSponge;
|
||
use ark_crypto_primitives::sponge::CryptographicSponge;
|
||
use ark_std::test_rng;
|
||
use ark_std::UniformRand;
|
||
|
||
use crate::commitment::{pedersen::Pedersen, CommitmentScheme};
|
||
use ark_pallas::{Fr, Projective};
|
||
|
||
#[test]
|
||
fn test_fold() {
|
||
let ccs = get_test_ccs();
|
||
let z1 = get_test_z::<Fr>(3);
|
||
let z2 = get_test_z::<Fr>(4);
|
||
ccs.check_relation(&z1).unwrap();
|
||
ccs.check_relation(&z2).unwrap();
|
||
|
||
let mut rng = test_rng();
|
||
let r_x_prime: Vec<Fr> = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect();
|
||
|
||
let sigmas_thetas =
|
||
compute_sigmas_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime).unwrap();
|
||
|
||
let (pedersen_params, _) =
|
||
Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
|
||
|
||
let (lcccs, w1) = ccs
|
||
.to_lcccs::<_, Projective, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z1)
|
||
.unwrap();
|
||
let (cccs, w2) = ccs
|
||
.to_cccs::<_, Projective, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z2)
|
||
.unwrap();
|
||
|
||
lcccs.check_relation(&ccs, &w1).unwrap();
|
||
cccs.check_relation(&ccs, &w2).unwrap();
|
||
|
||
let mut rng = test_rng();
|
||
let rho = Fr::rand(&mut rng);
|
||
|
||
let folded = NIMFS::<Projective, PoseidonSponge<Fr>>::fold(
|
||
&[lcccs],
|
||
&[cccs],
|
||
&sigmas_thetas,
|
||
r_x_prime,
|
||
rho,
|
||
);
|
||
|
||
let w_folded = NIMFS::<Projective, PoseidonSponge<Fr>>::fold_witness(&[w1], &[w2], rho);
|
||
|
||
// check lcccs relation
|
||
folded.check_relation(&ccs, &w_folded).unwrap();
|
||
}
|
||
|
||
/// Perform multifolding of an LCCCS instance with a CCCS instance (as described in the paper)
|
||
#[test]
|
||
pub fn test_basic_multifolding() {
|
||
let mut rng = test_rng();
|
||
|
||
// Create a basic CCS circuit
|
||
let ccs = get_test_ccs::<Fr>();
|
||
let (pedersen_params, _) =
|
||
Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
|
||
|
||
// Generate a satisfying witness
|
||
let z_1 = get_test_z(3);
|
||
// Generate another satisfying witness
|
||
let z_2 = get_test_z(4);
|
||
|
||
// Create the LCCCS instance out of z_1
|
||
let (running_instance, w1) = ccs
|
||
.to_lcccs::<_, _, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z_1)
|
||
.unwrap();
|
||
// Create the CCCS instance out of z_2
|
||
let (new_instance, w2) = ccs
|
||
.to_cccs::<_, _, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z_2)
|
||
.unwrap();
|
||
|
||
// Prover's transcript
|
||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||
let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||
transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init"));
|
||
|
||
// Run the prover side of the multifolding
|
||
let (proof, folded_lcccs, folded_witness, _) =
|
||
NIMFS::<Projective, PoseidonSponge<Fr>>::prove(
|
||
&mut transcript_p,
|
||
&ccs,
|
||
&[running_instance.clone()],
|
||
&[new_instance.clone()],
|
||
&[w1],
|
||
&[w2],
|
||
)
|
||
.unwrap();
|
||
|
||
// Verifier's transcript
|
||
let mut transcript_v: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||
transcript_v.absorb(&Fr::from_le_bytes_mod_order(b"init init"));
|
||
|
||
// Run the verifier side of the multifolding
|
||
let folded_lcccs_v = NIMFS::<Projective, PoseidonSponge<Fr>>::verify(
|
||
&mut transcript_v,
|
||
&ccs,
|
||
&[running_instance.clone()],
|
||
&[new_instance.clone()],
|
||
proof,
|
||
)
|
||
.unwrap();
|
||
assert_eq!(folded_lcccs, folded_lcccs_v);
|
||
|
||
// Check that the folded LCCCS instance is a valid instance with respect to the folded witness
|
||
folded_lcccs.check_relation(&ccs, &folded_witness).unwrap();
|
||
}
|
||
|
||
/// Perform multiple steps of multifolding of an LCCCS instance with a CCCS instance
|
||
#[test]
|
||
pub fn test_multifolding_two_instances_multiple_steps() {
|
||
let mut rng = test_rng();
|
||
|
||
let ccs = get_test_ccs::<Fr>();
|
||
|
||
let (pedersen_params, _) =
|
||
Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
|
||
|
||
// LCCCS witness
|
||
let z_1 = get_test_z(2);
|
||
let (mut running_instance, mut w1) = ccs
|
||
.to_lcccs::<_, _, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z_1)
|
||
.unwrap();
|
||
|
||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||
|
||
let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||
transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init"));
|
||
|
||
let mut transcript_v: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||
transcript_v.absorb(&Fr::from_le_bytes_mod_order(b"init init"));
|
||
|
||
let n: usize = 10;
|
||
for i in 3..n {
|
||
// CCS witness
|
||
let z_2 = get_test_z(i);
|
||
|
||
let (new_instance, w2) = ccs
|
||
.to_cccs::<_, _, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z_2)
|
||
.unwrap();
|
||
|
||
// run the prover side of the multifolding
|
||
let (proof, folded_lcccs, folded_witness, _) =
|
||
NIMFS::<Projective, PoseidonSponge<Fr>>::prove(
|
||
&mut transcript_p,
|
||
&ccs,
|
||
&[running_instance.clone()],
|
||
&[new_instance.clone()],
|
||
&[w1],
|
||
&[w2],
|
||
)
|
||
.unwrap();
|
||
|
||
// run the verifier side of the multifolding
|
||
let folded_lcccs_v = NIMFS::<Projective, PoseidonSponge<Fr>>::verify(
|
||
&mut transcript_v,
|
||
&ccs,
|
||
&[running_instance.clone()],
|
||
&[new_instance.clone()],
|
||
proof,
|
||
)
|
||
.unwrap();
|
||
assert_eq!(folded_lcccs, folded_lcccs_v);
|
||
|
||
// check that the folded instance with the folded witness holds the LCCCS relation
|
||
folded_lcccs.check_relation(&ccs, &folded_witness).unwrap();
|
||
|
||
running_instance = folded_lcccs;
|
||
w1 = folded_witness;
|
||
}
|
||
}
|
||
|
||
/// Test that generates mu>1 and nu>1 instances, and folds them in a single multifolding step.
|
||
#[test]
|
||
pub fn test_multifolding_mu_nu_instances() {
|
||
let mut rng = test_rng();
|
||
|
||
// Create a basic CCS circuit
|
||
let ccs = get_test_ccs::<Fr>();
|
||
let (pedersen_params, _) =
|
||
Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
|
||
|
||
let mu = 10;
|
||
let nu = 15;
|
||
|
||
// Generate a mu LCCCS & nu CCCS satisfying witness
|
||
let mut z_lcccs = Vec::new();
|
||
for i in 0..mu {
|
||
let z = get_test_z(i + 3);
|
||
z_lcccs.push(z);
|
||
}
|
||
let mut z_cccs = Vec::new();
|
||
for i in 0..nu {
|
||
let z = get_test_z(nu + i + 3);
|
||
z_cccs.push(z);
|
||
}
|
||
|
||
// Create the LCCCS instances out of z_lcccs
|
||
let mut lcccs_instances = Vec::new();
|
||
let mut w_lcccs = Vec::new();
|
||
for z_i in z_lcccs.iter() {
|
||
let (running_instance, w) = ccs
|
||
.to_lcccs::<_, _, Pedersen<Projective>, false>(&mut rng, &pedersen_params, z_i)
|
||
.unwrap();
|
||
lcccs_instances.push(running_instance);
|
||
w_lcccs.push(w);
|
||
}
|
||
// Create the CCCS instance out of z_cccs
|
||
let mut cccs_instances = Vec::new();
|
||
let mut w_cccs = Vec::new();
|
||
for z_i in z_cccs.iter() {
|
||
let (new_instance, w) = ccs
|
||
.to_cccs::<_, _, Pedersen<Projective>, false>(&mut rng, &pedersen_params, z_i)
|
||
.unwrap();
|
||
cccs_instances.push(new_instance);
|
||
w_cccs.push(w);
|
||
}
|
||
|
||
// Prover's transcript
|
||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||
let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||
transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init"));
|
||
|
||
// Run the prover side of the multifolding
|
||
let (proof, folded_lcccs, folded_witness, _) =
|
||
NIMFS::<Projective, PoseidonSponge<Fr>>::prove(
|
||
&mut transcript_p,
|
||
&ccs,
|
||
&lcccs_instances,
|
||
&cccs_instances,
|
||
&w_lcccs,
|
||
&w_cccs,
|
||
)
|
||
.unwrap();
|
||
|
||
// Verifier's transcript
|
||
let mut transcript_v: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||
transcript_v.absorb(&Fr::from_le_bytes_mod_order(b"init init"));
|
||
|
||
// Run the verifier side of the multifolding
|
||
let folded_lcccs_v = NIMFS::<Projective, PoseidonSponge<Fr>>::verify(
|
||
&mut transcript_v,
|
||
&ccs,
|
||
&lcccs_instances,
|
||
&cccs_instances,
|
||
proof,
|
||
)
|
||
.unwrap();
|
||
assert_eq!(folded_lcccs, folded_lcccs_v);
|
||
|
||
// Check that the folded LCCCS instance is a valid instance with respect to the folded witness
|
||
folded_lcccs.check_relation(&ccs, &folded_witness).unwrap();
|
||
}
|
||
|
||
/// Test that generates mu>1 and nu>1 instances, and folds them in a single multifolding step
|
||
/// and repeats the process doing multiple steps.
|
||
#[test]
|
||
pub fn test_multifolding_mu_nu_instances_multiple_steps() {
|
||
let mut rng = test_rng();
|
||
|
||
// Create a basic CCS circuit
|
||
let ccs = get_test_ccs::<Fr>();
|
||
let (pedersen_params, _) =
|
||
Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
|
||
|
||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||
// Prover's transcript
|
||
let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||
transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init"));
|
||
|
||
// Verifier's transcript
|
||
let mut transcript_v: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||
transcript_v.absorb(&Fr::from_le_bytes_mod_order(b"init init"));
|
||
|
||
let n_steps = 3;
|
||
|
||
// number of LCCCS & CCCS instances in each multifolding step
|
||
let mu = 10;
|
||
let nu = 15;
|
||
|
||
// Generate a mu LCCCS & nu CCCS satisfying witness, for each step
|
||
for step in 0..n_steps {
|
||
let mut z_lcccs = Vec::new();
|
||
for i in 0..mu {
|
||
let z = get_test_z(step + i + 3);
|
||
z_lcccs.push(z);
|
||
}
|
||
let mut z_cccs = Vec::new();
|
||
for i in 0..nu {
|
||
let z = get_test_z(nu + i + 3);
|
||
z_cccs.push(z);
|
||
}
|
||
|
||
// Create the LCCCS instances out of z_lcccs
|
||
let mut lcccs_instances = Vec::new();
|
||
let mut w_lcccs = Vec::new();
|
||
for z_i in z_lcccs.iter() {
|
||
let (running_instance, w) = ccs
|
||
.to_lcccs::<_, _, Pedersen<Projective>, false>(&mut rng, &pedersen_params, z_i)
|
||
.unwrap();
|
||
lcccs_instances.push(running_instance);
|
||
w_lcccs.push(w);
|
||
}
|
||
// Create the CCCS instance out of z_cccs
|
||
let mut cccs_instances = Vec::new();
|
||
let mut w_cccs = Vec::new();
|
||
for z_i in z_cccs.iter() {
|
||
let (new_instance, w) = ccs
|
||
.to_cccs::<_, _, Pedersen<Projective>, false>(&mut rng, &pedersen_params, z_i)
|
||
.unwrap();
|
||
cccs_instances.push(new_instance);
|
||
w_cccs.push(w);
|
||
}
|
||
|
||
// Run the prover side of the multifolding
|
||
let (proof, folded_lcccs, folded_witness, _) =
|
||
NIMFS::<Projective, PoseidonSponge<Fr>>::prove(
|
||
&mut transcript_p,
|
||
&ccs,
|
||
&lcccs_instances,
|
||
&cccs_instances,
|
||
&w_lcccs,
|
||
&w_cccs,
|
||
)
|
||
.unwrap();
|
||
|
||
// Run the verifier side of the multifolding
|
||
let folded_lcccs_v = NIMFS::<Projective, PoseidonSponge<Fr>>::verify(
|
||
&mut transcript_v,
|
||
&ccs,
|
||
&lcccs_instances,
|
||
&cccs_instances,
|
||
proof,
|
||
)
|
||
.unwrap();
|
||
|
||
assert_eq!(folded_lcccs, folded_lcccs_v);
|
||
|
||
// Check that the folded LCCCS instance is a valid instance with respect to the folded witness
|
||
folded_lcccs.check_relation(&ccs, &folded_witness).unwrap();
|
||
}
|
||
}
|
||
}
|