diff --git a/folding-schemes/src/arith/r1cs/mod.rs b/folding-schemes/src/arith/r1cs/mod.rs index 683d0888..7c899d48 100644 --- a/folding-schemes/src/arith/r1cs/mod.rs +++ b/folding-schemes/src/arith/r1cs/mod.rs @@ -5,6 +5,7 @@ use ark_std::rand::Rng; use super::ccs::CCS; use super::{Arith, ArithSerializer}; +use crate::frontend::alloc::{Committed, ConstraintSystemStatistics, Multiplicity, Query}; use crate::utils::vec::{ hadamard, is_zero_vec, mat_vec_mul, vec_scalar_mul, vec_sub, SparseMatrix, }; @@ -15,6 +16,9 @@ pub mod circuits; #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct R1CS { pub l: usize, // io len + pub n_queries: usize, + pub n_multiplicities: usize, + pub n_committed: usize, pub A: SparseMatrix, pub B: SparseMatrix, pub C: SparseMatrix, @@ -70,6 +74,9 @@ impl R1CS { pub fn empty() -> Self { R1CS { l: 0, + n_queries: 0, + n_multiplicities: 0, + n_committed: 0, A: SparseMatrix::empty(), B: SparseMatrix::empty(), C: SparseMatrix::empty(), @@ -78,6 +85,9 @@ impl R1CS { pub fn rand(rng: &mut R, n_rows: usize, n_cols: usize) -> Self { Self { l: 1, + n_queries: 0, + n_multiplicities: 0, + n_committed: 0, A: SparseMatrix::rand(rng, n_rows, n_cols), B: SparseMatrix::rand(rng, n_rows, n_cols), C: SparseMatrix::rand(rng, n_rows, n_cols), @@ -114,6 +124,9 @@ impl From> for R1CS { fn from(ccs: CCS) -> Self { R1CS:: { l: ccs.l, + n_queries: 0, + n_multiplicities: 0, + n_committed: 0, A: ccs.M[0].clone(), B: ccs.M[1].clone(), C: ccs.M[2].clone(), @@ -153,6 +166,9 @@ pub fn extract_r1cs(cs: &ConstraintSystem) -> Result, Ok(R1CS:: { l: cs.num_instance_variables - 1, // -1 to subtract the first '1' + n_queries: cs.num_variables_of_type::(), + n_multiplicities: cs.num_variables_of_type::(), + n_committed: cs.num_variables_of_type::(), A, B, C, @@ -200,7 +216,15 @@ pub mod tests { vec![0, 0, 1, 0, 0, 0], ]); - R1CS:: { l: 1, A, B, C } + R1CS:: { + l: 1, + n_queries: 0, + n_multiplicities: 0, + n_committed: 0, + A, + B, + C, + } } pub fn get_test_z(input: usize) -> Vec { diff --git a/folding-schemes/src/commitment/mod.rs b/folding-schemes/src/commitment/mod.rs index 0c9301d6..4a91a95d 100644 --- a/folding-schemes/src/commitment/mod.rs +++ b/folding-schemes/src/commitment/mod.rs @@ -1,6 +1,6 @@ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::fmt::Debug; -use ark_std::rand::RngCore; +use ark_std::{rand::RngCore, UniformRand, Zero}; use crate::transcript::Transcript; use crate::{Curve, Error}; @@ -31,6 +31,20 @@ pub trait CommitmentScheme: Clone + Debug { blind: &C::ScalarField, ) -> Result; + fn commit_with_rng( + params: &Self::ProverParams, + v: &[C::ScalarField], + mut rng: impl RngCore, + ) -> Result<(C, C::ScalarField), Error> { + let blind = if H { + C::ScalarField::rand(&mut rng) + } else { + C::ScalarField::zero() + }; + let cm = Self::commit(params, v, &blind)?; + Ok((cm, blind)) + } + fn prove( params: &Self::ProverParams, transcript: &mut impl Transcript, diff --git a/folding-schemes/src/folding/circuits/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs index d0bc623a..496cb439 100644 --- a/folding-schemes/src/folding/circuits/cyclefold.rs +++ b/folding-schemes/src/folding/circuits/cyclefold.rs @@ -15,12 +15,12 @@ use ark_r1cs_std::{ use ark_relations::r1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, }; -use ark_std::fmt::Debug; -use ark_std::rand::RngCore; -use ark_std::Zero; -use core::{borrow::Borrow, marker::PhantomData}; +use ark_std::{borrow::Borrow, fmt::Debug, marker::PhantomData, rand::RngCore, One}; -use super::{nonnative::uint::NonNativeUintVar, CF1, CF2}; +use super::{ + nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, + CF1, CF2, +}; use crate::commitment::CommitmentScheme; use crate::constants::NOVA_N_BITS_RO; use crate::folding::nova::nifs::{nova::NIFS, NIFSTrait}; @@ -65,6 +65,7 @@ pub struct CycleFoldCommittedInstanceVar { pub cmW: C::Var, pub x: Vec>>, } + impl AllocVar, CF2> for CycleFoldCommittedInstanceVar { @@ -129,17 +130,45 @@ impl AbsorbNonNativeGadget for CycleFoldCommittedInstanc } } +impl CycleFoldCommittedInstanceVar { + /// Creates a new `CycleFoldCommittedInstanceVar` from the given components. + pub fn new_incoming_from_components>( + cmW: C2::Var, + r_bits: &[Boolean>], + points: Vec>, + ) -> Result { + // Construct the public inputs `x` from `r_bits` and `points`. + // Note that the underlying field can only safely store + // `CF1::::MODULUS_BIT_SIZE - 1` bits, but `r_bits` may be longer + // than that. + // Thus, we need to chunk `r_bits` into pieces and convert each piece + // to a `NonNativeUintVar`. + let x = r_bits + .chunks(CF1::::MODULUS_BIT_SIZE as usize - 1) + .map(|bits| { + let mut bits = bits.to_vec(); + bits.resize(CF1::::MODULUS_BIT_SIZE as usize, Boolean::FALSE); + NonNativeUintVar::from(&bits) + }) + .chain(points.into_iter().flat_map(|p| [p.x, p.y])) + .collect::>(); + Ok(Self { + // `cmE` is always zero for incoming instances + cmE: C2::Var::zero(), + // `u` is always one for incoming instances + u: NonNativeUintVar::new_constant(ConstraintSystemRef::None, CF1::::one())?, + cmW, + x, + }) + } +} + impl CycleFoldCommittedInstance { /// hash_cyclefold implements the committed instance hash compatible with the /// in-circuit implementation `CycleFoldCommittedInstanceVar::hash`. /// Returns `H(U_i)`, where `U_i` is a `CycleFoldCommittedInstance`. - pub fn hash_cyclefold>( - &self, - sponge: &T, - pp_hash: C::BaseField, // public params hash - ) -> C::BaseField { + pub fn hash_cyclefold>(&self, sponge: &T) -> C::BaseField { let mut sponge = sponge.clone(); - sponge.absorb(&pp_hash); sponge.absorb_nonnative(self); sponge.squeeze_field_elements(1)[0] } @@ -157,11 +186,9 @@ impl CycleFoldCommittedInstanceVar { pub fn hash, S>>( &self, sponge: &T, - pp_hash: FpVar>, // public params hash ) -> Result<(FpVar>, Vec>>), SynthesisError> { let mut sponge = sponge.clone(); let U_vec = self.to_native_sponge_field_elements()?; - sponge.absorb(&pp_hash)?; sponge.absorb(&U_vec)?; Ok(( // `unwrap` is safe because the sponge is guaranteed to return a single element @@ -291,14 +318,12 @@ pub struct CycleFoldChallengeGadget { impl CycleFoldChallengeGadget { pub fn get_challenge_native>( transcript: &mut T, - pp_hash: C::BaseField, // public params hash - U_i: CycleFoldCommittedInstance, - u_i: CycleFoldCommittedInstance, + U_i: &CycleFoldCommittedInstance, + u_i: &CycleFoldCommittedInstance, cmT: C, ) -> Vec { - transcript.absorb(&pp_hash); - transcript.absorb_nonnative(&U_i); - transcript.absorb_nonnative(&u_i); + transcript.absorb_nonnative(U_i); + transcript.absorb_nonnative(u_i); transcript.absorb_point(&cmT); transcript.squeeze_bits(NOVA_N_BITS_RO) } @@ -306,144 +331,331 @@ impl CycleFoldChallengeGadget { // compatible with the native get_challenge_native pub fn get_challenge_gadget>( transcript: &mut T, - pp_hash: FpVar, // public params hash - U_i_vec: Vec>, - u_i: CycleFoldCommittedInstanceVar, - cmT: C::Var, + U_i_vec: &[FpVar], + u_i: &CycleFoldCommittedInstanceVar, + cmT: &C::Var, ) -> Result>, SynthesisError> { - transcript.absorb(&pp_hash)?; transcript.absorb(&U_i_vec)?; - transcript.absorb_nonnative(&u_i)?; - transcript.absorb_point(&cmT)?; + transcript.absorb_nonnative(u_i)?; + transcript.absorb_point(cmT)?; transcript.squeeze_bits(NOVA_N_BITS_RO) } } -/// `CycleFoldConfig` allows us to customize the behavior of CycleFold circuit -/// according to the folding scheme we are working with. -pub trait CycleFoldConfig { +/// [`CycleFoldConfig`] controls the behavior of [`CycleFoldCircuit`]. +/// +/// Looking ahead, the circuit computes the random linear combination of points, +/// which is essentially done by iteratively computing `P = (P + p_i) * r_i`, +/// where `P` is the folded point, `p_i` is the input point, and `r_i` is the +/// randomness. +pub trait CycleFoldConfig: Sized + Default { /// `N_INPUT_POINTS` specifies the number of input points that are folded in /// [`CycleFoldCircuit`] via random linear combinations. const N_INPUT_POINTS: usize; - /// `RANDOMNESS_BIT_LENGTH` is the (maximum) bit length of randomness `r`. + /// `N_UNIQUE_RANDOMNESSES` specifies the number of *unique* randomnesses + /// allocated in [`CycleFoldCircuit`]. Although the linear combination in + /// general consists of multiple randomnesses, some folding schemes (such as + /// Nova and HyperNova) only need a single one. Thus, by setting this value, + /// the circuit can learn how many randomnesses are used and how long the + /// public inputs vector should be. + const N_UNIQUE_RANDOMNESSES: usize; + /// `RANDOMNESS_BIT_LENGTH` is the maximum bit length of a randomness `r_i`. const RANDOMNESS_BIT_LENGTH: usize; /// `FIELD_CAPACITY` is the maximum number of bits that can be stored in a /// field element. /// - /// E.g., given a randomness `r` with `RANDOMNESS_BIT_LENGTH` bits, we need - /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY` field elements to represent `r` - /// compactly in-circuit. - const FIELD_CAPACITY: usize = CF2::::MODULUS_BIT_SIZE as usize - 1; - - /// Public inputs length for the CycleFoldCircuit. - /// * For Nova this is: `|[r, p1.x,y, p2.x,y, p3.x,y]|` - /// * In general, `|[r, (p_i.x,y)*n_points, p_folded.x,y]|`. + /// By default, `FIELD_CAPACITY` is set to `MODULUS_BIT_SIZE - 1`. /// - /// Thus, `IO_LEN` is: - /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY + 2 * N_INPUT_POINTS + 2` + /// Given a randomness `r_i` with `RANDOMNESS_BIT_LENGTH` bits, we need + /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY` field elements to represent it + /// *compactly* in-circuit. + const FIELD_CAPACITY: usize = CF2::::MODULUS_BIT_SIZE as usize - 1; + + /// Public inputs length for the [`CycleFoldCircuit`], which depends on the + /// above constants defined by the concrete folding scheme. For example: + /// * In Nova, this is `|r| + |p_1| + |p_2| + |P|` + /// * In HyperNova, this is `|r| + |p_i| * n_points + |P|`. + /// * In ProtoGalaxy, this is `|[..., r_i, ...]| + |p_i| * n_points + |P|`. + /// + /// As explained above, `|r|` (i.e., the length of a single randomness) is + /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY`. + /// When there are multiple randomnesses, the length of `|[..., r_i, ...]|` + /// is `RANDOMNESS_BIT_LENGTH * N_UNIQUE_RANDOMNESSES / FIELD_CAPACITY`, as + /// the bits of all randomnesses are concatenated before being packed into + /// field elements. + /// The length of a point `p_i` when treated as public inputs is 2, as we + /// only need the `x` and `y` coordinates of the point. + /// + /// Thus, `IO_LEN` is `RANDOMNESS_BIT_LENGTH * N_UNIQUE_RANDOMNESSES / FIELD_CAPACITY + 2 * (N_INPUT_POINTS + 1)`. const IO_LEN: usize = { - Self::RANDOMNESS_BIT_LENGTH.div_ceil(Self::FIELD_CAPACITY) + 2 * Self::N_INPUT_POINTS + 2 + (Self::RANDOMNESS_BIT_LENGTH * Self::N_UNIQUE_RANDOMNESSES).div_ceil(Self::FIELD_CAPACITY) + + 2 * (Self::N_INPUT_POINTS + 1) }; - type C: Curve; + /// `alloc_points` allocates the points that are going to be folded in the + /// [`CycleFoldCircuit`] via random linear combinations. + /// + /// The implementation must allocate the points as *witness* variables (i.e. + /// by calling [`AllocVar::new_witness`]) first, then mark them as public + /// inputs by calling [`CycleFoldConfig::mark_point_as_public`], and finally + /// return the allocated witness variables. + /// + /// While it is possible to allocate the points as public inputs directly, + /// we do not use this approach because this will create a longer vector of + /// public inputs, which is not ideal for the augmented step circuit on the + /// primary curve. + fn alloc_points(&self, cs: ConstraintSystemRef>) -> Result, SynthesisError>; + + /// `alloc_randomnesses` allocates the randomnesses used as coefficients of + /// the random linear combinations in the `CycleFoldCircuit`. + /// + /// The implementation must allocate the randomnesses as *witness* variables + /// (i.e. by calling [`AllocVar::new_witness`]) first, then mark them as + /// public inputs by calling [`CycleFoldConfig::mark_point_as_public`], and + /// finally return the allocated witness variables. + /// + /// See [`CycleFoldConfig::alloc_points`] for the reason why they need to be + /// allocated as witness variables first and converted to public later. + /// + /// In addition, because the circuit computes `P = (P + p_i) * r_i` for each + /// `i` from `N_INPUT_POINTS - 1` down to `0`, the actual linear combination + /// is `P = r_0 * p_0 + (r_0 r_1) * p_1 + (r_0 r_1 r_2) * p_2 + ...`. Thus, + /// to compute `P = R_0 p_0 + R_1 p_1 + R_2 p_2 + ...`, the implementation + /// should return `r_0 = R_0, r_1 = R_1 / R_0, ..., r_i = R_i / R_{i - 1}`. + /// A special case is `R_i = R^i`, where the allocated randomnesses become + /// `r_0 = 1, r_1 = r_2 = ... = R`. + fn alloc_randomnesses( + &self, + cs: ConstraintSystemRef>, + ) -> Result>>>, SynthesisError>; + + /// `mark_point_as_public` marks a point as public. + /// + /// The final vector of public inputs is shorter than the result of calling + /// [`AllocVar::new_input`], because we only need the x and y coordinates of + /// the point, but the `infinity` flag is not necessary. + fn mark_point_as_public(point: &C::Var) -> Result<(), SynthesisError> { + for x in &point.to_constraint_field()?[..2] { + // This line "converts" `x` from a witness to a public input. + // Instead of directly modifying the constraint system, we explicitly + // allocate a public input and enforce that its value is indeed `x`. + // While comparing `x` with itself seems redundant, this is necessary + // because: + // - `.value()` allows an honest prover to extract public inputs without + // computing them outside the circuit. + // - `.enforce_equal()` prevents a malicious prover from claiming wrong + // public inputs that are not the honest `x` computed in-circuit. + FpVar::new_input(x.cs().clone(), || x.value())?.enforce_equal(x)?; + } + Ok(()) + } + + /// `mark_randomness_as_public` marks randomness as public. + /// + /// The final vector of public inputs is shorter than the result of calling + /// [`AllocVar::new_input`], because we pack the bits of randomness into + /// a compact field elements. + fn mark_randomness_as_public(r: &[Boolean>]) -> Result<(), SynthesisError> { + for bits in r.chunks(Self::FIELD_CAPACITY) { + let x = Boolean::le_bits_to_fp(bits)?; + FpVar::new_input(x.cs().clone(), || x.value())?.enforce_equal(&x)?; + } + Ok(()) + } + + /// `build_circuit` creates a new [`CycleFoldCircuit`] with `self` as the + /// configuration. + fn build_circuit(self) -> CycleFoldCircuit { + CycleFoldCircuit { + _c: PhantomData, + cfg: self, + } + } } -/// CycleFoldCircuit contains the constraints that check the correct fold of the committed -/// instances from Curve1. Namely, it checks the random linear combinations of the elliptic curve -/// (Curve1) points of u_i, U_i leading to U_{i+1} #[derive(Debug, Clone)] -pub struct CycleFoldCircuit { - /// r_bits is the bit representation of the r whose powers are used in the - /// random-linear-combination inside the CycleFoldCircuit - pub r_bits: Option>, - /// points to be folded in the CycleFoldCircuit - pub points: Option>, +pub struct CycleFoldCircuit> { + _c: PhantomData, + cfg: CFG, } -impl CycleFoldCircuit { - /// n_points indicates the number of points being folded in the CycleFoldCircuit - pub fn empty() -> Self { - Self { - r_bits: None, - points: None, - } +impl> Default for CycleFoldCircuit { + fn default() -> Self { + CFG::default().build_circuit() } } -impl ConstraintSynthesizer> for CycleFoldCircuit { - fn generate_constraints( - self, - cs: ConstraintSystemRef>, - ) -> Result<(), SynthesisError> { - let r_bits = Vec::>>::new_witness(cs.clone(), || { - Ok(self - .r_bits - .unwrap_or(vec![false; CFG::RANDOMNESS_BIT_LENGTH])) - })?; - let points = Vec::<::Var>::new_witness(cs.clone(), || { - Ok(self - .points - .unwrap_or(vec![CFG::C::zero(); CFG::N_INPUT_POINTS])) - })?; +impl> ConstraintSynthesizer> for CycleFoldCircuit { + fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { + let rs = self.cfg.alloc_randomnesses(cs.clone())?; + let points = self.cfg.alloc_points(cs.clone())?; #[cfg(test)] { assert_eq!(CFG::N_INPUT_POINTS, points.len()); - assert_eq!(CFG::RANDOMNESS_BIT_LENGTH, r_bits.len()); + assert_eq!(CFG::N_INPUT_POINTS, rs.len()); + for r in &rs { + assert_eq!(CFG::RANDOMNESS_BIT_LENGTH, r.len()); + } + } + + // A slightly optimized version of `scalar_mul_le`. + fn point_mul( + point: &C::Var, + r: &[Boolean>], + ) -> Result { + if r.is_constant() { + let r = CF1::::from( as PrimeField>::BigInt::from_bits_le(&r.value()?)); + if r.is_one() { + return Ok(point.clone()); + } + } + point.scalar_mul_le(r.iter()) } - // Fold the original points of the instances natively in CycleFold. - // In Nova, - // - for the cmW we're computing: U_i1.cmW = U_i.cmW + r * u_i.cmW - // - for the cmE we're computing: U_i1.cmE = U_i.cmE + r * cmT + r^2 * u_i.cmE, where u_i.cmE - // is assumed to be 0, so, U_i1.cmE = U_i.cmE + r * cmT - // 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} - // so in order to do it more efficiently (less constraints) we do - // P_folded = (((P_{n-1} * r + P_{n-2}) * r + P_{n-3})... ) * r + P_0 - let mut p_folded = points[CFG::N_INPUT_POINTS - 1].clone(); + // Given a vector of points (over the primary curve) that are obtained + // from the instances of the folding scheme, we fold them *natively* in + // the CycleFold circuit (over the secondary curve). + // * In Nova, we need to compute P = p_0 + R * p_1. + // - for the cmW we're computing: U_i1.cmW = U_i.cmW + R * u_i.cmW + // - for the cmE we're computing: U_i1.cmE = U_i.cmE + R * cmT + R^2 * u_i.cmE, where u_i.cmE + // is assumed to be 0, so, U_i1.cmE = U_i.cmE + R * cmT + // * In HyperNova, we need to compute P = p_0 + R * p_1 + R^2 * p_2 + ... + R^{n-1} * p_{n-1}. + // * In ProtoGalaxy, we need to compute P = R_0 * p_0 + R_1 * p_1 + R_2 * p_2 + ... + R_{n-1} * p_{n-1}. + // + // To handle HyperNova more efficiently (with less constraints), we do + // P = ((((p_{n-1} * R) + p_{n-2}) * R + p_{n-3}) * R + ...) * R + p_0. + // This can be done iteratively by computing P = (P + p_i) * R. + // + // We further generalize this to support ProtoGalaxy, which now becomes + // P = (((((p_{n-1} * r_{n-1}) + p_{n-2}) * r_{n-2} + p_{n-3}) * r_{n-3} + ...) * r_1 + p_0) * r_0 + // + // Here, r_0 = 1, r_1 = r_2 = ... = r_{n-1} = R for Nova and HyperNova, + // and r_i = R_i / R_{i - 1} for ProtoGalaxy. + let mut p_folded = point_mul::( + &points[CFG::N_INPUT_POINTS - 1], + &rs[CFG::N_INPUT_POINTS - 1], + )?; for i in (0..CFG::N_INPUT_POINTS - 1).rev() { - p_folded = p_folded.scalar_mul_le(r_bits.iter())? + points[i].clone(); + p_folded = point_mul::(&(p_folded + &points[i]), &rs[i])?; } - // Check that the points coordinates are placed as the public input x: - // In Nova, this is: x == [r, p1, p2, p3] (wheere p3 is the p_folded). - // In multifolding schemes such as HyperNova, this is: - // computed_x = [r, p_0, p_1, p_2, ..., p_n, p_folded], - // where each p_i is in fact p_i.to_constraint_field() - let r_fp = r_bits - .chunks(CF2::::MODULUS_BIT_SIZE as usize - 1) - .map(Boolean::le_bits_to_fp) - .collect::, _>>()?; - let points_aux = points - .iter() - .map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec())) - .collect::, SynthesisError>>()? - .into_iter() - .flatten() - .collect(); + CFG::mark_point_as_public(&p_folded)?; + + Ok(()) + } +} + +impl> CycleFoldCircuit { + /// Generates a pair of incoming instance and witness for the CycleFold + /// circuit. + pub fn generate_incoming_instance_witness< + C2: Curve, BaseField = CF1>, + CS2: CommitmentScheme, + const H: bool, + >( + self, + cf_cs_params: &CS2::ProverParams, + mut rng: impl RngCore, + ) -> Result<(CycleFoldWitness, CycleFoldCommittedInstance), Error> { + let cs2 = ConstraintSystem::new_ref(); + self.generate_constraints(cs2.clone())?; + + let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let (cf_w_i, cf_x_i) = extract_w_x(&cs2); - let x = [ - r_fp, - points_aux, - p_folded.to_constraint_field()?[..2].to_vec(), - ] - .concat(); #[cfg(test)] - assert_eq!(x.len(), CFG::IO_LEN); // non-constrained sanity check - - // This line "converts" `x` from a witness to a public input. - // Instead of directly modifying the constraint system, we explicitly - // allocate a public input and enforce that its value is indeed `x`. - // While comparing `x` with itself seems redundant, this is necessary - // because: - // - `.value()` allows an honest prover to extract public inputs without - // computing them outside the circuit. - // - `.enforce_equal()` prevents a malicious prover from claiming wrong - // public inputs that are not the honest `x` computed in-circuit. - Vec::new_input(cs.clone(), || x.value())?.enforce_equal(&x)?; + assert_eq!(cf_x_i.len(), CFG::IO_LEN); - Ok(()) + // generate cyclefold instances + let cf_w_i = CycleFoldWitness::::new::(cf_w_i, cs2.num_constraints, &mut rng); + let cf_u_i = cf_w_i.commit::(cf_cs_params, cf_x_i)?; + + Ok((cf_w_i, cf_u_i)) + } +} + +/// [`CycleFoldAugmentationGadget`] implements methods for folding multiple +/// CycleFold instances, both natively and in the augmented step circuit. +pub struct CycleFoldAugmentationGadget; + +impl CycleFoldAugmentationGadget { + #[allow(clippy::too_many_arguments, clippy::type_complexity)] + pub fn fold_native, const H: bool>( + transcript: &mut impl Transcript>, + cf_r1cs: &R1CS, + cf_cs_params: &CS::ProverParams, + mut cf_W: CycleFoldWitness, // witness of the running instance + mut cf_U: CycleFoldCommittedInstance, // running instance + cf_ws: Vec>, // witnesses of the incoming instances + cf_us: Vec>, // incoming instances + ) -> Result< + ( + CycleFoldWitness, // W_i1 + CycleFoldCommittedInstance, // U_i1 + Vec, // cmT + ), + Error, + > { + assert_eq!(cf_ws.len(), cf_us.len()); + let mut cf_cmTs = vec![]; + + for (cf_w, cf_u) in cf_ws.into_iter().zip(cf_us) { + // compute T* and cmT* for CycleFoldCircuit + let (cf_T, cf_cmT) = NIFS::>, H>::compute_cyclefold_cmT( + cf_cs_params, + cf_r1cs, + &cf_w, + &cf_u, + &cf_W, + &cf_U, + )?; + cf_cmTs.push(cf_cmT); + + let cf_r_bits = + CycleFoldChallengeGadget::get_challenge_native(transcript, &cf_U, &cf_u, cf_cmT); + let cf_r_Fq = CF1::::from( as PrimeField>::BigInt::from_bits_le(&cf_r_bits)); + + (cf_W, cf_U) = CycleFoldNIFS::::prove( + cf_r_Fq, &cf_W, &cf_U, &cf_w, &cf_u, &cf_T, cf_cmT, + )?; + + #[cfg(test)] + { + use crate::{arith::Arith, folding::traits::CommittedInstanceOps}; + cf_u.check_incoming()?; + cf_r1cs.check_relation(&cf_w, &cf_u)?; + cf_r1cs.check_relation(&cf_W, &cf_U)?; + } + } + + Ok((cf_W, cf_U, cf_cmTs)) + } + + pub fn fold_gadget( + transcript: &mut impl TranscriptVar, S>, + mut cf_U: CycleFoldCommittedInstanceVar, + cf_us: Vec>, + cf_cmTs: Vec, + ) -> Result, SynthesisError> { + assert_eq!(cf_us.len(), cf_cmTs.len()); + + // Fold the incoming CycleFold instances into the running CycleFold + // instance in a iterative way, since `NIFSFullGadget` only supports + // folding one incoming instance at a time. + for (cf_u, cmT) in cf_us.into_iter().zip(cf_cmTs) { + let cf_r_bits = CycleFoldChallengeGadget::get_challenge_gadget( + transcript, + &cf_U.to_native_sponge_field_elements()?, + &cf_u, + &cmT, + )?; + // Fold the current incoming CycleFold instance `cf_u` into the + // running CycleFold instance `cf_U`. + cf_U = NIFSFullGadget::fold_committed_instance(cf_r_bits, cmT, cf_U, cf_u)?; + } + + Ok(cf_U) } } @@ -491,90 +703,12 @@ impl, const H: bool> CycleFoldNIFS( - transcript: &mut impl Transcript>, - cf_r1cs: R1CS, - cf_cs_params: CS2::ProverParams, - pp_hash: CF2, // public params hash - cf_W_i: CycleFoldWitness, // witness of the running instance - cf_U_i: CycleFoldCommittedInstance, // running instance - cf_circuit: CycleFoldCircuit, - mut rng: impl RngCore, -) -> Result< - ( - CycleFoldCommittedInstance, // u_i - CycleFoldWitness, // W_i1 - CycleFoldCommittedInstance, // U_i1 - C2, // cmT - ), - Error, -> -where - CFG: CycleFoldConfig, - C2: Curve, BaseField = CF1>, - CS2: CommitmentScheme, -{ - let cs2 = ConstraintSystem::new_ref(); - cf_circuit.generate_constraints(cs2.clone())?; - - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let (cf_w_i, cf_x_i) = extract_w_x(&cs2); - - #[cfg(test)] - assert_eq!(cf_x_i.len(), CFG::IO_LEN); - - // fold cyclefold instances - let cf_w_i = CycleFoldWitness::::new::(cf_w_i.clone(), cf_r1cs.A.n_rows, &mut rng); - let cf_u_i = cf_w_i.commit::(&cf_cs_params, cf_x_i.clone())?; - - // compute T* and cmT* for CycleFoldCircuit - let (cf_T, cf_cmT) = NIFS::>, H>::compute_cyclefold_cmT( - &cf_cs_params, - &cf_r1cs, - &cf_w_i, - &cf_u_i, - &cf_W_i, - &cf_U_i, - )?; - - let cf_r_bits = CycleFoldChallengeGadget::get_challenge_native( - transcript, - pp_hash, - cf_U_i.clone(), - cf_u_i.clone(), - cf_cmT, - ); - let cf_r_Fq = CF1::::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) - .expect("cf_r_bits out of bounds"); - - let (cf_W_i1, cf_U_i1) = CycleFoldNIFS::::prove( - cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT, - )?; - - #[cfg(test)] - { - use crate::{arith::Arith, folding::traits::CommittedInstanceOps}; - cf_u_i.check_incoming()?; - cf_r1cs.check_relation(&cf_w_i, &cf_u_i)?; - cf_r1cs.check_relation(&cf_W_i1, &cf_U_i1)?; - } - - Ok((cf_u_i, cf_W_i1, cf_U_i1, cf_cmT)) -} - #[cfg(test)] pub mod tests { use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective}; - use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, - }; + use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; use ark_r1cs_std::R1CSVar; - use ark_std::{One, UniformRand}; + use ark_std::{One, UniformRand, Zero}; use super::*; use crate::commitment::pedersen::Pedersen; @@ -583,13 +717,45 @@ pub mod tests { use crate::utils::get_cm_coordinates; struct TestCycleFoldConfig { - _c: PhantomData, + r: CF1, + points: Vec, } - impl CycleFoldConfig for TestCycleFoldConfig { + impl Default for TestCycleFoldConfig { + fn default() -> Self { + let r = CF1::::zero(); + let points = vec![C::zero(); N]; + Self { r, points } + } + } + + impl CycleFoldConfig for TestCycleFoldConfig { const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; const N_INPUT_POINTS: usize = N; - type C = C; + const N_UNIQUE_RANDOMNESSES: usize = 1; + + fn alloc_points( + &self, + cs: ConstraintSystemRef>, + ) -> Result, SynthesisError> { + let points = Vec::new_witness(cs.clone(), || Ok(self.points.clone()))?; + for point in &points { + Self::mark_point_as_public(point)?; + } + Ok(points) + } + + fn alloc_randomnesses( + &self, + cs: ConstraintSystemRef>, + ) -> Result>>>, SynthesisError> { + let one = &CF1::::one().into_bigint().to_bits_le()[..NOVA_N_BITS_RO]; + let r = &self.r.into_bigint().to_bits_le()[..NOVA_N_BITS_RO]; + let one_var = Vec::new_constant(cs.clone(), one)?; + let r_var = Vec::new_witness(cs.clone(), || Ok(r))?; + Self::mark_randomness_as_public(&r_var)?; + Ok([vec![one_var], vec![r_var; N - 1]].concat()) + } } #[test] @@ -627,10 +793,7 @@ pub mod tests { get_cm_coordinates(&res), ] .concat(); - let cf_circuit = CycleFoldCircuit::> { - r_bits: Some(rho_bits), - points: Some(points), - }; + let cf_circuit = TestCycleFoldConfig:: { r: rho_Fr, points }.build_circuit(); cf_circuit.generate_constraints(cs.clone())?; assert!(cs.is_satisfied()?); // `instance_assignment[0]` is the constant term 1 @@ -643,8 +806,8 @@ pub mod tests { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_canonical_config::(); - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); let pp_hash = Fr::rand(&mut rng); + let mut transcript_v = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); // prepare the committed instances to test in-circuit let ci: Vec> = (0..2) @@ -653,6 +816,7 @@ pub mod tests { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), + cmV: None, x: vec![Fr::rand(&mut rng); 1], }) .collect(); @@ -664,7 +828,6 @@ pub mod tests { let cmT = Projective::rand(&mut rng); // random only for testing let (ci3, r_bits) = NIFS::, PoseidonSponge>::verify( &mut transcript_v, - pp_hash, &ci1, &ci2, &cmT, @@ -692,12 +855,14 @@ pub mod tests { fn test_cyclefold_challenge_gadget() -> Result<(), Error> { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fq::from(42u32); // only for test + let mut transcript = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); let u_i = CycleFoldCommittedInstance:: { cmE: Projective::zero(), // zero on purpose, so we test also the zero point case u: Fr::zero(), cmW: Projective::rand(&mut rng), + cmV: None, x: std::iter::repeat_with(|| Fr::rand(&mut rng)) .take(TestCycleFoldConfig::::IO_LEN) .collect(), @@ -706,6 +871,7 @@ pub mod tests { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), + cmV: None, x: std::iter::repeat_with(|| Fr::rand(&mut rng)) .take(TestCycleFoldConfig::::IO_LEN) .collect(), @@ -713,12 +879,10 @@ pub mod tests { let cmT = Projective::rand(&mut rng); // random only for testing // compute the challenge natively - let pp_hash = Fq::from(42u32); // only for test let r_bits = CycleFoldChallengeGadget::::get_challenge_native( &mut transcript, - pp_hash, - U_i.clone(), - u_i.clone(), + &U_i, + &u_i, cmT, ); @@ -730,16 +894,15 @@ pub mod tests { Ok(U_i.clone()) })?; let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT))?; + let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; let mut transcript_var = - PoseidonSpongeVar::::new(ConstraintSystem::::new_ref(), &poseidon_config); + PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; - let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; let r_bitsVar = CycleFoldChallengeGadget::::get_challenge_gadget( &mut transcript_var, - pp_hashVar, - U_iVar.to_native_sponge_field_elements()?, - u_iVar, - cmTVar, + &U_iVar.to_native_sponge_field_elements()?, + &u_iVar, + &cmTVar, )?; assert!(cs.is_satisfied()?); @@ -755,28 +918,29 @@ pub mod tests { fn test_cyclefold_hash_gadget() -> Result<(), Error> { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_canonical_config::(); - let sponge = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fq::from(42u32); // only for test + let sponge = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); let U_i = CycleFoldCommittedInstance:: { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), + cmV: None, x: std::iter::repeat_with(|| Fr::rand(&mut rng)) .take(TestCycleFoldConfig::::IO_LEN) .collect(), }; - let pp_hash = Fq::from(42u32); // only for test - let h = U_i.hash_cyclefold(&sponge, pp_hash); + let h = U_i.hash_cyclefold(&sponge); let cs = ConstraintSystem::::new_ref(); let U_iVar = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(U_i.clone()) })?; let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; - let (hVar, _) = U_iVar.hash( - &PoseidonSpongeVar::new(cs.clone(), &poseidon_config), - pp_hashVar, - )?; + let (hVar, _) = U_iVar.hash(&PoseidonSpongeVar::new_with_pp_hash( + &poseidon_config, + &pp_hashVar, + )?)?; hVar.enforce_equal(&FpVar::new_witness(cs.clone(), || Ok(h))?)?; assert!(cs.is_satisfied()?); Ok(()) diff --git a/folding-schemes/src/folding/circuits/decider/mod.rs b/folding-schemes/src/folding/circuits/decider/mod.rs index fbfda6fb..d3323d14 100644 --- a/folding-schemes/src/folding/circuits/decider/mod.rs +++ b/folding-schemes/src/folding/circuits/decider/mod.rs @@ -115,7 +115,6 @@ pub trait DeciderEnabledNIFS< fn fold_field_elements_gadget( arith: &A, transcript: &mut PoseidonSpongeVar>, - pp_hash: FpVar>, U: RU::Var, U_vec: Vec>>, u: IU::Var, @@ -159,6 +158,7 @@ pub mod tests { u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), x: vec![Fr::rand(&mut rng); 1], + cmV: None, }; // compute the challenge natively diff --git a/folding-schemes/src/folding/circuits/decider/off_chain.rs b/folding-schemes/src/folding/circuits/decider/off_chain.rs index 1ea8c71a..b92ba9bb 100644 --- a/folding-schemes/src/folding/circuits/decider/off_chain.rs +++ b/folding-schemes/src/folding/circuits/decider/off_chain.rs @@ -3,7 +3,7 @@ /// More details can be found at the documentation page: /// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html use ark_crypto_primitives::sponge::{ - constraints::{AbsorbGadget, CryptographicSpongeVar}, + constraints::AbsorbGadget, poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, }; use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget, fields::fp::FpVar}; @@ -27,6 +27,7 @@ use crate::{ nova::{decider_eth_circuit::WitnessVar, nifs::nova_circuits::CommittedInstanceVar}, traits::{CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps, WitnessVarOps}, }, + transcript::TranscriptVar, Curve, }; @@ -177,10 +178,10 @@ where let kzg_evaluations = Vec::new_input(cs.clone(), || Ok(self.kzg_evaluations))?; // `sponge` is for digest computation. - let sponge = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config); + // notice that `pp_hash` has already been absorbed during init. + let sponge = PoseidonSpongeVar::new_with_pp_hash(&self.poseidon_config, &pp_hash)?; // `transcript` is for challenge generation. let mut transcript = sponge.clone(); - // notice that the `pp_hash` is absorbed inside the ChallengeGadget::get_challenge_gadget call // 1. enforce `U_{i+1}` and `W_{i+1}` satisfy `arith` arith.enforce_relation(&W_i1, &U_i1)?; @@ -189,15 +190,14 @@ where u_i.enforce_incoming()?; // 3. u_i.x[0] == H(i, z_0, z_i, U_i), u_i.x[1] == H(cf_U_i) - let (u_i_x, U_i_vec) = U_i.hash(&sponge, &pp_hash, &i, &z_0, &z_i)?; - let (cf_u_i_x, _) = cf_U_i.hash(&sponge, pp_hash.clone())?; + let (u_i_x, U_i_vec) = U_i.hash(&sponge, &i, &z_0, &z_i)?; + let (cf_u_i_x, _) = cf_U_i.hash(&sponge)?; u_i.get_public_inputs().enforce_equal(&[u_i_x, cf_u_i_x])?; // 6.1. partially enforce `NIFS.V(U_i, u_i) = U_{i+1}`. D::fold_field_elements_gadget( &self.arith, &mut transcript, - pp_hash, U_i, U_i_vec, u_i, @@ -281,8 +281,7 @@ impl ConstraintSynthesizer> for GenericOffchainDeciderCircuit let kzg_evaluations = Vec::new_input(cs.clone(), || Ok(self.kzg_evaluations))?; // `transcript` is for challenge generation. - let mut transcript = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config); - transcript.absorb(&pp_hash)?; + let mut transcript = PoseidonSpongeVar::new_with_pp_hash(&self.poseidon_config, &pp_hash)?; // 5. enforce `cf_U_i` and `cf_W_i` satisfy `cf_r1cs` cf_r1cs.enforce_relation(&cf_W_i, &cf_U_i)?; diff --git a/folding-schemes/src/folding/circuits/decider/on_chain.rs b/folding-schemes/src/folding/circuits/decider/on_chain.rs index 8c692229..a756ad42 100644 --- a/folding-schemes/src/folding/circuits/decider/on_chain.rs +++ b/folding-schemes/src/folding/circuits/decider/on_chain.rs @@ -1,7 +1,7 @@ /// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases, /// other more efficient approaches can be used. use ark_crypto_primitives::sponge::{ - constraints::{AbsorbGadget, CryptographicSpongeVar}, + constraints::AbsorbGadget, poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, }; use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget, fields::fp::FpVar}; @@ -22,6 +22,7 @@ use crate::{ }, traits::{CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps, WitnessVarOps}, }, + transcript::TranscriptVar, Curve, }; @@ -211,7 +212,7 @@ where let kzg_evaluations = Vec::new_input(cs.clone(), || Ok(self.kzg_evaluations))?; // `sponge` is for digest computation. - let sponge = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config); + let sponge = PoseidonSpongeVar::new_with_pp_hash(&self.poseidon_config, &pp_hash)?; // `transcript` is for challenge generation. let mut transcript = sponge.clone(); @@ -226,8 +227,8 @@ where u_i.enforce_incoming()?; // 3. u_i.x[0] == H(i, z_0, z_i, U_i), u_i.x[1] == H(cf_U_i) - let (u_i_x, U_i_vec) = U_i.hash(&sponge, &pp_hash, &i, &z_0, &z_i)?; - let (cf_u_i_x, _) = cf_U_i.hash(&sponge, pp_hash.clone())?; + let (u_i_x, U_i_vec) = U_i.hash(&sponge, &i, &z_0, &z_i)?; + let (cf_u_i_x, _) = cf_U_i.hash(&sponge)?; u_i.get_public_inputs().enforce_equal(&[u_i_x, cf_u_i_x])?; #[cfg(feature = "light-test")] @@ -286,7 +287,6 @@ where D::fold_field_elements_gadget( &self.arith, &mut transcript, - pp_hash, U_i, U_i_vec, u_i, diff --git a/folding-schemes/src/folding/hypernova/circuits.rs b/folding-schemes/src/folding/hypernova/circuits.rs index 808ea49f..de14ec9b 100644 --- a/folding-schemes/src/folding/hypernova/circuits.rs +++ b/folding-schemes/src/folding/hypernova/circuits.rs @@ -1,6 +1,6 @@ /// Implementation of [HyperNova](https://eprint.iacr.org/2023/573.pdf) circuits use ark_crypto_primitives::sponge::{ - constraints::{AbsorbGadget, CryptographicSpongeVar}, + constraints::AbsorbGadget, poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, CryptographicSponge, }; @@ -10,7 +10,6 @@ use ark_r1cs_std::{ boolean::Boolean, eq::EqGadget, fields::{fp::FpVar, FieldVar}, - prelude::CurveVar, uint8::UInt8, R1CSVar, }; @@ -26,17 +25,16 @@ use super::{ nimfs::{NIMFSProof, NIMFS}, HyperNovaCycleFoldConfig, Witness, }; -use crate::constants::NOVA_N_BITS_RO; use crate::folding::{ circuits::{ cyclefold::{ - CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, - CycleFoldConfig, NIFSFullGadget, + CycleFoldAugmentationGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, + CycleFoldConfig, }, - nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, + nonnative::affine::NonNativeAffineVar, sum_check::{IOPProofVar, SumCheckVerifierGadget, VPAuxInfoVar}, utils::EqEvalGadget, - CF1, CF2, + CF1, }, nova::get_r1cs_from_cs, traits::{CommittedInstanceVarOps, Dummy}, @@ -47,6 +45,7 @@ use crate::{ arith::{ccs::CCS, r1cs::extract_r1cs}, transcript::{AbsorbNonNativeGadget, TranscriptVar}, }; +use crate::{constants::NOVA_N_BITS_RO, transcript::Transcript}; use crate::{Curve, Error}; /// Committed CCS instance @@ -242,7 +241,6 @@ impl NIMFSGadget { // only used the CCS params, not the matrices ccs: &CCS, transcript: &mut T, - running_instances: &[LCCCSVar], // U new_instances: &[CCCSVar], // u proof: ProofVar, @@ -582,9 +580,10 @@ where let all_Ws = [vec![W_i.clone()], Ws].concat(); let all_ws = [vec![w_i.clone()], ws].concat(); - let mut transcript_p: PoseidonSponge = - PoseidonSponge::::new(&self.poseidon_config.clone()); - // since this is only for the number of constraints, no need to absorb the pp_hash here + let mut transcript_p = PoseidonSponge::new_with_pp_hash( + &self.poseidon_config.clone(), + C1::ScalarField::zero(), + ); let (nimfs_proof, U_i1, _, _) = NIMFS::>::prove( &mut transcript_p, &ccs, @@ -704,14 +703,20 @@ where Ok(self.nimfs_proof.unwrap_or(nimfs_proof_dummy)) })?; - let cf_u_dummy = - CycleFoldCommittedInstance::dummy(HyperNovaCycleFoldConfig::::IO_LEN); + let cf_u_dummy = CycleFoldCommittedInstance::dummy(( + HyperNovaCycleFoldConfig::::IO_LEN, + false, + )); let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) })?; let cf_cmT = C2::Var::new_witness(cs.clone(), || Ok(self.cf_cmT.unwrap_or_else(C2::zero)))?; - let sponge = PoseidonSpongeVar::::new(cs.clone(), &self.poseidon_config); + let sponge = PoseidonSpongeVar::::new_with_pp_hash( + &self.poseidon_config, + &pp_hash, + )?; + let mut transcript = sponge.clone(); let is_basecase = i.is_zero()?; let is_not_basecase = !&is_basecase; @@ -719,9 +724,9 @@ where // Primary Part // P.1. Compute u_i.x // u_i.x[0] = H(i, z_0, z_i, U_i) - let (u_i_x, _) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?; + let (u_i_x, _) = U_i.clone().hash(&sponge, &i, &z_0, &z_i)?; // u_i.x[1] = H(cf_U_i) - let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?; + let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge)?; // P.2. Construct u_i let u_i = CCCSVar:: { @@ -740,8 +745,6 @@ where // Notice that NIMFSGadget::fold_committed_instance does not fold C. We set `U_i1.C` to // unconstrained witnesses `U_i1_C` respectively. Its correctness will be checked on the // other curve. - let mut transcript = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config); - transcript.absorb(&pp_hash)?; let (mut U_i1, rho_bits) = NIMFSGadget::::verify( cs.clone(), &self.ccs.clone(), @@ -761,16 +764,11 @@ where .F .generate_step_constraints(cs.clone(), i_usize, z_i, external_inputs)?; - let (u_i1_x, _) = U_i1.clone().hash( - &sponge, - &pp_hash, - &(i + FpVar::>::one()), - &z_0, - &z_i1, - )?; + let (u_i1_x, _) = + U_i1.clone() + .hash(&sponge, &(i + FpVar::>::one()), &z_0, &z_i1)?; let (u_i1_x_base, _) = LCCCSVar::new_constant(cs.clone(), U_dummy)?.hash( &sponge, - &pp_hash, &FpVar::>::one(), &z_0, &z_i1, @@ -787,64 +785,40 @@ where // public inputs that are not the honest `x` computed in-circuit. FpVar::new_input(cs.clone(), || x.value())?.enforce_equal(&x)?; - // convert rho_bits of the rho_vec to a `NonNativeFieldVar` - let mut rho_bits_resized = rho_bits.clone(); - rho_bits_resized.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - let rho_nonnat = NonNativeUintVar::from(&rho_bits_resized); - // CycleFold part - // C.1. Compute cf1_u_i.x and cf2_u_i.x - let cf_x: Vec>> = [ - vec![rho_nonnat], + // C.1. Compute `cf_u_i.x` + // C.2. Construct `cf_u_i` + let cf_u_i = CycleFoldCommittedInstanceVar::new_incoming_from_components( + // `cf_u_i.cmW` is provided by the prover as witness. + C2::Var::new_witness(cs.clone(), || Ok(self.cf_u_i_cmW.unwrap_or(C2::zero())))?, + // To construct `cf_u_i.x`, we need to provide the randomness + // `rho_bits` and the `C` component in LCCCS and CCCS instances + // `all_Us`, `all_us` and `U_{i+1}`. + &rho_bits, all_Us - .iter() - .flat_map(|U| vec![U.C.x.clone(), U.C.y.clone()]) - .collect(), - all_us - .iter() - .flat_map(|u| vec![u.C.x.clone(), u.C.y.clone()]) + .into_iter() + .map(|U| U.C) + .chain(all_us.into_iter().map(|u| u.C)) + .chain(vec![U_i1.C]) .collect(), - vec![U_i1.C.x, U_i1.C.y], - ] - .concat(); - - // ensure that cf_u has as public inputs the C from main instances U_i, u_i, U_i+1 - // coordinates of the commitments. - // C.2. Construct `cf_u_i` - let cf_u_i = CycleFoldCommittedInstanceVar:: { - // cf1_u_i.cmE = 0. Notice that we enforce cmE to be equal to 0 since it is allocated - // as 0. - cmE: C2::Var::zero(), - // cf1_u_i.u = 1 - u: NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::one())?, - // cf_u_i.cmW is provided by the prover as witness - cmW: C2::Var::new_witness(cs.clone(), || Ok(self.cf_u_i_cmW.unwrap_or(C2::zero())))?, - // cf_u_i.x is computed in step 1 - x: cf_x, - }; + )?; // C.3. nifs.verify (fold_committed_instance), obtains cf_U_{i+1} by folding cf_u_i & cf_U_i. - // compute cf_r = H(cf_u_i, cf_U_i, cf_cmT) - // cf_r_bits is denoted by rho* in the paper. - let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( + let cf_U_i1 = CycleFoldAugmentationGadget::fold_gadget( &mut transcript, - pp_hash.clone(), - cf_U_i_vec, - cf_u_i.clone(), - cf_cmT.clone(), + cf_U_i, + vec![cf_u_i], + vec![cf_cmT], )?; - // Fold cf1_u_i & cf_U_i into cf1_U_{i+1} - let cf_U_i1 = - NIFSFullGadget::::fold_committed_instance(cf_r_bits, cf_cmT, cf_U_i, cf_u_i)?; // Back to Primary Part // P.4.b compute and check the second output of F' // Base case: u_{i+1}.x[1] == H(cf_U_{\bot}) // Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1}) - let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge, pp_hash.clone())?; + let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge)?; let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? - .hash(&sponge, pp_hash)?; + .hash(&sponge)?; let cf_x = is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?; // This line "converts" `cf_x` from a witness to a public input. // Instead of directly modifying the constraint system, we explicitly @@ -877,7 +851,6 @@ where mod tests { use ark_bn254::{Fq, Fr, G1Projective as Projective}; use ark_crypto_primitives::sponge::Absorb; - use ark_ff::BigInteger; use ark_grumpkin::Projective as Projective2; use ark_std::{test_rng, UniformRand}; use std::time::Instant; @@ -891,15 +864,12 @@ mod tests { }, commitment::{pedersen::Pedersen, CommitmentScheme}, folding::{ - circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldWitness}, - hypernova::{ - utils::{compute_c, compute_sigmas_thetas}, - HyperNovaCycleFoldCircuit, - }, + circuits::cyclefold::{CycleFoldCircuit, CycleFoldWitness}, + hypernova::utils::{compute_c, compute_sigmas_thetas}, traits::CommittedInstanceOps, }, frontend::utils::{cubic_step_native, CubicFCircuit}, - transcript::poseidon::poseidon_canonical_config, + transcript::{poseidon::poseidon_canonical_config, Transcript}, }; #[test] @@ -1045,7 +1015,11 @@ mod tests { // Prover's transcript let poseidon_config = poseidon_canonical_config::(); - let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::from(42u32); // only for test + let mut transcript_p: PoseidonSponge = + PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); + // Verifier's transcript + let mut transcript_v: PoseidonSponge = transcript_p.clone(); // Run the prover side of the multifolding let (proof, folded_lcccs, folded_witness, _) = @@ -1058,9 +1032,6 @@ mod tests { &w_cccs, )?; - // Verifier's transcript - let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - // Run the verifier side of the multifolding let folded_lcccs_v = NIMFS::>::verify( &mut transcript_v, @@ -1081,7 +1052,9 @@ mod tests { let cccs_instancesVar = Vec::>::new_witness(cs.clone(), || Ok(cccs_instances.clone()))?; let proofVar = ProofVar::::new_witness(cs.clone(), || Ok(proof.clone()))?; - let mut transcriptVar = PoseidonSpongeVar::::new(cs.clone(), &poseidon_config); + let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; + let mut transcriptVar = + PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; let enabled = Boolean::::new_witness(cs.clone(), || Ok(true))?; let (folded_lcccsVar, _) = NIMFSGadget::::verify( @@ -1136,13 +1109,13 @@ mod tests { pub fn test_lcccs_hash() -> Result<(), Error> { let mut rng = test_rng(); let poseidon_config = poseidon_canonical_config::(); - let sponge = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::from(42u32); // only for test + let sponge = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); let ccs = get_test_ccs(); let z1 = get_test_z::(3); let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1)?; - let pp_hash = Fr::from(42u32); // only for test let i = Fr::from(3_u32); let z_0 = vec![Fr::from(3_u32)]; @@ -1152,19 +1125,17 @@ mod tests { &pedersen_params, &z1, )?; - let h = lcccs.clone().hash(&sponge, pp_hash, i, &z_0, &z_i); + let h = lcccs.clone().hash(&sponge, i, &z_0, &z_i); let cs = ConstraintSystem::::new_ref(); - let spongeVar = PoseidonSpongeVar::::new(cs.clone(), &poseidon_config); let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; + let spongeVar = PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; let iVar = FpVar::::new_witness(cs.clone(), || Ok(i))?; let z_0Var = Vec::>::new_witness(cs.clone(), || Ok(z_0.clone()))?; let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i.clone()))?; let lcccsVar = LCCCSVar::::new_witness(cs.clone(), || Ok(lcccs))?; - let (hVar, _) = lcccsVar - .clone() - .hash(&spongeVar, &pp_hashVar, &iVar, &z_0Var, &z_iVar)?; + let (hVar, _) = lcccsVar.clone().hash(&spongeVar, &iVar, &z_0Var, &z_iVar)?; assert!(cs.is_satisfied()?); // check that the natively computed and in-circuit computed hashes match @@ -1176,7 +1147,9 @@ mod tests { pub fn test_augmented_f_circuit() -> Result<(), Error> { let mut rng = test_rng(); let poseidon_config = poseidon_canonical_config::(); - let sponge = PoseidonSponge::::new(&poseidon_config); + // public params hash + let pp_hash = Fr::from(42u32); // only for test + let sponge = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); const MU: usize = 3; const NU: usize = 3; @@ -1195,7 +1168,8 @@ mod tests { // CycleFold circuit let cs2 = ConstraintSystem::::new_ref(); - let cf_circuit = HyperNovaCycleFoldCircuit::::empty(); + let cf_circuit = + CycleFoldCircuit::<_, HyperNovaCycleFoldConfig>::default(); cf_circuit.generate_constraints(cs2.clone())?; cs2.finalize(); let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; @@ -1206,9 +1180,6 @@ mod tests { let (cf_pedersen_params, _) = Pedersen::::setup(&mut rng, cf_r1cs.A.n_cols - cf_r1cs.l - 1)?; - // public params hash - let pp_hash = Fr::from(42u32); // only for test - // first step let z_0 = vec![Fr::from(3_u32)]; let mut z_i = z_0.clone(); @@ -1231,8 +1202,8 @@ mod tests { let mut cf_W_i = cf_W_dummy.clone(); let mut cf_U_i = cf_U_dummy.clone(); u_i.x = vec![ - U_i.hash(&sponge, pp_hash, Fr::zero(), &z_0, &z_i), - cf_U_i.hash_cyclefold(&sponge, pp_hash), + U_i.hash(&sponge, Fr::zero(), &z_0, &z_i), + cf_U_i.hash_cyclefold(&sponge), ]; let n_steps: usize = 4; @@ -1261,11 +1232,11 @@ mod tests { W_i1 = Witness::::dummy(&ccs); U_i1 = LCCCS::dummy(&ccs); - u_i1_x = U_i1.hash(&sponge, pp_hash, Fr::one(), &z_0, &z_i1); + u_i1_x = U_i1.hash(&sponge, Fr::one(), &z_0, &z_i1); // hash the initial (dummy) CycleFold instance, which is used as the 2nd public // input in the AugmentedFCircuit - cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); + cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge); augmented_f_circuit = AugmentedFCircuit::, MU, NU> { @@ -1291,9 +1262,7 @@ mod tests { cf_cmT: None, }; } else { - let mut transcript_p: PoseidonSponge = - PoseidonSponge::::new(&poseidon_config.clone()); - transcript_p.absorb(&pp_hash); + let mut transcript_p: PoseidonSponge = sponge.clone(); let (rho, nimfs_proof); (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( &mut transcript_p, @@ -1307,49 +1276,46 @@ mod tests { // sanity check: check the folded instance relation ccs.check_relation(&W_i1, &U_i1)?; - u_i1_x = U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), &z_0, &z_i1); - - let rho_bits = rho.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); + u_i1_x = U_i1.hash(&sponge, iFr + Fr::one(), &z_0, &z_i1); // CycleFold part: - let cf_circuit = HyperNovaCycleFoldCircuit:: { - r_bits: Some(rho_bits.clone()), - points: Some( - [ - vec![U_i.clone().C], - Us.iter().map(|Us_i| Us_i.C).collect(), - vec![u_i.clone().C], - us.iter().map(|us_i| us_i.C).collect(), - ] - .concat(), - ), + let cf_config = HyperNovaCycleFoldConfig:: { + r: rho, + points: [ + vec![U_i.clone().C], + Us.iter().map(|Us_i| Us_i.C).collect(), + vec![u_i.clone().C], + us.iter().map(|us_i| us_i.C).collect(), + ] + .concat(), }; // ensure that the CycleFoldCircuit is well defined assert_eq!( - cf_circuit.points.clone().unwrap().len(), + cf_config.points.len(), HyperNovaCycleFoldConfig::::N_INPUT_POINTS ); - let (cf_u_i, cf_W_i1, cf_U_i1, cf_cmT) = fold_cyclefold_circuit::< - HyperNovaCycleFoldConfig, - Projective2, - Pedersen, - false, - >( - &mut transcript_p, - cf_r1cs.clone(), - cf_pedersen_params.clone(), - pp_hash, - cf_W_i.clone(), // CycleFold running instance witness - cf_U_i.clone(), // CycleFold running instance - cf_circuit, - &mut rng, - )?; + let (cf_w_i, cf_u_i) = cf_config + .build_circuit() + .generate_incoming_instance_witness::<_, Pedersen<_>, false>( + &cf_pedersen_params, + &mut rng, + )?; + let (cf_W_i1, cf_U_i1, cf_cmTs) = + CycleFoldAugmentationGadget::fold_native::<_, Pedersen<_>, false>( + &mut transcript_p, + &cf_r1cs, + &cf_pedersen_params, + cf_W_i, + cf_U_i.clone(), + vec![cf_w_i], + vec![cf_u_i.clone()], + )?; // hash the CycleFold folded instance, which is used as the 2nd public input in the // AugmentedFCircuit - cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, pp_hash); + cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge); augmented_f_circuit = AugmentedFCircuit::, MU, NU> { @@ -1372,7 +1338,7 @@ mod tests { // cyclefold values cf_u_i_cmW: Some(cf_u_i.cmW), cf_U_i: Some(cf_U_i), - cf_cmT: Some(cf_cmT), + cf_cmT: Some(cf_cmTs[0]), }; // assign the next round instances @@ -1404,8 +1370,8 @@ mod tests { assert_eq!(u_i.x, r1cs_x_i1); assert_eq!(u_i.x[0], u_i1_x); assert_eq!(u_i.x[1], cf_u_i1_x); - let expected_u_i1_x = U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), &z_0, &z_i1); - let expected_cf_U_i1_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); + let expected_u_i1_x = U_i1.hash(&sponge, iFr + Fr::one(), &z_0, &z_i1); + let expected_cf_U_i1_x = cf_U_i.hash_cyclefold(&sponge); // u_i is already u_i1 at this point, check that has the expected value at x[0] assert_eq!(u_i.x[0], expected_u_i1_x); assert_eq!(u_i.x[1], expected_cf_U_i1_x); diff --git a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs index 099e2146..ec76815e 100644 --- a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs @@ -1,16 +1,13 @@ /// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases, /// other more efficient approaches can be used. -use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, - CryptographicSponge, -}; +use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, boolean::Boolean, eq::EqGadget, fields::fp::FpVar, + R1CSVar, }; use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_std::{borrow::Borrow, log2, marker::PhantomData}; @@ -20,7 +17,6 @@ use super::{ nimfs::{NIMFSProof, NIMFS}, HyperNova, Witness, CCCS, LCCCS, }; -use crate::folding::circuits::{decider::on_chain::GenericOnchainDeciderCircuit, CF1}; use crate::folding::traits::{WitnessOps, WitnessVarOps}; use crate::frontend::FCircuit; use crate::utils::gadgets::{eval_mle, MatrixGadget}; @@ -37,6 +33,10 @@ use crate::{ folding::circuits::decider::DeciderEnabledNIFS, Curve, }; +use crate::{ + folding::circuits::{decider::on_chain::GenericOnchainDeciderCircuit, CF1}, + transcript::Transcript, +}; impl ArithGadget>, LCCCSVar> for CCSMatricesVar> { type Evaluation = Vec>>; @@ -125,8 +125,7 @@ impl< fn try_from(hn: HyperNova) -> Result { // compute the U_{i+1}, W_{i+1}, by folding the last running & incoming instances - let mut transcript = PoseidonSponge::::new(&hn.poseidon_config); - transcript.absorb(&hn.pp_hash); + let mut transcript = PoseidonSponge::new_with_pp_hash(&hn.poseidon_config, hn.pp_hash); let (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( &mut transcript, &hn.ccs, @@ -186,15 +185,13 @@ impl DeciderEnabledNIFS, CCCS, Witness, fn fold_field_elements_gadget( arith: &CCS>, transcript: &mut PoseidonSpongeVar>, - pp_hash: FpVar>, U: LCCCSVar, _U_vec: Vec>>, u: CCCSVar, proof: Self::Proof, randomness: Self::Randomness, ) -> Result, SynthesisError> { - let cs = transcript.cs(); - transcript.absorb(&pp_hash)?; + let cs = U.u.cs(); let nimfs_proof = NIMFSProofVar::::new_witness(cs.clone(), || Ok(proof))?; let rho = FpVar::>::new_input(cs.clone(), || Ok(randomness))?; let (computed_U_i1, rho_bits) = NIMFSGadget::::verify( diff --git a/folding-schemes/src/folding/hypernova/mod.rs b/folding-schemes/src/folding/hypernova/mod.rs index e6b5ae31..587a5b74 100644 --- a/folding-schemes/src/folding/hypernova/mod.rs +++ b/folding-schemes/src/folding/hypernova/mod.rs @@ -1,13 +1,12 @@ /// Implements the scheme described in [HyperNova](https://eprint.iacr.org/2023/573.pdf) -use ark_crypto_primitives::sponge::{ - poseidon::{PoseidonConfig, PoseidonSponge}, - CryptographicSponge, -}; +use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::R1CSVar; -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; +use ark_r1cs_std::{alloc::AllocVar, boolean::Boolean, R1CSVar}; +use ark_relations::r1cs::{ + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, +}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, SerializationError}; -use ark_std::{fmt::Debug, marker::PhantomData, rand::RngCore, One, Zero}; +use ark_std::{fmt::Debug, rand::RngCore, One, Zero}; pub mod cccs; pub mod circuits; @@ -23,12 +22,14 @@ use decider_eth_circuit::WitnessVar; use lcccs::LCCCS; use nimfs::NIMFS; -use crate::commitment::CommitmentScheme; use crate::constants::NOVA_N_BITS_RO; use crate::folding::{ - circuits::cyclefold::{ - fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig, - CycleFoldWitness, + circuits::{ + cyclefold::{ + CycleFoldAugmentationGadget, CycleFoldCircuit, CycleFoldCommittedInstance, + CycleFoldConfig, CycleFoldWitness, + }, + CF1, CF2, }, nova::{get_r1cs_from_cs, PreprocessorParam}, traits::{CommittedInstanceOps, Dummy, WitnessOps}, @@ -36,33 +37,58 @@ use crate::folding::{ use crate::frontend::FCircuit; use crate::transcript::poseidon::poseidon_canonical_config; use crate::utils::pp_hash; -use crate::Error; use crate::{ arith::{ ccs::CCS, r1cs::{extract_w_x, R1CS}, Arith, }, - Curve, FoldingScheme, MultiFolding, + Curve, Error, FoldingScheme, MultiFolding, }; +use crate::{commitment::CommitmentScheme, transcript::Transcript}; /// Configuration for HyperNova's CycleFold circuit pub struct HyperNovaCycleFoldConfig { - _c: PhantomData, + r: CF1, + points: Vec, +} + +impl Default for HyperNovaCycleFoldConfig { + fn default() -> Self { + Self { + r: CF1::::zero(), + points: vec![C::zero(); MU + NU], + } + } } -impl CycleFoldConfig +impl CycleFoldConfig for HyperNovaCycleFoldConfig { const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; const N_INPUT_POINTS: usize = MU + NU; - type C = C; -} + const N_UNIQUE_RANDOMNESSES: usize = 1; -/// CycleFold circuit for computing random linear combinations of group elements -/// in HyperNova instances. -pub type HyperNovaCycleFoldCircuit = - CycleFoldCircuit>; + fn alloc_points(&self, cs: ConstraintSystemRef>) -> Result, SynthesisError> { + let points = Vec::new_witness(cs.clone(), || Ok(self.points.clone()))?; + for point in &points { + Self::mark_point_as_public(point)?; + } + Ok(points) + } + + fn alloc_randomnesses( + &self, + cs: ConstraintSystemRef>, + ) -> Result>>>, SynthesisError> { + let one = &CF1::::one().into_bigint().to_bits_le()[..NOVA_N_BITS_RO]; + let r = &self.r.into_bigint().to_bits_le()[..NOVA_N_BITS_RO]; + let one_var = Vec::new_constant(cs.clone(), one)?; + let r_var = Vec::new_witness(cs.clone(), || Ok(r))?; + Self::mark_randomness_as_public(&r_var)?; + Ok([vec![one_var], vec![r_var; MU + NU - 1]].concat()) + } +} /// Witness for the LCCCS & CCCS, containing the w vector, and the r_w used as randomness in the Pedersen commitment. #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] @@ -340,17 +366,19 @@ where let (_, cf_U_i): (CycleFoldWitness, CycleFoldCommittedInstance) = self.cf_r1cs.dummy_witness_instance(); - let sponge = PoseidonSponge::::new(&self.poseidon_config); + let sponge = PoseidonSponge::::new_with_pp_hash( + &self.poseidon_config, + self.pp_hash, + ); u_i.x = vec![ U_i.hash( &sponge, - self.pp_hash, C1::ScalarField::zero(), // i &self.z_0, &state, ), - cf_U_i.hash_cyclefold(&sponge, self.pp_hash), + cf_U_i.hash_cyclefold(&sponge), ]; let us = vec![u_i.clone(); NU - 1]; @@ -472,7 +500,7 @@ where let ccs = augmented_F_circuit.ccs; // CycleFold circuit R1CS - let cf_circuit = HyperNovaCycleFoldCircuit::::empty(); + let cf_circuit = CycleFoldCircuit::<_, HyperNovaCycleFoldConfig>::default(); let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; @@ -502,7 +530,7 @@ where )?; let ccs = augmented_f_circuit.ccs.clone(); - let cf_circuit = HyperNovaCycleFoldCircuit::::empty(); + let cf_circuit = CycleFoldCircuit::<_, HyperNovaCycleFoldConfig>::default(); let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; // if cs params exist, use them, if not, generate new ones @@ -542,8 +570,12 @@ where return Err(Error::CantBeZero("mu,nu".to_string())); } + // compute the public params hash + let pp_hash = vp.pp_hash()?; + // `sponge` is for digest computation. - let sponge = PoseidonSponge::::new(&pp.poseidon_config); + let sponge = + PoseidonSponge::::new_with_pp_hash(&pp.poseidon_config, pp_hash); // prepare the HyperNova's AugmentedFCircuit and CycleFold's circuits and obtain its CCS // and R1CS respectively @@ -554,12 +586,9 @@ where )?; let ccs = augmented_f_circuit.ccs.clone(); - let cf_circuit = HyperNovaCycleFoldCircuit::::empty(); + let cf_circuit = CycleFoldCircuit::<_, HyperNovaCycleFoldConfig>::default(); let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; - // compute the public params hash - let pp_hash = vp.pp_hash()?; - // setup the dummy instances let W_dummy = Witness::::dummy(&ccs); let U_dummy = LCCCS::::dummy(&ccs); @@ -568,8 +597,8 @@ where let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness, CycleFoldCommittedInstance) = cf_r1cs.dummy_witness_instance(); u_dummy.x = vec![ - U_dummy.hash(&sponge, pp_hash, C1::ScalarField::zero(), &z_0, &z_0), - cf_U_dummy.hash_cyclefold(&sponge, pp_hash), + U_dummy.hash(&sponge, C1::ScalarField::zero(), &z_0, &z_0), + cf_U_dummy.hash_cyclefold(&sponge), ]; // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the @@ -717,8 +746,10 @@ where }; } else { let mut transcript_p: PoseidonSponge = - PoseidonSponge::::new(&self.poseidon_config); - transcript_p.absorb(&self.pp_hash); + PoseidonSponge::::new_with_pp_hash( + &self.poseidon_config, + self.pp_hash, + ); let (all_Us, all_us, all_Ws, all_ws) = ( [&[self.U_i.clone()][..], &Us].concat(), @@ -741,31 +772,27 @@ where #[cfg(test)] self.ccs.check_relation(&W_i1, &U_i1)?; - let rho_bits = rho.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); - // CycleFold part: - let cf_circuit = HyperNovaCycleFoldCircuit:: { - r_bits: Some(rho_bits), - points: Some( - [ - all_Us.iter().map(|Us_i| Us_i.C).collect::>(), - all_us.iter().map(|us_i| us_i.C).collect::>(), - ] - .concat(), - ), - }; + let (cf_w_i, cf_u_i) = HyperNovaCycleFoldConfig:: { + r: rho, + points: [ + all_Us.iter().map(|Us_i| Us_i.C).collect::>(), + all_us.iter().map(|us_i| us_i.C).collect::>(), + ] + .concat(), + } + .build_circuit() + .generate_incoming_instance_witness::<_, CS2, H>(&self.cf_cs_pp, &mut rng)?; - let (cf_u_i, cf_W_i1, cf_U_i1, cf_cmT) = - fold_cyclefold_circuit::, C2, CS2, H>( - &mut transcript_p, - self.cf_r1cs.clone(), - self.cf_cs_pp.clone(), - self.pp_hash, - self.cf_W_i.clone(), // CycleFold running instance witness - self.cf_U_i.clone(), // CycleFold running instance - cf_circuit, - &mut rng, - )?; + let (cf_W_i1, cf_U_i1, cf_cmTs) = CycleFoldAugmentationGadget::fold_native::<_, CS2, H>( + &mut transcript_p, + &self.cf_r1cs, + &self.cf_cs_pp, + self.cf_W_i.clone(), + self.cf_U_i.clone(), + vec![cf_w_i], + vec![cf_u_i.clone()], + )?; augmented_f_circuit = AugmentedFCircuit:: { poseidon_config: self.poseidon_config.clone(), @@ -787,7 +814,7 @@ where // cyclefold values cf_u_i_cmW: Some(cf_u_i.cmW), cf_U_i: Some(self.cf_U_i.clone()), - cf_cmT: Some(cf_cmT), + cf_cmT: Some(cf_cmTs[0]), }; // assign the next round instances @@ -880,7 +907,7 @@ where f_circuit.clone(), None, )?; - let cf_circuit = HyperNovaCycleFoldCircuit::::empty(); + let cf_circuit = CycleFoldCircuit::<_, HyperNovaCycleFoldConfig>::default(); let ccs = augmented_f_circuit.ccs.clone(); let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; @@ -927,22 +954,21 @@ where return Ok(()); } // `sponge` is for digest computation. - let sponge = PoseidonSponge::::new(&vp.poseidon_config); + let sponge = + PoseidonSponge::::new_with_pp_hash(&vp.poseidon_config, vp.pp_hash()?); if u_i.x.len() != 2 || U_i.x.len() != 2 { return Err(Error::IVCVerificationFail); } - let pp_hash = vp.pp_hash()?; - // check that u_i's output points to the running instance // u_i.X[0] == H(i, z_0, z_i, U_i) - let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, &z_0, &z_i); + let expected_u_i_x = U_i.hash(&sponge, num_steps, &z_0, &z_i); if expected_u_i_x != u_i.x[0] { return Err(Error::IVCVerificationFail); } // u_i.X[1] == H(cf_U_i) - let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); + let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge); if expected_cf_u_i_x != u_i.x[1] { return Err(Error::IVCVerificationFail); } diff --git a/folding-schemes/src/folding/hypernova/nimfs.rs b/folding-schemes/src/folding/hypernova/nimfs.rs index c126d18d..4680fb38 100644 --- a/folding-schemes/src/folding/hypernova/nimfs.rs +++ b/folding-schemes/src/folding/hypernova/nimfs.rs @@ -396,18 +396,16 @@ impl> NIMFS { #[cfg(test)] pub mod tests { use super::*; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_pallas::{Fr, Projective}; + use ark_std::{test_rng, UniformRand}; + 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}; + use crate::transcript::poseidon::poseidon_canonical_config; #[test] fn test_fold() -> Result<(), Error> { @@ -481,8 +479,10 @@ pub mod tests { // Prover's transcript let poseidon_config = poseidon_canonical_config::(); - let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init")); + let pp_hash = Fr::from_le_bytes_mod_order(b"init init"); + let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); + // Verifier's transcript + let mut transcript_v = transcript_p.clone(); // Run the prover side of the multifolding let (proof, folded_lcccs, folded_witness, _) = @@ -495,10 +495,6 @@ pub mod tests { &[w2], )?; - // Verifier's transcript - let mut transcript_v: PoseidonSponge = PoseidonSponge::::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::>::verify( &mut transcript_v, @@ -529,12 +525,10 @@ pub mod tests { ccs.to_lcccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, &z_1)?; let poseidon_config = poseidon_canonical_config::(); - - let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init")); - - let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - transcript_v.absorb(&Fr::from_le_bytes_mod_order(b"init init")); + let pp_hash = Fr::from_le_bytes_mod_order(b"init init"); + let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); + // Verifier's transcript + let mut transcript_v = transcript_p.clone(); let n: usize = 10; for i in 3..n { @@ -619,8 +613,10 @@ pub mod tests { // Prover's transcript let poseidon_config = poseidon_canonical_config::(); - let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init")); + let pp_hash = Fr::from_le_bytes_mod_order(b"init init"); + let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); + // Verifier's transcript + let mut transcript_v = transcript_p.clone(); // Run the prover side of the multifolding let (proof, folded_lcccs, folded_witness, _) = @@ -633,10 +629,6 @@ pub mod tests { &w_cccs, )?; - // Verifier's transcript - let mut transcript_v: PoseidonSponge = PoseidonSponge::::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::>::verify( &mut transcript_v, @@ -663,13 +655,11 @@ pub mod tests { let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1)?; let poseidon_config = poseidon_canonical_config::(); + let pp_hash = Fr::from_le_bytes_mod_order(b"init init"); // Prover's transcript - let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init")); - + let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); // Verifier's transcript - let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - transcript_v.absorb(&Fr::from_le_bytes_mod_order(b"init init")); + let mut transcript_v = transcript_p.clone(); let n_steps = 3; diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index 6b7f8fa1..77b6a6db 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -1,19 +1,15 @@ /// contains [Nova](https://eprint.iacr.org/2021/370.pdf) related circuits -use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, +use ark_crypto_primitives::sponge::poseidon::{ + constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge, }; -use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::AllocVar, - boolean::Boolean, eq::EqGadget, fields::{fp::FpVar, FieldVar}, - prelude::CurveVar, R1CSVar, }; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_std::{fmt::Debug, One, Zero}; +use ark_std::{fmt::Debug, Zero}; use super::{ nifs::{ @@ -22,18 +18,23 @@ use super::{ }, CommittedInstance, NovaCycleFoldConfig, }; -use crate::folding::circuits::{ - cyclefold::{ - CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, - CycleFoldConfig, NIFSFullGadget, +use crate::frontend::{logup::LogUp, FCircuit}; +use crate::Curve; +use crate::{ + folding::circuits::{ + cyclefold::{ + CycleFoldAugmentationGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, + CycleFoldConfig, + }, + nonnative::affine::NonNativeAffineVar, + CF1, }, - nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, - CF1, + transcript::TranscriptVar, +}; +use crate::{ + folding::traits::{CommittedInstanceVarOps, Dummy}, + frontend::alloc::{ConstraintSystemStatistics, Query}, }; -use crate::folding::traits::{CommittedInstanceVarOps, Dummy}; -use crate::frontend::FCircuit; -use crate::transcript::AbsorbNonNativeGadget; -use crate::Curve; /// `AugmentedFCircuit` enhances the original step function `F`, so that it can /// be used in recursive arguments such as IVC. @@ -50,51 +51,62 @@ use crate::Curve; #[derive(Debug, Clone)] pub struct AugmentedFCircuit>> { pub(super) poseidon_config: PoseidonConfig>, - pub(super) pp_hash: Option>, - pub(super) i: Option>, - pub(super) i_usize: Option, - pub(super) z_0: Option>, - pub(super) z_i: Option>, - pub(super) external_inputs: Option, - pub(super) u_i_cmW: Option, + pub(super) pp_hash: CF1, + pub(super) i: CF1, + pub(super) i_usize: usize, + pub(super) z_0: Vec, + pub(super) z_i: Vec, + pub(super) external_inputs: FC::ExternalInputs, + pub(super) u_i_cmW: C1, + pub(super) u_i_cmV: Option, pub(super) U_i: Option>, - pub(super) U_i1_cmE: Option, - pub(super) U_i1_cmW: Option, - pub(super) cmT: Option, + pub(super) U_i1_cmW: C1, + pub(super) U_i1_cmV: Option, + pub(super) U_i1_cmE: C1, + pub(super) cmT: C1, pub(super) F: FC, // F circuit // cyclefold verifier on C1 // Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and // cmE respectively - pub(super) cf1_u_i_cmW: Option, // input - pub(super) cf2_u_i_cmW: Option, // input - pub(super) cf_U_i: Option>, // input - pub(super) cf1_cmT: Option, - pub(super) cf2_cmT: Option, + pub(super) cf1_u_i_cmW: C2, // input + pub(super) cf2_u_i_cmW: C2, // input + pub(super) cf3_u_i_cmW: Option, // input + pub(super) cf_U_i: CycleFoldCommittedInstance, // input + pub(super) cf1_cmT: C2, + pub(super) cf2_cmT: C2, + pub(super) cf3_cmT: Option, } impl>> AugmentedFCircuit { pub fn empty(poseidon_config: &PoseidonConfig>, F_circuit: FC) -> Self { + let cf_u_dummy = + CycleFoldCommittedInstance::dummy((NovaCycleFoldConfig::::IO_LEN, false)); + Self { poseidon_config: poseidon_config.clone(), - pp_hash: None, - i: None, - i_usize: None, - z_0: None, - z_i: None, - external_inputs: None, - u_i_cmW: None, + pp_hash: Zero::zero(), + i: Zero::zero(), + i_usize: 0, + z_0: vec![Zero::zero(); F_circuit.state_len()], + z_i: vec![Zero::zero(); F_circuit.state_len()], + external_inputs: FC::ExternalInputs::default(), + u_i_cmW: Zero::zero(), + u_i_cmV: None, U_i: None, - U_i1_cmE: None, - U_i1_cmW: None, - cmT: None, + U_i1_cmW: Zero::zero(), + U_i1_cmV: None, + U_i1_cmE: Zero::zero(), + cmT: Zero::zero(), F: F_circuit, // cyclefold values - cf1_u_i_cmW: None, - cf2_u_i_cmW: None, - cf_U_i: None, - cf1_cmT: None, - cf2_cmT: None, + cf1_u_i_cmW: Zero::zero(), + cf2_u_i_cmW: Zero::zero(), + cf3_u_i_cmW: None, + cf_U_i: cf_u_dummy, + cf1_cmT: Zero::zero(), + cf2_cmT: Zero::zero(), + cf3_cmT: None, } } } @@ -109,51 +121,73 @@ where self, cs: ConstraintSystemRef>, ) -> Result>>, SynthesisError> { - let pp_hash = FpVar::>::new_witness(cs.clone(), || { - Ok(self.pp_hash.unwrap_or_else(CF1::::zero)) - })?; - let i = FpVar::>::new_witness(cs.clone(), || { - Ok(self.i.unwrap_or_else(CF1::::zero)) - })?; - let z_0 = Vec::>>::new_witness(cs.clone(), || { - Ok(self - .z_0 - .unwrap_or(vec![CF1::::zero(); self.F.state_len()])) - })?; - let z_i = Vec::>>::new_witness(cs.clone(), || { - Ok(self - .z_i - .unwrap_or(vec![CF1::::zero(); self.F.state_len()])) - })?; - let external_inputs = FC::ExternalInputsVar::new_witness(cs.clone(), || { - Ok(self.external_inputs.unwrap_or_default()) - })?; + let pp_hash = FpVar::>::new_witness(cs.clone(), || Ok(self.pp_hash))?; + let i = FpVar::>::new_witness(cs.clone(), || Ok(self.i))?; + let z_0 = Vec::>>::new_witness(cs.clone(), || Ok(self.z_0))?; + let z_i = Vec::>>::new_witness(cs.clone(), || Ok(self.z_i))?; + let external_inputs = + FC::ExternalInputsVar::new_witness(cs.clone(), || Ok(self.external_inputs))?; - let u_dummy = CommittedInstance::dummy(2); - let U_i = CommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(self.U_i.unwrap_or(u_dummy.clone())) - })?; - let U_i1_cmE = NonNativeAffineVar::new_witness(cs.clone(), || { - Ok(self.U_i1_cmE.unwrap_or_else(C1::zero)) - })?; - let U_i1_cmW = NonNativeAffineVar::new_witness(cs.clone(), || { - Ok(self.U_i1_cmW.unwrap_or_else(C1::zero)) - })?; + // get z_{i+1} from the F circuit + let z_i1 = self.F.generate_step_constraints( + cs.clone(), + self.i_usize, + z_i.clone(), + external_inputs, + )?; - let cmT = - NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?; + let lookup_enabled = cs.has_variables_of_type::(); - let cf_u_dummy = CycleFoldCommittedInstance::dummy(NovaCycleFoldConfig::::IO_LEN); - let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) + let u_dummy = CommittedInstance::dummy(if lookup_enabled { + (3, true) + } else { + (2, false) + }); + let U_i = CommittedInstanceVar::::new_witness(cs.clone(), || { + if cs.is_in_setup_mode() { + Ok(u_dummy.clone()) + } else { + self.U_i.ok_or(SynthesisError::AssignmentMissing) + } })?; - let cf1_cmT = - C2::Var::new_witness(cs.clone(), || Ok(self.cf1_cmT.unwrap_or_else(C2::zero)))?; - let cf2_cmT = - C2::Var::new_witness(cs.clone(), || Ok(self.cf2_cmT.unwrap_or_else(C2::zero)))?; + let u_i_cmW = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.u_i_cmW))?; + let u_i_cmV = if lookup_enabled { + Some(NonNativeAffineVar::new_witness(cs.clone(), || { + if cs.is_in_setup_mode() { + Ok(Zero::zero()) + } else { + self.u_i_cmV.ok_or(SynthesisError::AssignmentMissing) + } + })?) + } else { + None + }; + + let cmT = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT))?; + + let cf_u_dummy = + CycleFoldCommittedInstance::dummy((NovaCycleFoldConfig::::IO_LEN, false)); + let cf_U_i = + CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || Ok(self.cf_U_i))?; + let cf1_cmT = C2::Var::new_witness(cs.clone(), || Ok(self.cf1_cmT))?; + let cf2_cmT = C2::Var::new_witness(cs.clone(), || Ok(self.cf2_cmT))?; + let cf3_cmT = if lookup_enabled { + Some(C2::Var::new_witness(cs.clone(), || { + if cs.is_in_setup_mode() { + Ok(Zero::zero()) + } else { + self.cf3_cmT.ok_or(SynthesisError::AssignmentMissing) + } + })?) + } else { + None + }; // `sponge` is for digest computation. - let sponge = PoseidonSpongeVar::::new(cs.clone(), &self.poseidon_config); + let sponge = PoseidonSpongeVar::::new_with_pp_hash( + &self.poseidon_config, + &pp_hash, + )?; // `transcript` is for challenge generation. let mut transcript = sponge.clone(); @@ -162,9 +196,15 @@ where // Primary Part // P.1. Compute u_i.x // u_i.x[0] = H(i, z_0, z_i, U_i) - let (u_i_x, U_i_vec) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?; + let (x0, U_i_vec) = U_i.clone().hash(&sponge, &i, &z_0, &z_i)?; // u_i.x[1] = H(cf_U_i) - let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?; + let (x1, _) = cf_U_i.clone().hash(&sponge)?; + // u_i.x[2] = H... + let x2 = if lookup_enabled { + Some(LogUp::challenge_gadget(&sponge, &u_i_cmW)?) + } else { + None + }; // P.2. Construct u_i let u_i = CommittedInstanceVar { @@ -173,11 +213,14 @@ where // u_i.u = 1 u: FpVar::one(), // u_i.cmW is provided by the prover as witness - cmW: NonNativeAffineVar::new_witness(cs.clone(), || { - Ok(self.u_i_cmW.unwrap_or(C1::zero())) - })?, + cmW: u_i_cmW, + cmV: u_i_cmV, // u_i.x is computed in step 1 - x: vec![u_i_x, cf_u_i_x], + x: if let Some(x2) = x2 { + vec![x0, x1, x2] + } else { + vec![x0, x1] + }, }; // P.3. nifs.verify, obtains U_{i+1} by folding u_i & U_i. @@ -191,42 +234,33 @@ where PoseidonSpongeVar, >::verify( &mut transcript, - pp_hash.clone(), U_i.clone(), U_i_vec, u_i.clone(), Some(cmT.clone()), )?; - U_i1.cmE = U_i1_cmE; - U_i1.cmW = U_i1_cmW; - - // convert r_bits to a `NonNativeFieldVar` - let r_nonnat = { - let mut bits = r_bits; - bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - NonNativeUintVar::from(&bits) + U_i1.cmE = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.U_i1_cmE))?; + U_i1.cmW = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.U_i1_cmW))?; + U_i1.cmV = if lookup_enabled { + Some(NonNativeAffineVar::new_witness(cs.clone(), || { + if cs.is_in_setup_mode() { + Ok(Zero::zero()) + } else { + self.U_i1_cmV.ok_or(SynthesisError::AssignmentMissing) + } + })?) + } else { + None }; // P.4.a compute and check the first output of F' - - // get z_{i+1} from the F circuit - let i_usize = self.i_usize.unwrap_or(0); - let z_i1 = self - .F - .generate_step_constraints(cs.clone(), i_usize, z_i, external_inputs)?; - // Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot}) // Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1}) - let (u_i1_x, _) = U_i1.clone().hash( - &sponge, - &pp_hash, - &(i + FpVar::>::one()), - &z_0, - &z_i1, - )?; + let (u_i1_x, _) = + U_i1.clone() + .hash(&sponge, &(i + FpVar::>::one()), &z_0, &z_i1)?; let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash( &sponge, - &pp_hash, &FpVar::>::one(), &z_0, &z_i1, @@ -245,80 +279,69 @@ where // CycleFold part // C.1. Compute cf1_u_i.x and cf2_u_i.x - let cfW_x = vec![ - r_nonnat.clone(), - U_i.cmW.x, - U_i.cmW.y, - u_i.cmW.x, - u_i.cmW.y, - U_i1.cmW.x, - U_i1.cmW.y, - ]; - let cfE_x = vec![ - r_nonnat, U_i.cmE.x, U_i.cmE.y, cmT.x, cmT.y, U_i1.cmE.x, U_i1.cmE.y, - ]; - - // ensure that cf1_u & cf2_u have as public inputs the cmW & cmE from main instances U_i, - // u_i, U_i+1 coordinates of the commitments // C.2. Construct `cf1_u_i` and `cf2_u_i` - let cf1_u_i = CycleFoldCommittedInstanceVar { - // cf1_u_i.cmE = 0 - cmE: C2::Var::zero(), - // cf1_u_i.u = 1 - u: NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::one())?, - // cf1_u_i.cmW is provided by the prover as witness - cmW: C2::Var::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW.unwrap_or(C2::zero())))?, - // cf1_u_i.x is computed in step 1 - x: cfW_x, - }; - let cf2_u_i = CycleFoldCommittedInstanceVar { - // cf2_u_i.cmE = 0 - cmE: C2::Var::zero(), - // cf2_u_i.u = 1 - u: NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::one())?, - // cf2_u_i.cmW is provided by the prover as witness - cmW: C2::Var::new_witness(cs.clone(), || Ok(self.cf2_u_i_cmW.unwrap_or(C2::zero())))?, - // cf2_u_i.x is computed in step 1 - x: cfE_x, - }; - - // C.3. nifs.verify, obtains cf1_U_{i+1} by folding cf1_u_i & cf_U_i, and then cf_U_{i+1} - // by folding cf2_u_i & cf1_U_{i+1}. - - // compute cf1_r = H(cf1_u_i, cf_U_i, cf1_cmT) - // cf_r_bits is denoted by rho* in the paper. - let cf1_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( - &mut transcript, - pp_hash.clone(), - cf_U_i_vec, - cf1_u_i.clone(), - cf1_cmT.clone(), + let cf1_u_i = CycleFoldCommittedInstanceVar::new_incoming_from_components( + // `cf1_u_i.cmW` is provided by the prover as witness. + C2::Var::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW))?, + // To construct `cf1_u_i.x`, we need to provide the randomness + // `r_bits`, the `cmE` component in running instances `U_i` and + // `U_{i+1}`, and the cross term commitment `cmT`. + &r_bits, + vec![U_i.cmE, cmT, U_i1.cmE], + )?; + let cf2_u_i = CycleFoldCommittedInstanceVar::new_incoming_from_components( + // `cf2_u_i.cmW` is provided by the prover as witness. + C2::Var::new_witness(cs.clone(), || Ok(self.cf2_u_i_cmW))?, + // To construct `cf2_u_i.x`, we need to provide the randomness + // `r_bits` and the `cmW` component in committed instances `U_i`, + // `u_i`, and `U_{i+1}`. + &r_bits, + vec![U_i.cmW, u_i.cmW, U_i1.cmW], )?; - // Fold cf1_u_i & cf_U_i into cf1_U_{i+1} - let cf1_U_i1 = - NIFSFullGadget::::fold_committed_instance(cf1_r_bits, cf1_cmT, cf_U_i, cf1_u_i)?; + let cf3_u_i = if lookup_enabled { + Some(CycleFoldCommittedInstanceVar::new_incoming_from_components( + // `cf3_u_i.cmW` is provided by the prover as witness. + C2::Var::new_witness(cs.clone(), || { + if cs.is_in_setup_mode() { + Ok(Zero::zero()) + } else { + self.cf3_u_i_cmW.ok_or(SynthesisError::AssignmentMissing) + } + })?, + // To construct `cf3_u_i.x`, we need to provide the randomness + // `r_bits` and the `cmW` component in committed instances `U_i`, + // `u_i`, and `U_{i+1}`. + &r_bits, + vec![U_i.cmV.unwrap(), u_i.cmV.unwrap(), U_i1.cmV.unwrap()], + )?) + } else { + None + }; - // same for cf2_r: - let cf2_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( + // C.3. nifs.verify, obtains cf_U_{i+1} by folding cf1_u_i and cf2_u_i into cf_U. + let cf_U_i1 = CycleFoldAugmentationGadget::fold_gadget( &mut transcript, - pp_hash.clone(), - cf1_U_i1.to_native_sponge_field_elements()?, - cf2_u_i.clone(), - cf2_cmT.clone(), - )?; - let cf_U_i1 = NIFSFullGadget::::fold_committed_instance( - cf2_r_bits, cf2_cmT, cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u) - cf2_u_i, + cf_U_i, + if let Some(cf3_u_i) = cf3_u_i { + vec![cf1_u_i, cf2_u_i, cf3_u_i] + } else { + vec![cf1_u_i, cf2_u_i] + }, + if let Some(cf3_cmT) = cf3_cmT { + vec![cf1_cmT, cf2_cmT, cf3_cmT] + } else { + vec![cf1_cmT, cf2_cmT] + }, )?; // Back to Primary Part // P.4.b compute and check the second output of F' // Base case: u_{i+1}.x[1] == H(cf_U_{\bot}) // Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1}) - let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge, pp_hash.clone())?; + let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge)?; let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? - .hash(&sponge, pp_hash)?; + .hash(&sponge)?; let cf_x = is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?; // This line "converts" `cf_x` from a witness to a public input. // Instead of directly modifying the constraint system, we explicitly @@ -350,16 +373,15 @@ where pub mod tests { use super::*; use ark_bn254::{Fr, G1Projective as Projective}; - use ark_crypto_primitives::sponge::{ - constraints::AbsorbGadget, poseidon::PoseidonSponge, CryptographicSponge, - }; - use ark_ff::BigInteger; + use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, poseidon::PoseidonSponge}; + use ark_ff::{BigInteger, PrimeField}; + use ark_r1cs_std::prelude::Boolean; use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; use crate::folding::nova::nifs::nova::ChallengeGadget; - use crate::transcript::poseidon::poseidon_canonical_config; + use crate::transcript::{poseidon::poseidon_canonical_config, Transcript}; use crate::Error; // checks that the gadget and native implementations of the challenge computation match @@ -367,29 +389,29 @@ pub mod tests { fn test_challenge_gadget() -> Result<(), Error> { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::from(42u32); // only for testing + let mut transcript = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); let u_i = CommittedInstance:: { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), + cmV: None, x: vec![Fr::rand(&mut rng); 1], }; let U_i = CommittedInstance:: { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), + cmV: None, x: vec![Fr::rand(&mut rng); 1], }; let cmT = Projective::rand(&mut rng); - let pp_hash = Fr::from(42u32); // only for testing - // compute the challenge natively let r_bits = ChallengeGadget::>::get_challenge_native( &mut transcript, - pp_hash, &U_i, &u_i, Some(&cmT), @@ -403,13 +425,13 @@ pub mod tests { let U_iVar = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(U_i.clone()))?; let cmTVar = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(cmT))?; - let mut transcriptVar = PoseidonSpongeVar::::new(cs.clone(), &poseidon_config); + let mut transcriptVar = + PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; // compute the challenge in-circuit let r_bitsVar = ChallengeGadget::>::get_challenge_gadget( &mut transcriptVar, - pp_hashVar, U_iVar.to_sponge_field_elements()?, u_iVar, Some(cmTVar), diff --git a/folding-schemes/src/folding/nova/decider_circuits.rs b/folding-schemes/src/folding/nova/decider_circuits.rs index db9a686d..f7ec1014 100644 --- a/folding-schemes/src/folding/nova/decider_circuits.rs +++ b/folding-schemes/src/folding/nova/decider_circuits.rs @@ -2,7 +2,7 @@ /// DeciderEthCircuit. /// More details can be found at the documentation page: /// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html -use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; +use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::fields::fp::FpVar; use core::marker::PhantomData; @@ -12,17 +12,23 @@ use super::{ nifs::{nova::NIFS, NIFSTrait}, CommittedInstance, Nova, Witness, }; -use crate::folding::{circuits::CF1, traits::WitnessOps}; -use crate::frontend::FCircuit; use crate::{ arith::r1cs::{circuits::R1CSMatricesVar, R1CS}, - folding::circuits::decider::{ - off_chain::{GenericOffchainDeciderCircuit1, GenericOffchainDeciderCircuit2}, - EvalGadget, KZGChallengesGadget, + commitment::CommitmentScheme, + folding::{ + circuits::{ + decider::{ + off_chain::{GenericOffchainDeciderCircuit1, GenericOffchainDeciderCircuit2}, + EvalGadget, KZGChallengesGadget, + }, + CF1, + }, + traits::WitnessOps, }, + frontend::FCircuit, + transcript::{poseidon::poseidon_canonical_config, Transcript}, + Curve, Error, }; -use crate::{commitment::CommitmentScheme, transcript::poseidon::poseidon_canonical_config}; -use crate::{Curve, Error}; /// Circuit that implements part of the in-circuit checks needed for the offchain verification over /// the Curve2's BaseField (=Curve1's ScalarField). @@ -50,7 +56,7 @@ impl< type Error = Error; fn try_from(nova: Nova) -> Result { - let mut transcript = PoseidonSponge::::new(&nova.poseidon_config); + let mut transcript = PoseidonSponge::new_with_pp_hash(&nova.poseidon_config, nova.pp_hash); // pp_hash is absorbed to transcript at the NIFS::prove call // compute the U_{i+1}, W_{i+1} @@ -58,7 +64,6 @@ impl< &nova.cs_pp, &nova.r1cs.clone(), &mut transcript, - nova.pp_hash, &nova.W_i, &nova.U_i, &nova.w_i, @@ -120,10 +125,10 @@ impl< // compute the Commitment Scheme challenges of the CycleFold instance commitments, used as // inputs in the circuit let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); let pp_hash_Fq = C2::ScalarField::from_le_bytes_mod_order(&nova.pp_hash.into_bigint().to_bytes_le()); - transcript.absorb(&pp_hash_Fq); + let mut transcript = + PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash_Fq); // compute the KZG challenges used as inputs in the circuit let kzg_challenges = diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index e3f7fdba..482c89db 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -2,15 +2,12 @@ /// other more efficient approaches can be used. /// More details can be found at the documentation page: /// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html -use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, - CryptographicSponge, -}; +use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, fields::fp::FpVar, + R1CSVar, }; use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_std::{borrow::Borrow, marker::PhantomData}; @@ -20,19 +17,24 @@ use super::{ nifs::{nova::NIFS, NIFSGadgetTrait, NIFSTrait}, CommittedInstance, Nova, Witness, }; -use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; -use crate::folding::{ - circuits::{ - decider::on_chain::GenericOnchainDeciderCircuit, nonnative::affine::NonNativeAffineVar, CF1, - }, - traits::{WitnessOps, WitnessVarOps}, -}; -use crate::frontend::FCircuit; use crate::{ arith::r1cs::{circuits::R1CSMatricesVar, R1CS}, - folding::circuits::decider::{DeciderEnabledNIFS, EvalGadget, KZGChallengesGadget}, + commitment::{pedersen::Params as PedersenParams, CommitmentScheme}, + folding::{ + circuits::{ + decider::{ + on_chain::GenericOnchainDeciderCircuit, DeciderEnabledNIFS, EvalGadget, + KZGChallengesGadget, + }, + nonnative::affine::NonNativeAffineVar, + CF1, + }, + traits::{WitnessOps, WitnessVarOps}, + }, + frontend::FCircuit, + transcript::Transcript, + Curve, Error, }; -use crate::{Curve, Error}; /// In-circuit representation of the Witness associated to the CommittedInstance. #[derive(Debug, Clone)] @@ -41,6 +43,8 @@ pub struct WitnessVar { pub rE: FpVar, pub W: Vec>, pub rW: FpVar, + pub V: Vec>, + pub rV: Option>, } impl AllocVar, CF1> for WitnessVar { @@ -62,14 +66,40 @@ impl AllocVar, CF1> for WitnessVar { let rW = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().rW), mode)?; - Ok(Self { E, rE, W, rW }) + let V: Vec> = + Vec::new_variable(cs.clone(), || Ok(val.borrow().V.clone()), mode)?; + let rV = match val.borrow().rV { + Some(rV) => Some(FpVar::::new_variable( + cs.clone(), + || Ok(rV), + mode, + )?), + None => None, + }; + + Ok(Self { + E, + rE, + W, + rW, + V, + rV, + }) }) } } impl WitnessVarOps for WitnessVar { fn get_openings(&self) -> Vec<(&[FpVar], FpVar)> { - vec![(&self.W, self.rW.clone()), (&self.E, self.rE.clone())] + if let Some(rV) = &self.rV { + vec![ + (&self.W, self.rW.clone()), + (&self.V, rV.clone()), + (&self.E, self.rE.clone()), + ] + } else { + vec![(&self.W, self.rW.clone()), (&self.E, self.rE.clone())] + } } } @@ -98,14 +128,13 @@ impl< type Error = Error; fn try_from(nova: Nova) -> Result { - let mut transcript = PoseidonSponge::::new(&nova.poseidon_config); + let mut transcript = PoseidonSponge::new_with_pp_hash(&nova.poseidon_config, nova.pp_hash); // compute the U_{i+1}, W_{i+1} let (W_i1, U_i1, cmT, r_bits) = NIFS::, H>::prove( &nova.cs_pp, &nova.r1cs.clone(), &mut transcript, - nova.pp_hash, &nova.W_i, &nova.U_i, &nova.w_i, @@ -165,16 +194,15 @@ impl fn fold_field_elements_gadget( _arith: &R1CS>, transcript: &mut PoseidonSpongeVar>, - pp_hash: FpVar>, U: CommittedInstanceVar, U_vec: Vec>>, u: CommittedInstanceVar, proof: C, _randomness: CF1, ) -> Result, SynthesisError> { - let cs = transcript.cs(); + let cs = U.u.cs(); let cmT = NonNativeAffineVar::new_input(cs.clone(), || Ok(proof))?; - let (new_U, _) = NIFSGadget::verify(transcript, pp_hash, U, U_vec, u, Some(cmT))?; + let (new_U, _) = NIFSGadget::verify(transcript, U, U_vec, u, Some(cmT))?; Ok(new_U) } diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index cae1782a..ca1bea20 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -6,33 +6,39 @@ /// - IVC and the Decider (offchain Decider & onchain Decider) implementations for Nova use ark_crypto_primitives::sponge::{ poseidon::{PoseidonConfig, PoseidonSponge}, - Absorb, CryptographicSponge, + Absorb, }; use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::R1CSVar; -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid}; -use ark_std::fmt::Debug; -use ark_std::rand::RngCore; -use ark_std::{One, UniformRand, Zero}; -use core::marker::PhantomData; - -use crate::folding::circuits::cyclefold::{ - fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig, - CycleFoldWitness, +use ark_r1cs_std::{alloc::AllocVar, prelude::Boolean, R1CSVar}; +use ark_relations::r1cs::{ + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, }; -use crate::folding::{circuits::CF1, traits::Dummy}; -use crate::frontend::FCircuit; -use crate::transcript::{poseidon::poseidon_canonical_config, Transcript}; -use crate::utils::vec::is_zero_vec; -use crate::FoldingScheme; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid}; +use ark_std::{fmt::Debug, rand::RngCore, One, UniformRand, Zero}; +use num_bigint::BigUint; + use crate::{ - arith::r1cs::{extract_r1cs, extract_w_x, R1CS}, + arith::{ + r1cs::{extract_r1cs, extract_w_x, R1CS}, + Arith, + }, + commitment::CommitmentScheme, constants::NOVA_N_BITS_RO, - utils::pp_hash, + folding::{ + circuits::{ + cyclefold::{ + CycleFoldAugmentationGadget, CycleFoldCommittedInstance, CycleFoldConfig, + CycleFoldWitness, + }, + CF1, + }, + traits::Dummy, + }, + frontend::{alloc::LookupConstraintSystem, logup::LogUp, FCircuit}, + transcript::{poseidon::poseidon_canonical_config, Transcript}, + utils::{pp_hash, vec::is_zero_vec}, + Curve, Error, FoldingScheme, }; -use crate::{arith::Arith, commitment::CommitmentScheme}; -use crate::{Curve, Error}; use decider_eth_circuit::WitnessVar; pub mod circuits; @@ -52,48 +58,78 @@ pub mod decider_circuits; pub mod decider_eth; pub mod decider_eth_circuit; -use super::traits::{CommittedInstanceOps, Inputize, WitnessOps}; +use super::{ + circuits::{cyclefold::CycleFoldCircuit, CF2}, + traits::{CommittedInstanceOps, Inputize, WitnessOps}, +}; /// Configuration for Nova's CycleFold circuit pub struct NovaCycleFoldConfig { - _c: PhantomData, + r: Vec, + points: Vec, } -impl CycleFoldConfig for NovaCycleFoldConfig { +impl Default for NovaCycleFoldConfig { + fn default() -> Self { + Self { + r: vec![false; NOVA_N_BITS_RO], + points: vec![C::zero(); 2], + } + } +} + +impl CycleFoldConfig for NovaCycleFoldConfig { const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; // Number of points to be folded in the CycleFold circuit, in Nova's case, this is a fixed // amount: // 2 points to be folded. const N_INPUT_POINTS: usize = 2; - type C = C; -} + const N_UNIQUE_RANDOMNESSES: usize = 1; -/// CycleFold circuit for computing random linear combinations of group elements -/// in Nova instances. -pub type NovaCycleFoldCircuit = CycleFoldCircuit>; + fn alloc_points(&self, cs: ConstraintSystemRef>) -> Result, SynthesisError> { + let points = Vec::new_witness(cs.clone(), || Ok(self.points.clone()))?; + for point in &points { + Self::mark_point_as_public(point)?; + } + Ok(points) + } + + fn alloc_randomnesses( + &self, + cs: ConstraintSystemRef>, + ) -> Result>>>, SynthesisError> { + let one = &CF1::::one().into_bigint().to_bits_le()[..NOVA_N_BITS_RO]; + let one_var = Vec::new_constant(cs.clone(), one)?; + let r_var = Vec::new_witness(cs.clone(), || Ok(self.r.clone()))?; + Self::mark_randomness_as_public(&r_var)?; + Ok(vec![one_var, r_var]) + } +} #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct CommittedInstance { - pub cmE: C, pub u: C::ScalarField, - pub cmW: C, pub x: Vec, + pub cmW: C, + pub cmV: Option, + pub cmE: C, } -impl Dummy for CommittedInstance { - fn dummy(io_len: usize) -> Self { +impl Dummy<(usize, bool)> for CommittedInstance { + fn dummy((io_len, has_queries): (usize, bool)) -> Self { Self { - cmE: C::zero(), u: CF1::::zero(), - cmW: C::zero(), x: vec![CF1::::zero(); io_len], + cmW: C::zero(), + cmV: if has_queries { Some(C::zero()) } else { None }, + cmE: C::zero(), } } } impl Dummy<&R1CS>> for CommittedInstance { fn dummy(r1cs: &R1CS>) -> Self { - Self::dummy(r1cs.l) + Self::dummy((r1cs.l, r1cs.n_queries > 0 && r1cs.n_multiplicities > 0)) } } @@ -105,8 +141,11 @@ impl Absorb for CommittedInstance { fn to_sponge_field_elements(&self, dest: &mut Vec) { self.u.to_sponge_field_elements(dest); self.x.to_sponge_field_elements(dest); - self.cmE.to_native_sponge_field_elements(dest); self.cmW.to_native_sponge_field_elements(dest); + if let Some(cmV) = &self.cmV { + cmV.to_native_sponge_field_elements(dest); + } + self.cmE.to_native_sponge_field_elements(dest); } } @@ -114,7 +153,11 @@ impl CommittedInstanceOps for CommittedInstance { type Var = CommittedInstanceVar; fn get_commitments(&self) -> Vec { - vec![self.cmW, self.cmE] + if let Some(cmV) = &self.cmV { + vec![self.cmW, *cmV, self.cmE] + } else { + vec![self.cmW, self.cmE] + } } fn is_incoming(&self) -> bool { @@ -129,8 +172,13 @@ impl Inputize> for CommittedInstance { [ &[self.u][..], &self.x, - &self.cmE.inputize_nonnative(), &self.cmW.inputize_nonnative(), + &if let Some(cmV) = &self.cmV { + cmV.inputize_nonnative() + } else { + vec![] + }, + &self.cmE.inputize_nonnative(), ] .concat() } @@ -142,24 +190,32 @@ pub struct Witness { pub rE: C::ScalarField, pub W: Vec, pub rW: C::ScalarField, + pub V: Vec, + pub rV: Option, } impl Witness { - pub fn new(w: Vec, e_len: usize, mut rng: impl RngCore) -> Self { + pub fn new( + w1: Vec, + e_len: usize, + mut rng: impl RngCore, + ) -> Self { let (rW, rE) = if H { ( C::ScalarField::rand(&mut rng), C::ScalarField::rand(&mut rng), ) } else { - (C::ScalarField::zero(), C::ScalarField::zero()) + (Zero::zero(), Zero::zero()) }; Self { E: vec![C::ScalarField::zero(); e_len], rE, - W: w, + W: w1, rW, + V: vec![], + rV: None, } } @@ -177,6 +233,7 @@ impl Witness { cmE, u: C::ScalarField::one(), cmW, + cmV: None, x, }) } @@ -187,8 +244,17 @@ impl Dummy<&R1CS>> for Witness { Self { E: vec![C::ScalarField::zero(); r1cs.A.n_rows], rE: C::ScalarField::zero(), - W: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l], + W: vec![ + C::ScalarField::zero(); + r1cs.A.n_cols - 1 - r1cs.l - r1cs.n_queries - r1cs.n_multiplicities + ], rW: C::ScalarField::zero(), + V: vec![C::ScalarField::zero(); r1cs.n_queries + r1cs.n_multiplicities], + rV: if r1cs.n_queries + r1cs.n_multiplicities > 0 { + Some(C::ScalarField::zero()) + } else { + None + }, } } } @@ -197,7 +263,11 @@ impl WitnessOps for Witness { type Var = WitnessVar; fn get_openings(&self) -> Vec<(&[C::ScalarField], C::ScalarField)> { - vec![(&self.W, self.rW), (&self.E, self.rE)] + if let Some(rV) = self.rV { + vec![(&self.W, self.rW), (&self.V, rV), (&self.E, self.rE)] + } else { + vec![(&self.W, self.rW), (&self.E, self.rE)] + } } } @@ -499,16 +569,20 @@ where // main circuit R1CS: let f_circuit = FC::new(fc_params)?; let cs = ConstraintSystem::::new_ref(); + cs.set_mode(SynthesisMode::Setup); let augmented_F_circuit = AugmentedFCircuit::::empty(&poseidon_config, f_circuit.clone()); augmented_F_circuit.generate_constraints(cs.clone())?; + cs.finalize_lookup()?; + let mut cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + LogUp::generate_verification_constraints(&mut cs, Zero::zero())?; cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let r1cs = extract_r1cs::(&cs)?; // CycleFold circuit R1CS let cs2 = ConstraintSystem::::new_ref(); - let cf_circuit = NovaCycleFoldCircuit::::empty(); + cs2.set_mode(SynthesisMode::Setup); + let cf_circuit = CycleFoldCircuit::<_, NovaCycleFoldConfig>::default(); cf_circuit.generate_constraints(cs2.clone())?; cs2.finalize(); let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; @@ -569,15 +643,19 @@ where // prepare the circuit to obtain its R1CS let cs = ConstraintSystem::::new_ref(); + cs.set_mode(SynthesisMode::Setup); let cs2 = ConstraintSystem::::new_ref(); + cs2.set_mode(SynthesisMode::Setup); let augmented_F_circuit = AugmentedFCircuit::::empty(&pp.poseidon_config, F.clone()); - let cf_circuit = NovaCycleFoldCircuit::::empty(); + let cf_circuit = CycleFoldCircuit::<_, NovaCycleFoldConfig>::default(); augmented_F_circuit.generate_constraints(cs.clone())?; + cs.finalize_lookup()?; + let mut cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + LogUp::generate_verification_constraints(&mut cs, Zero::zero())?; cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let r1cs = extract_r1cs::(&cs)?; cf_circuit.generate_constraints(cs2.clone())?; @@ -640,11 +718,14 @@ where } } // `sponge` is for digest computation. - let sponge = PoseidonSponge::::new(&self.poseidon_config); + let sponge = PoseidonSponge::::new_with_pp_hash( + &self.poseidon_config, + self.pp_hash, + ); // `transcript` is for challenge generation. let mut transcript = sponge.clone(); - let augmented_F_circuit: AugmentedFCircuit; + let mut augmented_F_circuit: AugmentedFCircuit; // Nova does not support (by design) multi-instances folding if _other_instances.is_some() { @@ -660,25 +741,8 @@ where )); } - if self.i > C1::ScalarField::from_le_bytes_mod_order(&usize::MAX.to_le_bytes()) { - return Err(Error::MaxStep); - } - - let i_usize; - - #[cfg(target_pointer_width = "64")] - { - let mut i_bytes: [u8; 8] = [0; 8]; - i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..8]); - i_usize = usize::from_le_bytes(i_bytes); - } - - #[cfg(target_pointer_width = "32")] - { - let mut i_bytes: [u8; 4] = [0; 4]; - i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..4]); - i_usize = usize::from_le_bytes(i_bytes); - } + let i_bn: BigUint = self.i.into(); + let i_usize: usize = i_bn.try_into().map_err(|_| Error::MaxStep)?; // fold Nova instances let (W_i1, U_i1, cmT, r_bits): (Witness, CommittedInstance, C1, Vec) = @@ -686,7 +750,6 @@ where &self.cs_pp, &self.r1cs, &mut transcript, - self.pp_hash, &self.W_i, &self.U_i, &self.w_i, @@ -695,25 +758,25 @@ where if self.i == C1::ScalarField::zero() { // base case - augmented_F_circuit = AugmentedFCircuit:: { - poseidon_config: self.poseidon_config.clone(), - pp_hash: Some(self.pp_hash), - i: Some(C1::ScalarField::zero()), // = i=0 - i_usize: Some(0), - z_0: Some(self.z_0.clone()), // = z_i - z_i: Some(self.z_i.clone()), - external_inputs: Some(external_inputs.clone()), - u_i_cmW: Some(self.u_i.cmW), // = dummy - U_i: Some(self.U_i.clone()), // = dummy - U_i1_cmE: Some(U_i1.cmE), - U_i1_cmW: Some(U_i1.cmW), - cmT: Some(cmT), - F: self.F.clone(), - cf1_u_i_cmW: None, - cf2_u_i_cmW: None, - cf_U_i: None, - cf1_cmT: None, - cf2_cmT: None, + augmented_F_circuit = AugmentedFCircuit::empty(&self.poseidon_config, self.F.clone()); + augmented_F_circuit.pp_hash = self.pp_hash; + augmented_F_circuit.z_0.clone_from(&self.z_0); + augmented_F_circuit.z_i.clone_from(&self.z_i); + augmented_F_circuit + .external_inputs + .clone_from(&external_inputs); + augmented_F_circuit.u_i_cmV = self.u_i.cmV; + augmented_F_circuit.U_i = Some(self.U_i.clone()); + augmented_F_circuit.U_i1_cmV = self.u_i.cmV; + augmented_F_circuit.cf3_u_i_cmW = if self.u_i.cmV.is_some() { + Some(Zero::zero()) + } else { + None + }; + augmented_F_circuit.cf3_cmT = if self.u_i.cmV.is_some() { + Some(Zero::zero()) + } else { + None }; #[cfg(test)] @@ -728,52 +791,73 @@ where } } else { // CycleFold part: - let cfW_circuit = NovaCycleFoldCircuit:: { - r_bits: Some(r_bits.clone()), - points: Some(vec![self.U_i.clone().cmW, self.u_i.clone().cmW]), - }; - let cfE_circuit = NovaCycleFoldCircuit:: { - r_bits: Some(r_bits.clone()), - points: Some(vec![self.U_i.clone().cmE, cmT]), + let (cfE_w_i, cfE_u_i) = NovaCycleFoldConfig { + r: r_bits.clone(), + points: vec![self.U_i.cmE, cmT], + } + .build_circuit() + .generate_incoming_instance_witness::<_, CS2, H>(&self.cf_cs_pp, &mut rng)?; + let (cfW_w_i, cfW_u_i) = NovaCycleFoldConfig { + r: r_bits.clone(), + points: vec![self.U_i.cmW, self.u_i.cmW], + } + .build_circuit() + .generate_incoming_instance_witness::<_, CS2, H>(&self.cf_cs_pp, &mut rng)?; + let (cfV_w_i, cfV_u_i) = match (self.U_i.cmV, self.u_i.cmV) { + (Some(cmV), Some(u_cmV)) => { + let (cfV_w_i, cfV_u_i) = NovaCycleFoldConfig { + r: r_bits.clone(), + points: vec![cmV, u_cmV], + } + .build_circuit() + .generate_incoming_instance_witness::<_, CS2, H>(&self.cf_cs_pp, &mut rng)?; + (Some(cfV_w_i), Some(cfV_u_i)) + } + _ => (None, None), }; - // fold self.cf_U_i + cfW_U -> folded running with cfW - let (cfW_u_i, cfW_W_i1, cfW_U_i1, cfW_cmT) = self.fold_cyclefold_circuit( + let (cf_W_i1, cf_U_i1, cf_cmTs) = CycleFoldAugmentationGadget::fold_native::<_, CS2, H>( &mut transcript, - self.cf_W_i.clone(), // CycleFold running instance witness - self.cf_U_i.clone(), // CycleFold running instance - cfW_circuit, - &mut rng, - )?; - // fold [the output from folding self.cf_U_i + cfW_U] + cfE_U = folded_running_with_cfW + cfE - let (cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT) = self.fold_cyclefold_circuit( - &mut transcript, - cfW_W_i1, - cfW_U_i1.clone(), - cfE_circuit, - &mut rng, + &self.cf_r1cs, + &self.cf_cs_pp, + self.cf_W_i.clone(), + self.cf_U_i.clone(), + if let Some(cfV_w_i) = cfV_w_i { + vec![cfE_w_i, cfW_w_i, cfV_w_i] + } else { + vec![cfE_w_i, cfW_w_i] + }, + if let Some(cfV_u_i) = cfV_u_i.clone() { + vec![cfE_u_i.clone(), cfW_u_i.clone(), cfV_u_i] + } else { + vec![cfE_u_i.clone(), cfW_u_i.clone()] + }, )?; augmented_F_circuit = AugmentedFCircuit:: { poseidon_config: self.poseidon_config.clone(), - pp_hash: Some(self.pp_hash), - i: Some(self.i), - i_usize: Some(i_usize), - z_0: Some(self.z_0.clone()), - z_i: Some(self.z_i.clone()), - external_inputs: Some(external_inputs.clone()), - u_i_cmW: Some(self.u_i.cmW), + pp_hash: self.pp_hash, + i: self.i, + i_usize, + z_0: self.z_0.clone(), + z_i: self.z_i.clone(), + external_inputs: external_inputs.clone(), + u_i_cmW: self.u_i.cmW, + u_i_cmV: self.u_i.cmV, U_i: Some(self.U_i.clone()), - U_i1_cmE: Some(U_i1.cmE), - U_i1_cmW: Some(U_i1.cmW), - cmT: Some(cmT), + U_i1_cmE: U_i1.cmE, + U_i1_cmW: U_i1.cmW, + U_i1_cmV: U_i1.cmV, + cmT, F: self.F.clone(), // cyclefold values - cf1_u_i_cmW: Some(cfW_u_i.cmW), - cf2_u_i_cmW: Some(cfE_u_i.cmW), - cf_U_i: Some(self.cf_U_i.clone()), - cf1_cmT: Some(cfW_cmT), - cf2_cmT: Some(cf_cmT), + cf1_u_i_cmW: cfE_u_i.cmW, + cf2_u_i_cmW: cfW_u_i.cmW, + cf3_u_i_cmW: cfV_u_i.map(|cfV_u_i| cfV_u_i.cmW), + cf_U_i: self.cf_U_i.clone(), + cf1_cmT: cf_cmTs[0], + cf2_cmT: cf_cmTs[1], + cf3_cmT: cf_cmTs.get(2).copied(), }; self.cf_W_i = cf_W_i1; @@ -786,22 +870,56 @@ where .compute_next_state(cs.clone())? .value()?; + cs.finalize_lookup()?; + let mut cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + + let w1_len = cs.num_witness_variables; + let (cmW, rW) = CS1::commit_with_rng(&self.cs_pp, &cs.witness_assignment, &mut rng)?; + LogUp::generate_verification_constraints(&mut cs, LogUp::challenge(&sponge, &cmW))?; + #[cfg(test)] assert!(cs.is_satisfied()?); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let (cmV, rV) = if cs.num_witness_variables > w1_len { + let (cmV, rV) = + CS1::commit_with_rng(&self.cs_pp, &cs.witness_assignment[w1_len..], &mut rng)?; + (Some(cmV), Some(rV)) + } else { + (None, None) + }; + let (w_i1, x_i1) = extract_w_x::(&cs); #[cfg(test)] - if x_i1.len() != 2 { - return Err(Error::NotExpectedLength(x_i1.len(), 2)); + if cs.num_witness_variables > w1_len { + if x_i1.len() != 3 { + return Err(Error::NotExpectedLength(x_i1.len(), 3)); + } + } else { + if x_i1.len() != 2 { + return Err(Error::NotExpectedLength(x_i1.len(), 2)); + } } // set values for next iteration self.i += C1::ScalarField::one(); self.z_i = z_i1; - self.w_i = Witness::::new::(w_i1, self.r1cs.A.n_rows, &mut rng); - self.u_i = self.w_i.commit::(&self.cs_pp, x_i1)?; + self.w_i.W = w_i1[..w1_len].to_vec(); + self.w_i.rW = rW; + self.w_i.V = w_i1[w1_len..].to_vec(); + self.w_i.rV = rV; + self.w_i.rE = if H { + C1::ScalarField::rand(&mut rng) + } else { + Zero::zero() + }; + self.u_i = CommittedInstance { + u: One::one(), + x: x_i1, + cmW, + cmV, + cmE: Zero::zero(), + }; self.W_i = W_i1; self.U_i = U_i1; @@ -856,7 +974,7 @@ where let cs2 = ConstraintSystem::::new_ref(); let augmented_F_circuit = AugmentedFCircuit::::empty(&pp.poseidon_config, f_circuit.clone()); - let cf_circuit = NovaCycleFoldCircuit::::empty(); + let cf_circuit = CycleFoldCircuit::<_, NovaCycleFoldConfig>::default(); augmented_F_circuit.generate_constraints(cs.clone())?; cs.finalize(); @@ -903,7 +1021,8 @@ where cf_U_i, } = ivc_proof; - let sponge = PoseidonSponge::::new(&vp.poseidon_config); + let sponge = + PoseidonSponge::::new_with_pp_hash(&vp.poseidon_config, vp.pp_hash()?); if num_steps == C1::ScalarField::zero() { if z_0 != z_i { @@ -912,86 +1031,60 @@ where return Ok(()); } - if u_i.x.len() != 2 || U_i.x.len() != 2 { + if u_i.x.len() != U_i.x.len() { return Err(Error::IVCVerificationFail); } - let pp_hash = vp.pp_hash()?; + let expected_x = match (U_i.cmV, u_i.cmV) { + (Some(_), Some(_)) => { + vec![ + // u_i.X[0] = H(i, z_0, z_i, U_i) + U_i.hash(&sponge, num_steps, &z_0, &z_i), + // u_i.X[1] = H(cf_U_i) + cf_U_i.hash_cyclefold(&sponge), + LogUp::challenge(&sponge, &u_i.cmW), + ] + } + (None, None) => { + vec![ + // u_i.X[0] = H(i, z_0, z_i, U_i) + U_i.hash(&sponge, num_steps, &z_0, &z_i), + // u_i.X[1] = H(cf_U_i) + cf_U_i.hash_cyclefold(&sponge), + ] + } + _ => return Err(Error::IVCVerificationFail), + }; - // check that u_i's output points to the running instance - // u_i.X[0] == H(i, z_0, z_i, U_i) - let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, &z_0, &z_i); - if expected_u_i_x != u_i.x[0] { - return Err(Error::IVCVerificationFail); - } - // u_i.X[1] == H(cf_U_i) - let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); - if expected_cf_u_i_x != u_i.x[1] { + if expected_x != u_i.x { return Err(Error::IVCVerificationFail); } // check R1CS satisfiability, which is equivalent to checking if `u_i` // is an incoming instance and if `w_i` and `u_i` satisfy RelaxedR1CS - u_i.check_incoming()?; - vp.r1cs.check_relation(&w_i, &u_i)?; + u_i.check_incoming().unwrap(); + vp.r1cs.check_relation(&w_i, &u_i).unwrap(); // check RelaxedR1CS satisfiability - vp.r1cs.check_relation(&W_i, &U_i)?; + vp.r1cs.check_relation(&W_i, &U_i).unwrap(); // check CycleFold RelaxedR1CS satisfiability - vp.cf_r1cs.check_relation(&cf_W_i, &cf_U_i)?; + vp.cf_r1cs.check_relation(&cf_W_i, &cf_U_i).unwrap(); Ok(()) } } -impl Nova -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - C1: Curve, -{ - // folds the given cyclefold circuit and its instances - #[allow(clippy::type_complexity)] - fn fold_cyclefold_circuit>( - &self, - transcript: &mut T, - cf_W_i: CycleFoldWitness, // witness of the running instance - cf_U_i: CycleFoldCommittedInstance, // running instance - cf_circuit: NovaCycleFoldCircuit, - rng: &mut impl RngCore, - ) -> Result< - ( - CycleFoldCommittedInstance, // u_i - CycleFoldWitness, // W_i1 - CycleFoldCommittedInstance, // U_i1 - C2, // cmT - ), - Error, - > { - fold_cyclefold_circuit::, C2, CS2, H>( - transcript, - self.cf_r1cs.clone(), - self.cf_cs_pp.clone(), - self.pp_hash, - cf_W_i, - cf_U_i, - cf_circuit, - rng, - ) - } -} - /// helper method to get the r1cs from the ConstraintSynthesizer pub fn get_r1cs_from_cs( circuit: impl ConstraintSynthesizer, ) -> Result, Error> { let cs = ConstraintSystem::::new_ref(); + cs.set_mode(SynthesisMode::Setup); circuit.generate_constraints(cs.clone())?; + cs.finalize_lookup()?; + let mut cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + LogUp::generate_verification_constraints(&mut cs, Zero::zero())?; cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let r1cs = extract_r1cs::(&cs)?; Ok(r1cs) } @@ -1009,7 +1102,7 @@ where C1: Curve, { let augmented_F_circuit = AugmentedFCircuit::::empty(poseidon_config, F_circuit); - let cf_circuit = NovaCycleFoldCircuit::::empty(); + let cf_circuit = CycleFoldCircuit::<_, NovaCycleFoldConfig>::default(); let r1cs = get_r1cs_from_cs::(augmented_F_circuit)?; let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; Ok((r1cs, cf_r1cs)) @@ -1036,28 +1129,65 @@ pub mod tests { use crate::commitment::kzg::KZG; use ark_bn254::{Bn254, Fr, G1Projective as Projective}; use ark_grumpkin::Projective as Projective2; + use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar}; + use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; + use ark_std::marker::PhantomData; use super::*; use crate::commitment::pedersen::Pedersen; - use crate::frontend::utils::CubicFCircuit; + use crate::frontend::alloc::LookupConstraintSystem; use crate::transcript::poseidon::poseidon_canonical_config; + #[cfg(test)] + #[derive(Clone, Copy, Debug)] + pub struct Circuit { + _f: PhantomData, + } + + #[cfg(test)] + impl FCircuit for Circuit { + type Params = (); + type ExternalInputs = (); + type ExternalInputsVar = (); + + fn new(_params: Self::Params) -> Result { + Ok(Self { _f: PhantomData }) + } + fn state_len(&self) -> usize { + 1 + } + fn generate_step_constraints( + &self, + cs: ConstraintSystemRef, + i: usize, + z_i: Vec>, + _external_inputs: Self::ExternalInputsVar, + ) -> Result>, SynthesisError> { + cs.init_lookup((0..256).map(F::from).collect())?; + let five = FpVar::::new_constant(cs.clone(), F::from(5u32))?; + let z_i = z_i[0].clone(); + let t = cs.new_query_fp(|| Ok(F::from(i as u64)))?; + + Ok(vec![&z_i * &z_i * &z_i + &t + &five]) + } + } + /// This test tests the Nova+CycleFold IVC, and by consequence it is also testing the /// AugmentedFCircuit #[test] fn test_ivc() -> Result<(), Error> { let poseidon_config = poseidon_canonical_config::(); - let F_circuit = CubicFCircuit::::new(())?; + let F_circuit = Circuit::::new(())?; // run the test using Pedersen commitments on both sides of the curve cycle - let _ = test_ivc_opt::, Pedersen, false>( + let _ = test_ivc_opt::<_, Pedersen, Pedersen, false>( poseidon_config.clone(), F_circuit, 3, )?; - let _ = test_ivc_opt::, Pedersen, true>( + let _ = test_ivc_opt::<_, Pedersen, Pedersen, true>( poseidon_config.clone(), F_circuit, 3, @@ -1065,7 +1195,7 @@ pub mod tests { // run the test using KZG for the commitments on the main curve, and Pedersen for the // commitments on the secondary curve - let _ = test_ivc_opt::, Pedersen, false>( + let _ = test_ivc_opt::<_, KZG, Pedersen, false>( poseidon_config, F_circuit, 3, @@ -1076,39 +1206,30 @@ pub mod tests { // test_ivc allowing to choose the CommitmentSchemes #[allow(clippy::type_complexity)] pub(crate) fn test_ivc_opt< + FC: FCircuit, CS1: CommitmentScheme, CS2: CommitmentScheme, const H: bool, >( poseidon_config: PoseidonConfig, - F_circuit: CubicFCircuit, + F_circuit: FC, num_steps: usize, - ) -> Result< - ( - Vec, - Nova, CS1, CS2, H>, - ), - Error, - > { + ) -> Result<(Vec, Nova), Error> { let mut rng = ark_std::test_rng(); - let prep_param = - PreprocessorParam::, CS1, CS2, H> { - poseidon_config, - F: F_circuit, - cs_pp: None, - cs_vp: None, - cf_cs_pp: None, - cf_cs_vp: None, - }; + let prep_param = PreprocessorParam:: { + poseidon_config, + F: F_circuit.clone(), + cs_pp: None, + cs_vp: None, + cf_cs_pp: None, + cf_cs_vp: None, + }; let nova_params = - Nova::, CS1, CS2, H>::preprocess( - &mut rng, - &prep_param, - )?; + Nova::::preprocess(&mut rng, &prep_param)?; let z_0 = vec![Fr::from(3_u32)]; - let mut nova = Nova::, CS1, CS2, H>::init( + let mut nova = Nova::::init( &nova_params, F_circuit, z_0.clone(), @@ -1118,6 +1239,10 @@ pub mod tests { nova.prove_step(&mut rng, (), None)?; } assert_eq!(Fr::from(num_steps as u32), nova.i); + Nova::::verify( + nova_params.1.clone(), // Nova's verifier params + nova.ivc_proof(), + )?; // serialize the Nova Prover & Verifier params. These params are the trusted setup of the commitment schemes used let mut nova_pp_serialized = vec![]; @@ -1134,19 +1259,13 @@ pub mod tests { ProverParams::::deserialize_compressed( &mut nova_pp_serialized.as_slice(), )?; - let nova_vp_deserialized = Nova::< - Projective, - Projective2, - CubicFCircuit, - CS1, - CS2, - H, - >::vp_deserialize_with_mode( - &mut nova_vp_serialized.as_slice(), - ark_serialize::Compress::Yes, - ark_serialize::Validate::Yes, - (), // fcircuit_params - )?; + let nova_vp_deserialized = + Nova::::vp_deserialize_with_mode( + &mut nova_vp_serialized.as_slice(), + ark_serialize::Compress::Yes, + ark_serialize::Validate::Yes, + (), // fcircuit_params + )?; let ivc_proof = nova.ivc_proof(); @@ -1157,14 +1276,14 @@ pub mod tests { .is_ok()); // deserialize IVCProof let ivc_proof_deserialized = - , CS1, CS2, H> as FoldingScheme< + as FoldingScheme< Projective, Projective2, - CubicFCircuit, + FC, >>::IVCProof::deserialize_compressed(ivc_proof_serialized.as_slice())?; // verify the deserialized IVCProof with the deserialized VerifierParams - Nova::, CS1, CS2, H>::verify( + Nova::::verify( nova_vp_deserialized, // Nova's verifier params ivc_proof_deserialized, )?; diff --git a/folding-schemes/src/folding/nova/nifs/mod.rs b/folding-schemes/src/folding/nova/nifs/mod.rs index 9b9fe3b1..b65744f6 100644 --- a/folding-schemes/src/folding/nova/nifs/mod.rs +++ b/folding-schemes/src/folding/nova/nifs/mod.rs @@ -68,7 +68,6 @@ pub trait NIFSTrait< cs_prover_params: &CS::ProverParams, r1cs: &R1CS, transcript: &mut T, - pp_hash: C::ScalarField, W_i: &Self::Witness, // running witness U_i: &Self::CommittedInstance, // running committed instance w_i: &Self::Witness, // incoming witness @@ -87,7 +86,6 @@ pub trait NIFSTrait< /// bits, so that it can be reused in other methods. fn verify( transcript: &mut T, - pp_hash: C::ScalarField, U_i: &Self::CommittedInstance, u_i: &Self::CommittedInstance, proof: &Self::Proof, @@ -113,7 +111,6 @@ pub trait NIFSGadgetTrait>, U_i: Self::CommittedInstanceVar, // U_i_vec is passed to reuse the already computed U_i_vec from previous methods U_i_vec: Vec>>, @@ -128,11 +125,10 @@ pub trait NIFSGadgetTrait::setup(&mut rng, r1cs.A.n_cols)?; let poseidon_config = poseidon_canonical_config::(); - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); let pp_hash = Fr::rand(&mut rng); + let mut transcript_p = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); + let mut transcript_v = transcript_p.clone(); // prepare the running instance let z = get_test_z(3); @@ -180,7 +176,6 @@ pub mod tests { &pedersen_params, &r1cs, &mut transcript_p, - pp_hash, &W_i, &U_i, &w_i, @@ -188,8 +183,7 @@ pub mod tests { )?; // NIFS.V - let (folded_committed_instance, _) = - N::verify(&mut transcript_v, pp_hash, &U_i, &u_i, &proof)?; + let (folded_committed_instance, _) = N::verify(&mut transcript_v, &U_i, &u_i, &proof)?; // set running_instance for next loop iteration W_i = folded_witness; @@ -221,13 +215,14 @@ pub mod tests { let (U_i, u_i) = (ci[0].clone(), ci[1].clone()); let pp_hash = Fr::rand(&mut rng); let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); - let (ci3, _) = N::verify(&mut transcript, pp_hash, &U_i, &u_i, &proof)?; + let mut transcript = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); + let (ci3, _) = N::verify(&mut transcript, &U_i, &u_i, &proof)?; let cs = ConstraintSystem::::new_ref(); - let mut transcriptVar = PoseidonSpongeVar::::new(cs.clone(), &poseidon_config); let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; + let mut transcriptVar = + PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; let ci1Var = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(U_i.clone()))?; let ci2Var = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(u_i.clone()))?; let proofVar = NG::ProofVar::new_witness(cs.clone(), || Ok(proof))?; @@ -235,7 +230,6 @@ pub mod tests { let ci1Var_vec = ci1Var.to_sponge_field_elements()?; let (out, _) = NG::verify( &mut transcriptVar, - pp_hashVar, ci1Var.clone(), ci1Var_vec, ci2Var.clone(), @@ -293,15 +287,15 @@ pub mod tests { N::CommittedInstance: CommittedInstanceOps, { let poseidon_config = poseidon_canonical_config::(); - let sponge = PoseidonSponge::::new(&poseidon_config); let pp_hash = Fr::from(42u32); // only for test + let sponge = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); let i = Fr::from(3_u32); let z_0 = vec![Fr::from(3_u32)]; let z_i = vec![Fr::from(3_u32)]; // compute the CommittedInstance hash natively - let h = ci.hash(&sponge, pp_hash, i, &z_0, &z_i); + let h = ci.hash(&sponge, i, &z_0, &z_i); let cs = ConstraintSystem::::new_ref(); @@ -311,10 +305,10 @@ pub mod tests { let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i.clone()))?; let ciVar = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(ci.clone()))?; - let sponge = PoseidonSpongeVar::::new(cs.clone(), &poseidon_config); + let sponge = PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; // compute the CommittedInstance hash in-circuit - let (hVar, _) = ciVar.hash(&sponge, &pp_hashVar, &iVar, &z_0Var, &z_iVar)?; + let (hVar, _) = ciVar.hash(&sponge, &iVar, &z_0Var, &z_iVar)?; assert!(cs.is_satisfied()?); // check that the natively computed and in-circuit computed hashes match diff --git a/folding-schemes/src/folding/nova/nifs/mova.rs b/folding-schemes/src/folding/nova/nifs/mova.rs index da3bdf7a..2ef209b0 100644 --- a/folding-schemes/src/folding/nova/nifs/mova.rs +++ b/folding-schemes/src/folding/nova/nifs/mova.rs @@ -200,7 +200,6 @@ impl, T: Transcript, const _cs_prover_params: &CS::ProverParams, // not used in Mova since we don't commit to T r1cs: &R1CS, transcript: &mut T, - pp_hash: C::ScalarField, W_i: &Witness, U_i: &CommittedInstance, w_i: &Witness, @@ -214,7 +213,6 @@ impl, T: Transcript, const ), Error, > { - transcript.absorb(&pp_hash); // Protocol 5 is pre-processing transcript.absorb(U_i); transcript.absorb(u_i); @@ -282,12 +280,10 @@ impl, T: Transcript, const /// returns the folded committed instance fn verify( transcript: &mut T, - pp_hash: C::ScalarField, U_i: &CommittedInstance, u_i: &CommittedInstance, proof: &Proof, ) -> Result<(Self::CommittedInstance, Vec), Error> { - transcript.absorb(&pp_hash); transcript.absorb(U_i); transcript.absorb(u_i); let rE_prime = PointVsLine::::verify( diff --git a/folding-schemes/src/folding/nova/nifs/nova.rs b/folding-schemes/src/folding/nova/nifs/nova.rs index da5c13bf..0752cac1 100644 --- a/folding-schemes/src/folding/nova/nifs/nova.rs +++ b/folding-schemes/src/folding/nova/nifs/nova.rs @@ -31,12 +31,10 @@ pub struct ChallengeGadget { impl ChallengeGadget { pub fn get_challenge_native>( transcript: &mut T, - pp_hash: C::ScalarField, // public params hash U_i: &CI, u_i: &CI, cmT: Option<&C>, ) -> Vec { - transcript.absorb(&pp_hash); transcript.absorb(&U_i); transcript.absorb(&u_i); // in the Nova case we absorb the cmT, in Ova case we don't since it is not used. @@ -53,12 +51,10 @@ impl ChallengeGadget { CIVar: AbsorbGadget>, >( transcript: &mut T, - pp_hash: FpVar>, // public params hash U_i_vec: Vec>>, // apready processed input, so we don't have to recompute these values u_i: CIVar, cmT: Option>, ) -> Result>, SynthesisError> { - transcript.absorb(&pp_hash)?; transcript.absorb(&U_i_vec)?; transcript.absorb(&u_i)?; // in the Nova case we absorb the cmT, in Ova case we don't since it is not used. @@ -102,7 +98,7 @@ impl, T: Transcript, const x: Vec, _aux: Vec, ) -> Result { - W.commit::(params, x) + unimplemented!() } fn fold_witness( @@ -127,14 +123,30 @@ impl, T: Transcript, const .collect(); let rW = W_i.rW + r * w_i.rW; - Ok(Self::Witness { E, rE, W, rW }) + let V: Vec = W_i + .V + .iter() + .zip(&w_i.V) + .map(|(a, b)| *a + (r * b)) + .collect(); + let rV = match (W_i.rV, w_i.rV) { + (Some(a), Some(b)) => Some(a + r * b), + _ => None, + }; + Ok(Self::Witness { + E, + rE, + W, + rW, + V, + rV, + }) } fn prove( cs_prover_params: &CS::ProverParams, r1cs: &R1CS, transcript: &mut T, - pp_hash: C::ScalarField, W_i: &Self::Witness, U_i: &Self::CommittedInstance, w_i: &Self::Witness, @@ -149,8 +161,8 @@ impl, T: Transcript, const Error, > { // compute the cross terms - let z1: Vec = [vec![U_i.u], U_i.x.to_vec(), W_i.W.to_vec()].concat(); - let z2: Vec = [vec![u_i.u], u_i.x.to_vec(), w_i.W.to_vec()].concat(); + let z1: Vec = [&[U_i.u][..], &U_i.x, &W_i.W, &W_i.V].concat(); + let z2: Vec = [&[u_i.u][..], &u_i.x, &w_i.W, &w_i.V].concat(); let T = Self::compute_T(r1cs, U_i.u, u_i.u, &z1, &z2, &W_i.E, &w_i.E)?; // use r_T=0 since we don't need hiding property for cm(T) @@ -158,7 +170,6 @@ impl, T: Transcript, const let r_bits = ChallengeGadget::::get_challenge_native( transcript, - pp_hash, U_i, u_i, Some(&cmT), @@ -175,14 +186,12 @@ impl, T: Transcript, const fn verify( transcript: &mut T, - pp_hash: C::ScalarField, U_i: &Self::CommittedInstance, u_i: &Self::CommittedInstance, cmT: &C, // Proof ) -> Result<(Self::CommittedInstance, Vec), Error> { let r_bits = ChallengeGadget::::get_challenge_native( transcript, - pp_hash, U_i, u_i, Some(cmT), @@ -251,6 +260,10 @@ impl, T: Transcript, const let cmE = U_i.cmE + cmT.mul(r) + u_i.cmE.mul(r2); let u = U_i.u + r * u_i.u; let cmW = U_i.cmW + u_i.cmW.mul(r); + let cmV = match (U_i.cmV, u_i.cmV) { + (Some(a), Some(b)) => Some(a + b.mul(r)), + _ => None, + }; let x = U_i .x .iter() @@ -258,7 +271,13 @@ impl, T: Transcript, const .map(|(a, b)| *a + (r * b)) .collect::>(); - CommittedInstance { cmE, u, cmW, x } + CommittedInstance { + cmE, + u, + cmW, + cmV, + x, + } } pub fn prove_commitments( diff --git a/folding-schemes/src/folding/nova/nifs/nova_circuits.rs b/folding-schemes/src/folding/nova/nifs/nova_circuits.rs index 2487c165..66cdd7c7 100644 --- a/folding-schemes/src/folding/nova/nifs/nova_circuits.rs +++ b/folding-schemes/src/folding/nova/nifs/nova_circuits.rs @@ -32,8 +32,9 @@ use super::nova::ChallengeGadget; pub struct CommittedInstanceVar { pub u: FpVar, pub x: Vec>, - pub cmE: NonNativeAffineVar, pub cmW: NonNativeAffineVar, + pub cmV: Option>, + pub cmE: NonNativeAffineVar, } impl AllocVar, CF1> for CommittedInstanceVar { @@ -49,12 +50,27 @@ impl AllocVar, CF1> for CommittedInstanceVar> = Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; - let cmE = - NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; let cmW = NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?; + let cmV = if let Some(cmV) = val.borrow().cmV { + Some(NonNativeAffineVar::::new_variable( + cs.clone(), + || Ok(cmV), + mode, + )?) + } else { + None + }; + let cmE = + NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; - Ok(Self { u, x, cmE, cmW }) + Ok(Self { + u, + x, + cmW, + cmV, + cmE, + }) }) } } @@ -68,8 +84,13 @@ impl AbsorbGadget for CommittedInstanceVar { Ok([ vec![self.u.clone()], self.x.clone(), - self.cmE.to_native_sponge_field_elements()?, self.cmW.to_native_sponge_field_elements()?, + if let Some(cmV) = &self.cmV { + cmV.to_native_sponge_field_elements()? + } else { + vec![] + }, + self.cmE.to_native_sponge_field_elements()?, ] .concat()) } @@ -79,7 +100,11 @@ impl CommittedInstanceVarOps for CommittedInstanceVar { type PointVar = NonNativeAffineVar; fn get_commitments(&self) -> Vec { - vec![self.cmW.clone(), self.cmE.clone()] + if let Some(cmV) = &self.cmV { + vec![self.cmW.clone(), cmV.clone(), self.cmE.clone()] + } else { + vec![self.cmW.clone(), self.cmE.clone()] + } } fn get_public_inputs(&self) -> &[FpVar>] { @@ -121,7 +146,6 @@ where fn verify( transcript: &mut T, - pp_hash: FpVar>, U_i: Self::CommittedInstanceVar, // U_i_vec is passed to reuse the already computed U_i_vec from previous methods U_i_vec: Vec>>, @@ -130,7 +154,6 @@ where ) -> Result<(Self::CommittedInstanceVar, Vec>>), SynthesisError> { let r_bits = ChallengeGadget::>::get_challenge_gadget( transcript, - pp_hash.clone(), U_i_vec, u_i.clone(), cmT.clone(), @@ -141,6 +164,14 @@ where Self::CommittedInstanceVar { cmE: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, cmW: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, + cmV: if U_i.cmV.is_some() { + Some(NonNativeAffineVar::new_constant( + ConstraintSystemRef::None, + C::zero(), + )?) + } else { + None + }, // ci3.u = U_i.u + r * u_i.u u: U_i.u + &r * u_i.u, // ci3.x = U_i.x + r * u_i.x @@ -185,6 +216,7 @@ pub mod tests { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), + cmV: None, x: vec![Fr::rand(&mut rng); 1], }) .collect(); @@ -206,6 +238,7 @@ pub mod tests { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), + cmV: None, x: vec![Fr::rand(&mut rng); 1], }; @@ -223,6 +256,7 @@ pub mod tests { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), + cmV: None, x: vec![Fr::rand(&mut rng); 1], }; test_committed_instance_hash_opt::< diff --git a/folding-schemes/src/folding/nova/nifs/ova.rs b/folding-schemes/src/folding/nova/nifs/ova.rs index 7e6757b8..7395e344 100644 --- a/folding-schemes/src/folding/nova/nifs/ova.rs +++ b/folding-schemes/src/folding/nova/nifs/ova.rs @@ -170,7 +170,6 @@ impl, T: Transcript, const _cs_prover_params: &CS::ProverParams, _r1cs: &R1CS, transcript: &mut T, - pp_hash: C::ScalarField, W_i: &Self::Witness, U_i: &Self::CommittedInstance, w_i: &Self::Witness, @@ -187,7 +186,7 @@ impl, T: Transcript, const let mut transcript_v = transcript.clone(); let r_bits = ChallengeGadget::::get_challenge_native( - transcript, pp_hash, U_i, u_i, None, // cmT not used in Ova + transcript, U_i, u_i, None, // cmT not used in Ova ); let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; @@ -195,7 +194,7 @@ impl, T: Transcript, const let w = Self::fold_witness(r_Fr, W_i, w_i, &())?; let proof = C::ScalarField::zero(); - let (ci, _r_bits_v) = Self::verify(&mut transcript_v, pp_hash, U_i, u_i, &proof)?; + let (ci, _r_bits_v) = Self::verify(&mut transcript_v, U_i, u_i, &proof)?; #[cfg(test)] assert_eq!(_r_bits_v, r_bits); @@ -204,13 +203,12 @@ impl, T: Transcript, const fn verify( transcript: &mut T, - pp_hash: C::ScalarField, U_i: &Self::CommittedInstance, u_i: &Self::CommittedInstance, _proof: &Self::Proof, // unused in Ova ) -> Result<(Self::CommittedInstance, Vec), Error> { let r_bits = ChallengeGadget::::get_challenge_native( - transcript, pp_hash, U_i, u_i, None, // cmT not used in Ova + transcript, U_i, u_i, None, // cmT not used in Ova ); let r = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; diff --git a/folding-schemes/src/folding/nova/nifs/ova_circuits.rs b/folding-schemes/src/folding/nova/nifs/ova_circuits.rs index 75c3eff7..a4396cd6 100644 --- a/folding-schemes/src/folding/nova/nifs/ova_circuits.rs +++ b/folding-schemes/src/folding/nova/nifs/ova_circuits.rs @@ -108,7 +108,6 @@ where fn verify( transcript: &mut T, - pp_hash: FpVar>, U_i: Self::CommittedInstanceVar, // U_i_vec is passed to reuse the already computed U_i_vec from previous methods U_i_vec: Vec>>, @@ -117,7 +116,6 @@ where ) -> Result<(Self::CommittedInstanceVar, Vec>>), SynthesisError> { let r_bits = ChallengeGadget::>::get_challenge_gadget( transcript, - pp_hash.clone(), U_i_vec, u_i.clone(), None, diff --git a/folding-schemes/src/folding/nova/traits.rs b/folding-schemes/src/folding/nova/traits.rs index 9e68e102..7501e36f 100644 --- a/folding-schemes/src/folding/nova/traits.rs +++ b/folding-schemes/src/folding/nova/traits.rs @@ -49,7 +49,7 @@ impl Arith, CommittedInstance> for R1CS> { w: &Witness, u: &CommittedInstance, ) -> Result { - self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat()) + self.eval_at_z(&[&[u.u][..], &u.x, &w.W, &w.V].concat()) } fn check_evaluation( @@ -85,7 +85,14 @@ impl ArithSampler, CommittedInstance> for R1CS let E = self.eval_at_z(&z)?; - let witness = Witness { E, rE, W, rW }; + let witness = Witness { + E, + rE, + W, + rW, + V: vec![], + rV: None, + }; let mut cm_witness = witness.commit::(params, x)?; // witness.commit() sets u to 1, we set it to the sampled u value @@ -112,7 +119,7 @@ impl ArithGadget, CommittedInstanceVar> w: &WitnessVar, u: &CommittedInstanceVar, ) -> Result { - self.eval_at_z(&[&[u.u.clone()][..], &u.x, &w.W].concat()) + self.eval_at_z(&[&[u.u.clone()][..], &u.x, &w.W, &w.V].concat()) } fn enforce_evaluation( diff --git a/folding-schemes/src/folding/nova/zk.rs b/folding-schemes/src/folding/nova/zk.rs index 2f809973..dc077758 100644 --- a/folding-schemes/src/folding/nova/zk.rs +++ b/folding-schemes/src/folding/nova/zk.rs @@ -30,10 +30,7 @@ /// paper). /// And the Use-case-2 would require a modified version of the Decider circuits. /// -use ark_crypto_primitives::sponge::{ - poseidon::{PoseidonConfig, PoseidonSponge}, - CryptographicSponge, -}; +use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; use ark_std::{rand::RngCore, One, Zero}; use super::{ @@ -45,6 +42,7 @@ use crate::{ commitment::CommitmentScheme, folding::traits::CommittedInstanceOps, frontend::FCircuit, + transcript::Transcript, Curve, Error, }; @@ -71,7 +69,10 @@ impl RandomizedIVCProof { nova: &Nova, mut rng: impl RngCore, ) -> Result, Error> { - let mut transcript = PoseidonSponge::::new(&nova.poseidon_config); + let mut transcript = PoseidonSponge::::new_with_pp_hash( + &nova.poseidon_config, + nova.pp_hash, + ); // I. Compute proof for 'regular' instances // 1. Fold the instance-witness pairs (U_i, W_i) with (u_i, w_i) @@ -79,7 +80,6 @@ impl RandomizedIVCProof { &nova.cs_pp, &nova.r1cs, &mut transcript, - nova.pp_hash, &nova.w_i, &nova.u_i, &nova.W_i, @@ -97,7 +97,6 @@ impl RandomizedIVCProof { &nova.cs_pp, &nova.r1cs, &mut transcript, - nova.pp_hash, &W_f, &U_f, &W_r, @@ -148,13 +147,14 @@ impl RandomizedIVCProof { } // b. Check computed hashes are correct - let sponge = PoseidonSponge::::new(poseidon_config); - let expected_u_i_x = proof.U_i.hash(&sponge, pp_hash, i, &z_0, &z_i); + let sponge = PoseidonSponge::::new_with_pp_hash(poseidon_config, pp_hash); + let mut transcript = sponge.clone(); + let expected_u_i_x = proof.U_i.hash(&sponge, i, &z_0, &z_i); if expected_u_i_x != proof.u_i.x[0] { return Err(Error::zkIVCVerificationFail); } - let expected_cf_u_i_x = proof.cf_U_i.hash_cyclefold(&sponge, pp_hash); + let expected_cf_u_i_x = proof.cf_U_i.hash_cyclefold(&sponge); if expected_cf_u_i_x != proof.u_i.x[1] { return Err(Error::IVCVerificationFail); } @@ -164,11 +164,9 @@ impl RandomizedIVCProof { return Err(Error::zkIVCVerificationFail); } - let mut transcript = PoseidonSponge::::new(poseidon_config); // 3. Obtain the U_f folded instance let (U_f, _) = NIFS::, true>::verify( &mut transcript, - pp_hash, &proof.u_i, &proof.U_i, &proof.pi, @@ -177,7 +175,6 @@ impl RandomizedIVCProof { // 4. Obtain the U^{\prime}_i folded instance let (U_i_prime, _) = NIFS::, true>::verify( &mut transcript, - pp_hash, &U_f, &proof.U_r, &proof.pi_prime, @@ -211,6 +208,7 @@ pub mod tests { let poseidon_config = poseidon_canonical_config::(); let F_circuit = CubicFCircuit::::new(())?; let (_, nova) = test_ivc_opt::< + _, Pedersen, Pedersen, true, @@ -238,6 +236,7 @@ pub mod tests { let poseidon_config = poseidon_canonical_config::(); let F_circuit = CubicFCircuit::::new(())?; let (_, nova) = test_ivc_opt::< + _, Pedersen, Pedersen, true, @@ -265,6 +264,7 @@ pub mod tests { let poseidon_config = poseidon_canonical_config::(); let F_circuit = CubicFCircuit::::new(())?; let (_, nova) = test_ivc_opt::< + _, Pedersen, Pedersen, true, @@ -299,6 +299,7 @@ pub mod tests { let poseidon_config = poseidon_canonical_config::(); let F_circuit = CubicFCircuit::::new(())?; let (_, nova) = test_ivc_opt::< + _, Pedersen, Pedersen, true, diff --git a/folding-schemes/src/folding/protogalaxy/circuits.rs b/folding-schemes/src/folding/protogalaxy/circuits.rs index 3bfac847..c8605c24 100644 --- a/folding-schemes/src/folding/protogalaxy/circuits.rs +++ b/folding-schemes/src/folding/protogalaxy/circuits.rs @@ -1,22 +1,18 @@ use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, + poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, CryptographicSponge, }; -use ark_ff::PrimeField; use ark_poly::{univariate::DensePolynomial, EvaluationDomain, GeneralEvaluationDomain}; use ark_r1cs_std::{ alloc::AllocVar, - boolean::Boolean, convert::ToBitsGadget, eq::EqGadget, fields::{fp::FpVar, FieldVar}, - groups::CurveVar, poly::polynomial::univariate::dense::DensePolynomialVar, R1CSVar, }; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_std::{fmt::Debug, One, Zero}; +use ark_std::{fmt::Debug, Zero}; use super::{ folding::lagrange_polys, @@ -27,16 +23,16 @@ use crate::{ folding::{ circuits::{ cyclefold::{ - CycleFoldChallengeGadget, CycleFoldCommittedInstance, - CycleFoldCommittedInstanceVar, CycleFoldConfig, NIFSFullGadget, + CycleFoldAugmentationGadget, CycleFoldCommittedInstance, + CycleFoldCommittedInstanceVar, CycleFoldConfig, }, - nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, + nonnative::affine::NonNativeAffineVar, CF1, }, traits::{CommittedInstanceVarOps, Dummy}, }, frontend::FCircuit, - transcript::{AbsorbNonNativeGadget, TranscriptVar}, + transcript::TranscriptVar, utils::gadgets::VectorGadget, Curve, }; @@ -169,51 +165,6 @@ impl AugmentationGadget { Ok((U, L_X_evals)) } - - pub fn prepare_and_fold_cyclefold< - C1: Curve, - C2: Curve, - S: CryptographicSponge, - >( - transcript: &mut PoseidonSpongeVar>, - pp_hash: FpVar>, - mut cf_U: CycleFoldCommittedInstanceVar, - cf_u_cmWs: Vec, - cf_u_xs: Vec>>>, - cf_cmTs: Vec, - ) -> Result, SynthesisError> { - assert_eq!(cf_u_cmWs.len(), cf_u_xs.len()); - assert_eq!(cf_u_xs.len(), cf_cmTs.len()); - - // Fold the incoming CycleFold instances into the running CycleFold - // instance in a iterative way, since `NIFSFullGadget` only supports - // folding one incoming instance at a time. - for ((cmW, x), cmT) in cf_u_cmWs.into_iter().zip(cf_u_xs).zip(cf_cmTs) { - // Prepare the incoming CycleFold instance `cf_u` for the current - // iteration. - // For each CycleFold instance `cf_u`, we have `cf_u.cmE = 0`, and - // `cf_u.u = 1`. - let cf_u = CycleFoldCommittedInstanceVar { - cmE: C2::Var::zero(), - u: NonNativeUintVar::new_constant(ConstraintSystemRef::None, C1::BaseField::one())?, - cmW, - x, - }; - - let cf_r_bits = CycleFoldChallengeGadget::get_challenge_gadget( - transcript, - pp_hash.clone(), - cf_U.to_native_sponge_field_elements()?, - cf_u.clone(), - cmT.clone(), - )?; - // Fold the current incoming CycleFold instance `cf_u` into the - // running CycleFold instance `cf_U`. - cf_U = NIFSFullGadget::fold_committed_instance(cf_r_bits, cmT, cf_U, cf_u)?; - } - - Ok(cf_U) - } } /// `AugmentedFCircuit` enhances the original step function `F`, so that it can @@ -246,13 +197,9 @@ pub struct AugmentedFCircuit>> { pub(super) F_coeffs: Vec>, pub(super) K_coeffs: Vec>, - pub(super) phi_stars: Vec, - - pub(super) cf1_u_i_cmW: C2, // input - pub(super) cf2_u_i_cmW: C2, // input + pub(super) cf_u_i_cmW: C2, // input pub(super) cf_U_i: CycleFoldCommittedInstance, // input - pub(super) cf1_cmT: C2, - pub(super) cf2_cmT: C2, + pub(super) cf_cmT: C2, } impl>> AugmentedFCircuit { @@ -265,7 +212,7 @@ impl>> AugmentedFCircuit ) -> Self { let u_dummy = CommittedInstance::dummy((2, t)); let cf_u_dummy = - CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::::IO_LEN); + CycleFoldCommittedInstance::dummy((ProtoGalaxyCycleFoldConfig::::IO_LEN, false)); Self { poseidon_config: poseidon_config.clone(), @@ -280,14 +227,11 @@ impl>> AugmentedFCircuit U_i1_phi: C1::zero(), F_coeffs: vec![CF1::::zero(); t], K_coeffs: vec![CF1::::zero(); d * k + 1], - phi_stars: vec![C1::zero(); k], F: F_circuit, // cyclefold values - cf1_u_i_cmW: C2::zero(), - cf2_u_i_cmW: C2::zero(), + cf_u_i_cmW: C2::zero(), cf_U_i: cf_u_dummy, - cf1_cmT: C2::zero(), - cf2_cmT: C2::zero(), + cf_cmT: C2::zero(), } } } @@ -313,21 +257,18 @@ where let U_i = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(self.U_i))?; let u_i_phi = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.u_i_phi))?; let U_i1_phi = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.U_i1_phi))?; - let phi_stars = - Vec::>::new_witness(cs.clone(), || Ok(self.phi_stars))?; let cf_u_dummy = - CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::::IO_LEN); + CycleFoldCommittedInstance::dummy((ProtoGalaxyCycleFoldConfig::::IO_LEN, false)); let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || Ok(self.cf_U_i))?; - let cf1_cmT = C2::Var::new_witness(cs.clone(), || Ok(self.cf1_cmT))?; - let cf2_cmT = C2::Var::new_witness(cs.clone(), || Ok(self.cf2_cmT))?; + let cf_cmT = C2::Var::new_witness(cs.clone(), || Ok(self.cf_cmT))?; let F_coeffs = Vec::new_witness(cs.clone(), || Ok(self.F_coeffs))?; let K_coeffs = Vec::new_witness(cs.clone(), || Ok(self.K_coeffs))?; // `sponge` is for digest computation. - let sponge = PoseidonSpongeVar::::new(cs.clone(), &self.poseidon_config); + let sponge = PoseidonSpongeVar::new_with_pp_hash(&self.poseidon_config, &pp_hash)?; // `transcript` is for challenge generation. let mut transcript = sponge.clone(); @@ -336,9 +277,9 @@ where // Primary Part // P.1. Compute u_i.x // u_i.x[0] = H(i, z_0, z_i, U_i) - let (u_i_x, _) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?; + let (u_i_x, _) = U_i.clone().hash(&sponge, &i, &z_0, &z_i)?; // u_i.x[1] = H(cf_U_i) - let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?; + let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge)?; // P.2. Prepare incoming primary instances // P.3. Fold incoming primary instances into the running instance @@ -361,16 +302,11 @@ where // Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot}) // Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1}) - let (u_i1_x, _) = U_i1.clone().hash( - &sponge, - &pp_hash, - &(i + FpVar::>::one()), - &z_0, - &z_i1, - )?; + let (u_i1_x, _) = + U_i1.clone() + .hash(&sponge, &(i + FpVar::>::one()), &z_0, &z_i1)?; let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash( &sponge, - &pp_hash, &FpVar::>::one(), &z_0, &z_i1, @@ -388,73 +324,42 @@ where FpVar::new_input(cs.clone(), || x.value())?.enforce_equal(&x)?; // CycleFold part - // C.1. Compute cf1_u_i.x and cf2_u_i.x - let mut r0_bits = r[0].to_bits_le()?; - let mut r1_bits = r[1].to_bits_le()?; - r0_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - r1_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - let cf1_x = [ - r0_bits - .chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1) - .map(|bits| { - let mut bits = bits.to_vec(); - bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - NonNativeUintVar::from(&bits) - }) - .collect::>(), - vec![ - NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?, - NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?, - U_i.phi.x.clone(), - U_i.phi.y.clone(), - phi_stars[0].x.clone(), - phi_stars[0].y.clone(), - ], - ] - .concat(); - let cf2_x = [ - r1_bits - .chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1) - .map(|bits| { - let mut bits = bits.to_vec(); - bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - NonNativeUintVar::from(&bits) - }) - .collect::>(), - vec![ - phi_stars[0].x.clone(), - phi_stars[0].y.clone(), - u_i_phi.x.clone(), - u_i_phi.y.clone(), - U_i1.phi.x.clone(), - U_i1.phi.y.clone(), - ], - ] - .concat(); + // C.1. Compute `cf_u_i.x` + // C.2. Construct `cf_u_i` + let cf_u_i = CycleFoldCommittedInstanceVar::new_incoming_from_components( + // `cf_u_i.cmW` is provided by the prover as witness. + C2::Var::new_witness(cs.clone(), || Ok(self.cf_u_i_cmW))?, + // To construct `cf_u_i.x`, we need to provide the randomness `r` as + // well as the `phi` component in committed instances `U_i`, `u_i`, + // and `U_{i+1}`. + // Note that the randomness `r` is converted to `r_0, r_1 / r_0` due + // to how `ProtoGalaxyCycleFoldConfig::alloc_randomnesses` creates + // randomness in the CycleFold circuit. + &[ + r[0].to_bits_le()?, + r[1].mul_by_inverse(&r[0])?.to_bits_le()?, + ] + .concat(), + vec![U_i.phi, u_i_phi, U_i1.phi], + )?; // C.2. Prepare incoming CycleFold instances // C.3. Fold incoming CycleFold instances into the running instance - let cf_U_i1 = - AugmentationGadget::prepare_and_fold_cyclefold::>>( - &mut transcript, - pp_hash.clone(), - cf_U_i, - vec![ - C2::Var::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW))?, - C2::Var::new_witness(cs.clone(), || Ok(self.cf2_u_i_cmW))?, - ], - vec![cf1_x, cf2_x], - vec![cf1_cmT, cf2_cmT], - )?; + let cf_U_i1 = CycleFoldAugmentationGadget::fold_gadget( + &mut transcript, + cf_U_i, + vec![cf_u_i], + vec![cf_cmT], + )?; // Back to Primary Part // P.4.b compute and check the second output of F' // Base case: u_{i+1}.x[1] == H(cf_U_{\bot}) // Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1}) - let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge, pp_hash.clone())?; + let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge)?; let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? - .hash(&sponge, pp_hash.clone())?; + .hash(&sponge)?; let cf_x = is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?; // This line "converts" `cf_x` from a witness to a public input. // Instead of directly modifying the constraint system, we explicitly @@ -489,11 +394,12 @@ mod tests { use crate::{ arith::r1cs::tests::get_test_r1cs, folding::protogalaxy::folding::{tests::prepare_inputs, Folding}, - transcript::poseidon::poseidon_canonical_config, + transcript::{poseidon::poseidon_canonical_config, Transcript}, Error, }; use ark_bn254::{Fr, G1Projective as Projective}; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; use ark_relations::r1cs::ConstraintSystem; #[test] @@ -504,8 +410,9 @@ mod tests { // init Prover & Verifier's transcript let poseidon_config = poseidon_canonical_config::(); - let mut transcript_p = PoseidonSponge::new(&poseidon_config); - let mut transcript_v = PoseidonSponge::new(&poseidon_config); + let pp_hash = Fr::from(42u32); // only for testing + let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); + let mut transcript_v = transcript_p.clone(); let (_, _, proof, _) = Folding::::prove( &mut transcript_p, @@ -520,7 +427,9 @@ mod tests { Folding::::verify(&mut transcript_v, &instance, &instances, proof.clone())?; let cs = ConstraintSystem::new_ref(); - let mut transcript_var = PoseidonSpongeVar::new(cs.clone(), &poseidon_config); + let pp_hash_var = FpVar::new_witness(cs.clone(), || Ok(pp_hash))?; + let mut transcript_var = + PoseidonSpongeVar::new_with_pp_hash(&poseidon_config, &pp_hash_var)?; let instance_var = CommittedInstanceVar::new_witness(cs.clone(), || Ok(instance))?; let instances_var = Vec::new_witness(cs.clone(), || Ok(instances))?; let F_coeffs_var = Vec::new_witness(cs.clone(), || Ok(proof.F_coeffs))?; diff --git a/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs b/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs index da9ce4e3..0984453d 100644 --- a/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs @@ -1,15 +1,12 @@ /// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases, /// other more efficient approaches can be used. -use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, - CryptographicSponge, -}; +use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, eq::EqGadget, fields::fp::FpVar, + R1CSVar, }; use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_std::{borrow::Borrow, marker::PhantomData}; @@ -28,6 +25,7 @@ use crate::{ traits::{WitnessOps, WitnessVarOps}, }, frontend::FCircuit, + transcript::Transcript, Curve, Error, }; @@ -92,7 +90,8 @@ impl< type Error = Error; fn try_from(protogalaxy: ProtoGalaxy) -> Result { - let mut transcript = PoseidonSponge::::new(&protogalaxy.poseidon_config); + let mut transcript = + PoseidonSponge::new_with_pp_hash(&protogalaxy.poseidon_config, protogalaxy.pp_hash); let (U_i1, W_i1, proof, aux) = Folding::prove( &mut transcript, @@ -159,14 +158,13 @@ impl fn fold_field_elements_gadget( _arith: &R1CS>, transcript: &mut PoseidonSpongeVar>, - _pp_hash: FpVar>, U: CommittedInstanceVar, _U_vec: Vec>>, u: CommittedInstanceVar, proof: Self::Proof, randomness: Self::Randomness, ) -> Result, SynthesisError> { - let cs = transcript.cs(); + let cs = U.e.cs(); let F_coeffs = Vec::new_witness(cs.clone(), || Ok(&proof.F_coeffs[..]))?; let K_coeffs = Vec::new_witness(cs.clone(), || Ok(&proof.K_coeffs[..]))?; let randomness = Vec::new_input(cs.clone(), || Ok(randomness))?; diff --git a/folding-schemes/src/folding/protogalaxy/folding.rs b/folding-schemes/src/folding/protogalaxy/folding.rs index 4fc40594..2d3a220a 100644 --- a/folding-schemes/src/folding/protogalaxy/folding.rs +++ b/folding-schemes/src/folding/protogalaxy/folding.rs @@ -403,7 +403,6 @@ pub fn lagrange_polys( pub mod tests { use super::*; use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_crypto_primitives::sponge::CryptographicSponge; use ark_pallas::{Fr, Projective}; use ark_std::{rand::Rng, UniformRand}; @@ -494,8 +493,9 @@ pub mod tests { // init Prover & Verifier's transcript let poseidon_config = poseidon_canonical_config::(); - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::from(42u32); // only for testing + let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); + let mut transcript_v = transcript_p.clone(); let (folded_instance, folded_witness, proof, _) = Folding::::prove( &mut transcript_p, @@ -527,8 +527,9 @@ pub mod tests { // init Prover & Verifier's transcript let poseidon_config = poseidon_canonical_config::(); - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::from(42u32); // only for testing + let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); + let mut transcript_v = transcript_p.clone(); let (mut running_witness, mut running_instance, _, _) = prepare_inputs(0)?; diff --git a/folding-schemes/src/folding/protogalaxy/mod.rs b/folding-schemes/src/folding/protogalaxy/mod.rs index 32fa797f..20f65ad0 100644 --- a/folding-schemes/src/folding/protogalaxy/mod.rs +++ b/folding-schemes/src/folding/protogalaxy/mod.rs @@ -1,22 +1,18 @@ /// Implements the scheme described in [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf) -use ark_crypto_primitives::sponge::{ - poseidon::{PoseidonConfig, PoseidonSponge}, - CryptographicSponge, -}; +use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, eq::EqGadget, fields::{fp::FpVar, FieldVar}, + prelude::Boolean, R1CSVar, }; use ark_relations::r1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, }; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid}; -use ark_std::{ - borrow::Borrow, cmp::max, fmt::Debug, log2, marker::PhantomData, rand::RngCore, One, Zero, -}; +use ark_std::{borrow::Borrow, cmp::max, fmt::Debug, log2, rand::RngCore, One, Zero}; use constants::{INCOMING, RUNNING}; use num_bigint::BigUint; @@ -28,14 +24,14 @@ use crate::{ commitment::CommitmentScheme, folding::circuits::{ cyclefold::{ - fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig, + CycleFoldAugmentationGadget, CycleFoldCommittedInstance, CycleFoldConfig, CycleFoldWitness, }, nonnative::affine::NonNativeAffineVar, CF1, }, frontend::{utils::DummyCircuit, FCircuit}, - transcript::poseidon::poseidon_canonical_config, + transcript::{poseidon::poseidon_canonical_config, Transcript}, utils::pp_hash, Curve, Error, FoldingScheme, }; @@ -51,24 +47,58 @@ pub(crate) mod utils; use circuits::AugmentedFCircuit; use folding::Folding; -use super::traits::{ - CommittedInstanceOps, CommittedInstanceVarOps, Dummy, Inputize, WitnessOps, WitnessVarOps, +use super::{ + circuits::{cyclefold::CycleFoldCircuit, CF2}, + traits::{ + CommittedInstanceOps, CommittedInstanceVarOps, Dummy, Inputize, WitnessOps, WitnessVarOps, + }, }; /// Configuration for ProtoGalaxy's CycleFold circuit pub struct ProtoGalaxyCycleFoldConfig { - _c: PhantomData, + rs: Vec>, + points: Vec, +} + +impl Default for ProtoGalaxyCycleFoldConfig { + fn default() -> Self { + Self { + rs: vec![CF1::::one(); 2], + points: vec![C::zero(); 2], + } + } } -impl CycleFoldConfig for ProtoGalaxyCycleFoldConfig { +impl CycleFoldConfig for ProtoGalaxyCycleFoldConfig { const RANDOMNESS_BIT_LENGTH: usize = C::ScalarField::MODULUS_BIT_SIZE as usize; + const N_UNIQUE_RANDOMNESSES: usize = 2; const N_INPUT_POINTS: usize = 2; - type C = C; -} -/// CycleFold circuit for computing random linear combinations of group elements -/// in ProtoGalaxy instances. -pub type ProtoGalaxyCycleFoldCircuit = CycleFoldCircuit>; + fn alloc_points(&self, cs: ConstraintSystemRef>) -> Result, SynthesisError> { + let points = Vec::new_witness(cs.clone(), || Ok(self.points.clone()))?; + for point in &points { + Self::mark_point_as_public(point)?; + } + Ok(points) + } + + fn alloc_randomnesses( + &self, + cs: ConstraintSystemRef>, + ) -> Result>>>, SynthesisError> { + let rs = vec![self.rs[0]] + .into_iter() + .chain(self.rs.windows(2).map(|r| r[1] / r[0])) + .map(|r| { + let mut bits = r.into_bigint().to_bits_le(); + bits.resize(CF1::::MODULUS_BIT_SIZE as usize, false); + Vec::new_witness(cs.clone(), || Ok(bits)) + }) + .collect::, _>>()?; + Self::mark_randomness_as_public(&rs.concat())?; + Ok(rs) + } +} /// The committed instance of ProtoGalaxy. /// @@ -673,7 +703,7 @@ where // CycleFold circuit R1CS let cs2 = ConstraintSystem::::new_ref(); - let cf_circuit = ProtoGalaxyCycleFoldCircuit::::empty(); + let cf_circuit = CycleFoldCircuit::<_, ProtoGalaxyCycleFoldConfig>::default(); cf_circuit.generate_constraints(cs2.clone())?; cs2.finalize(); let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; @@ -712,7 +742,7 @@ where let augmented_F_circuit = AugmentedFCircuit::::empty(poseidon_config, F.clone(), t, d, k); - let cf_circuit = ProtoGalaxyCycleFoldCircuit::::empty(); + let cf_circuit = CycleFoldCircuit::<_, ProtoGalaxyCycleFoldConfig>::default(); augmented_F_circuit.generate_constraints(cs.clone())?; cs.finalize(); @@ -803,7 +833,10 @@ where let d = 2; // `sponge` is for digest computation. - let sponge = PoseidonSponge::::new(&self.poseidon_config); + let sponge = PoseidonSponge::::new_with_pp_hash( + &self.poseidon_config, + self.pp_hash, + ); // `transcript` is for challenge generation. let mut transcript_prover = sponge.clone(); @@ -850,42 +883,26 @@ where )?; // CycleFold part: - let mut r0_bits = aux.L_X_evals[0].into_bigint().to_bits_le(); - let mut r1_bits = aux.L_X_evals[1].into_bigint().to_bits_le(); - r0_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, false); - r1_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, false); - - // cyclefold circuit for enforcing: - // 0 + U_i.phi * L_evals[0] == phi_stars[0] - let cf1_circuit = ProtoGalaxyCycleFoldCircuit:: { - r_bits: Some(r0_bits), - points: Some(vec![C1::zero(), self.U_i.phi]), - }; - - // cyclefold circuit for enforcing: - // phi_stars[0] + u_i.phi * L_evals[1] == U_i1.phi - // i.e., U_i.phi * L_evals[0] + u_i.phi * L_evals[1] == U_i1.phi - let cf2_circuit = ProtoGalaxyCycleFoldCircuit:: { - r_bits: Some(r1_bits), - points: Some(vec![aux.phi_stars[0], self.u_i.phi]), - }; - - // fold self.cf_U_i + cf1_U -> folded running with cf1 - let (cf1_u_i, cf1_W_i1, cf1_U_i1, cf1_cmT) = self.fold_cyclefold_circuit( - &mut transcript_prover, - self.cf_W_i.clone(), // CycleFold running instance witness - self.cf_U_i.clone(), // CycleFold running instance - cf1_circuit, - &mut rng, - )?; - // fold [the output from folding self.cf_U_i + cf1_U] + cf2_U = folded_running_with_cf1 + cf2 - let (cf2_u_i, cf_W_i1, cf_U_i1, cf2_cmT) = self.fold_cyclefold_circuit( - &mut transcript_prover, - cf1_W_i1, - cf1_U_i1.clone(), - cf2_circuit, - &mut rng, - )?; + // Create cyclefold circuit for enforcing: + // U_i.phi * L_evals[0] + u_i.phi * L_evals[1] = U_i1.phi + let (cf_w_i, cf_u_i) = ProtoGalaxyCycleFoldConfig { + rs: aux.L_X_evals, + points: vec![self.U_i.phi, self.u_i.phi], + } + .build_circuit() + .generate_incoming_instance_witness::<_, CS2, false>(&self.cf_cs_params, &mut rng)?; + + // fold cf_U_i + cf_u_i -> folded running instance cf_U_i1 + let (cf_W_i1, cf_U_i1, cf_cmTs) = + CycleFoldAugmentationGadget::fold_native::<_, CS2, false>( + &mut transcript_prover, + &self.cf_r1cs, + &self.cf_cs_params, + self.cf_W_i.clone(), + self.cf_U_i.clone(), + vec![cf_w_i], + vec![cf_u_i.clone()], + )?; augmented_F_circuit = AugmentedFCircuit { poseidon_config: self.poseidon_config.clone(), @@ -900,14 +917,11 @@ where U_i1_phi: U_i1.phi, F_coeffs: proof.F_coeffs.clone(), K_coeffs: proof.K_coeffs.clone(), - phi_stars: aux.phi_stars, F: self.F.clone(), // cyclefold values - cf1_u_i_cmW: cf1_u_i.cmW, - cf2_u_i_cmW: cf2_u_i.cmW, + cf_u_i_cmW: cf_u_i.cmW, cf_U_i: self.cf_U_i.clone(), - cf1_cmT, - cf2_cmT, + cf_cmT: cf_cmTs[0], }; #[cfg(test)] @@ -1035,22 +1049,20 @@ where cf_U_i, } = ivc_proof; - let sponge = PoseidonSponge::::new(&vp.poseidon_config); + let sponge = PoseidonSponge::new_with_pp_hash(&vp.poseidon_config, vp.pp_hash()?); if u_i.x.len() != 2 || U_i.x.len() != 2 { return Err(Error::IVCVerificationFail); } - let pp_hash = vp.pp_hash()?; - // check that u_i's output points to the running instance // u_i.X[0] == H(i, z_0, z_i, U_i) - let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, &z_0, &z_i); + let expected_u_i_x = U_i.hash(&sponge, num_steps, &z_0, &z_i); if expected_u_i_x != u_i.x[0] { return Err(Error::IVCVerificationFail); } // u_i.X[1] == H(cf_U_i) - let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); + let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge); if expected_cf_u_i_x != u_i.x[1] { return Err(Error::IVCVerificationFail); } @@ -1069,45 +1081,6 @@ where } } -impl ProtoGalaxy -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - // folds the given cyclefold circuit and its instances - #[allow(clippy::type_complexity)] - fn fold_cyclefold_circuit( - &self, - transcript: &mut PoseidonSponge, - cf_W_i: CycleFoldWitness, // witness of the running instance - cf_U_i: CycleFoldCommittedInstance, // running instance - cf_circuit: ProtoGalaxyCycleFoldCircuit, - rng: &mut impl RngCore, - ) -> Result< - ( - CycleFoldCommittedInstance, // u_i - CycleFoldWitness, // W_i1 - CycleFoldCommittedInstance, // U_i1 - C2, // cmT - ), - Error, - > { - fold_cyclefold_circuit::, C2, CS2, false>( - transcript, - self.cf_r1cs.clone(), - self.cf_cs_params.clone(), - self.pp_hash, - cf_W_i, - cf_U_i, - cf_circuit, - rng, - ) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/folding-schemes/src/folding/traits.rs b/folding-schemes/src/folding/traits.rs index 74ab3b28..cdd3b050 100644 --- a/folding-schemes/src/folding/traits.rs +++ b/folding-schemes/src/folding/traits.rs @@ -25,7 +25,6 @@ pub trait CommittedInstanceOps: Inputize> { fn hash>>( &self, sponge: &T, - pp_hash: CF1, // public params hash i: CF1, z_0: &[CF1], z_i: &[CF1], @@ -34,7 +33,6 @@ pub trait CommittedInstanceOps: Inputize> { Self: Sized + Absorb, { let mut sponge = sponge.clone(); - sponge.absorb(&pp_hash); sponge.absorb(&i); sponge.absorb(&z_0); sponge.absorb(&z_i); @@ -71,7 +69,6 @@ pub trait CommittedInstanceVarOps { fn hash( &self, sponge: &PoseidonSpongeVar>, - pp_hash: &FpVar>, i: &FpVar>, z_0: &[FpVar>], z_i: &[FpVar>], @@ -81,7 +78,6 @@ pub trait CommittedInstanceVarOps { { let mut sponge = sponge.clone(); let U_vec = self.to_sponge_field_elements()?; - sponge.absorb(&pp_hash)?; sponge.absorb(&i)?; sponge.absorb(&z_0)?; sponge.absorb(&z_i)?; diff --git a/folding-schemes/src/frontend/alloc.rs b/folding-schemes/src/frontend/alloc.rs new file mode 100644 index 00000000..c0ba0d1b --- /dev/null +++ b/folding-schemes/src/frontend/alloc.rs @@ -0,0 +1,209 @@ +use ark_ff::PrimeField; +use ark_r1cs_std::fields::fp::{AllocatedFp, FpVar}; +use ark_relations::r1cs::{ConstraintSystem, ConstraintSystemRef, SynthesisError, Variable}; +use ark_std::{ + any::{Any, TypeId}, + collections::{BTreeMap, HashMap}, +}; + +pub trait ArkCacheCast { + fn get_as(&self) -> Option<&R>; + + fn get_mut_as(&mut self) -> Option<&mut R>; +} + +impl ArkCacheCast for BTreeMap> { + fn get_as(&self) -> Option<&R> { + self.get(&TypeId::of::())?.downcast_ref::() + } + + fn get_mut_as(&mut self) -> Option<&mut R> { + self.get_mut(&TypeId::of::())?.downcast_mut::() + } +} + +pub struct Query; +pub struct LookupTable; +pub struct Committed; +pub struct Multiplicity; + +pub trait LookupConstraintSystem { + fn init_lookup(&self, table: Vec) -> Result<(), SynthesisError>; + + fn new_query_variable(&self, f: Func) -> Result + where + Func: FnOnce() -> Result; + + fn new_query_fp(&self, f: Func) -> Result, SynthesisError> + where + Func: FnOnce() -> Result; + + /// Gadget for computing the vector of multiplicities $(m_1, ..., m_M)$, + /// where $m_j$ is the number of times $t_j$ appears in the queries + /// $(q_1, ..., q_N)$. + /// + /// The multiplicities are allocated as witness variables and stored in the + /// cache map of arkworks' constraint system. + fn finalize_lookup(&self) -> Result<(), SynthesisError>; +} + +impl LookupConstraintSystem for ConstraintSystemRef { + fn init_lookup(&self, table: Vec) -> Result<(), SynthesisError> { + let cs = self.borrow_mut().ok_or(SynthesisError::MissingCS)?; + + let mut cache = cs.cache_map.borrow_mut(); + cache.insert(TypeId::of::(), Box::new(table)); + cache.insert(TypeId::of::(), Box::new(Vec::::new())); + + Ok(()) + } + + fn new_query_variable(&self, f: Func) -> Result + where + Func: FnOnce() -> Result, + { + let mut cs = self.borrow_mut().ok_or(SynthesisError::MissingCS)?; + let index = cs.num_witness_variables; + + let query = cs.new_witness_variable(f)?; + + let mut cache = cs.cache_map.borrow_mut(); + let queries = cache + .get_mut_as::>() + .ok_or(SynthesisError::AssignmentMissing)?; + // `index` is the inner value of the query variable, i.e., + // `query = Variable::Witness(index)` + queries.push(index); + + Ok(query) + } + + fn new_query_fp(&self, f: Func) -> Result, SynthesisError> + where + Func: FnOnce() -> Result, + { + let mut value = None; + let value_generator = || { + use ark_std::borrow::Borrow; + value = Some(*f()?.borrow()); + value.ok_or(SynthesisError::AssignmentMissing) + }; + let variable = self.new_query_variable(value_generator)?; + Ok(FpVar::Var(AllocatedFp::new(value, variable, self.clone()))) + } + + fn finalize_lookup(&self) -> Result<(), SynthesisError> { + let mut cs = self.borrow_mut().ok_or(SynthesisError::MissingCS)?; + + // Wrapped in a block to make the borrow checker happy. + let multiplicity_values = { + let cache = cs.cache_map.borrow(); + let table = match cache.get_as::>() { + Some(table) => table, + None => return Ok(()), + }; + let mut histo = table + .iter() + .map(|&entry| (entry, 0u64)) + .collect::>(); + let queries = cache + .get_as::>() + .ok_or(SynthesisError::AssignmentMissing)?; + + if !cs.is_in_setup_mode() { + for &query in queries { + histo + .get_mut(&cs.witness_assignment[query]) + .map(|c| *c += 1) + .ok_or(SynthesisError::AssignmentMissing)?; + } + }; + + table + .iter() + .map(|entry| F::from(histo[entry])) + .collect::>() + }; + let multiplicity_variables = (0..multiplicity_values.len()) + .map(|i| cs.num_witness_variables + i) + .collect::>(); + cs.num_witness_variables += multiplicity_values.len(); + + if !cs.is_in_setup_mode() { + cs.witness_assignment.extend(multiplicity_values); + } + + let mut cache = cs.cache_map.borrow_mut(); + cache.insert( + TypeId::of::(), + Box::new(multiplicity_variables), + ); + + Ok(()) + } +} + +pub trait CommitAndProveConstraintSystem { + fn init_commit_and_prove(&mut self) -> Result<(), SynthesisError>; + + fn new_committed_variable(&mut self, f: Func) -> Result + where + Func: FnOnce() -> Result; +} + +impl CommitAndProveConstraintSystem for ConstraintSystemRef { + fn init_commit_and_prove(&mut self) -> Result<(), SynthesisError> { + let cs = self.borrow_mut().ok_or(SynthesisError::MissingCS)?; + + let mut cache = cs.cache_map.borrow_mut(); + cache.insert(TypeId::of::(), Box::new(Vec::::new())); + + Ok(()) + } + + fn new_committed_variable(&mut self, f: Func) -> Result + where + Func: FnOnce() -> Result, + { + let mut cs = self.borrow_mut().ok_or(SynthesisError::MissingCS)?; + let index = cs.num_instance_variables; + + let committed = cs.new_input_variable(f)?; + + let mut cache = cs.cache_map.borrow_mut(); + let committed_variables = cache + .get_mut_as::>() + .ok_or(SynthesisError::AssignmentMissing)?; + // `index` is the inner value of the committed variable, i.e., + // `committed = Variable::Instance(index)` + committed_variables.push(index); + + Ok(committed) + } +} + +pub trait ConstraintSystemStatistics { + fn num_variables_of_type(&self) -> usize; + + fn has_variables_of_type(&self) -> bool { + self.num_variables_of_type::() > 0 + } +} + +impl ConstraintSystemStatistics for ConstraintSystem { + fn num_variables_of_type(&self) -> usize { + let cache = self.cache_map.borrow(); + if let Some(vec) = cache.get_as::>() { + vec.len() + } else { + 0 + } + } +} + +impl ConstraintSystemStatistics for ConstraintSystemRef { + fn num_variables_of_type(&self) -> usize { + let cs = self.borrow().unwrap(); + cs.num_variables_of_type::() + } +} diff --git a/folding-schemes/src/frontend/logup.rs b/folding-schemes/src/frontend/logup.rs new file mode 100644 index 00000000..572bbb85 --- /dev/null +++ b/folding-schemes/src/frontend/logup.rs @@ -0,0 +1,179 @@ +use ark_crypto_primitives::sponge::CryptographicSponge; +use ark_ff::{batch_inversion, PrimeField}; +use ark_r1cs_std::fields::fp::FpVar; +use ark_relations::{ + lc, + r1cs::{ConstraintSystem, LinearCombination, SynthesisError, Variable}, +}; + +use crate::{ + folding::circuits::{nonnative::affine::NonNativeAffineVar, CF1}, + transcript::{Transcript, TranscriptVar}, + Curve, +}; + +use super::alloc::{ArkCacheCast, LookupTable, Multiplicity, Query}; + +/// [`LogUp`] implements the checks for the set inclusion identity described in +/// [the LogUp paper](https://eprint.iacr.org/2022/1530.pdf). +/// +/// Specifically, as per Lemma 5 of LogUp, all the queries $(q_1, ..., q_N)$ are +/// in the lookup table $(t_1, ..., t_M)$ (i.e., $\{ q_i \} \subseteq \{ t_j \}$ +/// by removing duplicates in $(q_1, ..., q_N)$) if and only if there exists a +/// vector of multiplicities $(m_1, ..., m_M)$, such that: +/// $\sum_{i=1}^{N} \frac{1}{X - q_i} = \sum_{j=1}^{M} \frac{m_j}{X - t_j}$. +/// +/// By the Schwartz-Zippel lemma, we can check the identity by evaluating at a +/// random point $c$, which can be computed as the Fiat-Shamir challenge. +/// +/// Note that the LogUp paper further provides a dedicated protocol for checking +/// the identity, while in this implementation, the identity is encoded as an +/// arithmetic circuit, whose satisfiability is guaranteed by a SNARK. +pub struct LogUp; + +impl LogUp { + /// Computes the challenge $c$ for the set inclusion identity. + pub fn challenge>>(sponge: &T, cm: &C) -> CF1 { + let mut sponge = sponge.clone(); + sponge.absorb_nonnative(cm); + sponge.get_challenge() + } + + pub fn challenge_gadget, S>>( + sponge: &T, + cm: &NonNativeAffineVar, + ) -> Result>, SynthesisError> { + let mut sponge = sponge.clone(); + sponge.absorb_nonnative(cm)?; + sponge.get_challenge() + } + + /// Generates constraints for checking the set inclusion identity at random + /// point $c$. + pub fn generate_verification_constraints( + cs: &mut ConstraintSystem, + c: F, + ) -> Result<(), SynthesisError> { + // Return early if the lookup table is not initialized, i.e., lookup + // argument is unnecessary for the user. + // Wrapped in a block to make the borrow checker happy. + { + let cache = cs.cache_map.borrow(); + if cache.get_as::>().is_none() { + return Ok(()); + } + } + let c = cs.new_input_variable(|| Ok(c))?; + + let lhs = Self::compute_lhs(cs, c)?; + let rhs = Self::compute_rhs(cs, c)?; + + cs.enforce_constraint(lhs, Variable::One.into(), rhs)?; + + Ok(()) + } + + /// Computes the left-hand side of the set inclusion identity, i.e., + /// $\sum_{i=1}^{N} \frac{1}{X - q_i}$. + fn compute_lhs( + cs: &mut ConstraintSystem, + c: Variable, + ) -> Result, SynthesisError> { + // Wrapped in a block to make the borrow checker happy. + let (a_lcs, summand_values) = { + let cache = cs.cache_map.borrow(); + let queries = cache + .get_as::>() + .ok_or(SynthesisError::AssignmentMissing)?; + + let summands = if cs.is_in_setup_mode() { + vec![F::zero(); queries.len()] + } else { + let c_value = cs + .assigned_value(c) + .ok_or(SynthesisError::AssignmentMissing)?; + let mut inverses = queries + .iter() + .map(|&query| Ok(c_value - cs.witness_assignment[query])) + .collect::, _>>()?; + // Compute inverses in batch for better performance. + batch_inversion(&mut inverses); + inverses + }; + + ( + queries + .iter() + .map(|&query| lc!() + c - Variable::Witness(query)) + .collect::>(), + summands, + ) + }; + + let mut sum = lc!(); + for (a_lc, summand_value) in a_lcs.into_iter().zip(summand_values) { + let summand_variable = cs.new_witness_variable(|| Ok(summand_value))?; + sum = sum + summand_variable; + cs.enforce_constraint(a_lc, summand_variable.into(), Variable::One.into())?; + } + + Ok(sum) + } + + /// Computes the right-hand side of the set inclusion identity, i.e., + /// $\sum_{j=1}^{M} \frac{m_j}{X - t_j}$. + fn compute_rhs( + cs: &mut ConstraintSystem, + c: Variable, + ) -> Result, SynthesisError> { + // Wrapped in a block to make the borrow checker happy. + let (a_lcs, c_lcs, summand_values) = { + let cache = cs.cache_map.borrow(); + let table = cache + .get_as::>() + .ok_or(SynthesisError::AssignmentMissing)?; + let multiplicities = cache + .get_as::>() + .ok_or(SynthesisError::AssignmentMissing)?; + + let summands = if cs.is_in_setup_mode() { + vec![F::zero(); table.len()] + } else { + let c_value = cs + .assigned_value(c) + .ok_or(SynthesisError::AssignmentMissing)?; + let mut inverses = table.iter().map(|i| c_value - i).collect::>(); + // Compute inverses in batch for better performance. + batch_inversion(&mut inverses); + inverses + .into_iter() + .zip(multiplicities) + .map(|(inverse, &multiplicity)| { + Ok(inverse * cs.witness_assignment[multiplicity]) + }) + .collect::, _>>()? + }; + + ( + table + .iter() + .map(|&entry| lc!() + c - (entry, Variable::One)) + .collect::>(), + multiplicities + .iter() + .map(|&m| Variable::Witness(m).into()) + .collect::>(), + summands, + ) + }; + + let mut sum = lc!(); + for ((a_lc, c_lc), summand_value) in a_lcs.into_iter().zip(c_lcs).zip(summand_values) { + let summand_variable = cs.new_witness_variable(|| Ok(summand_value))?; + sum = sum + summand_variable; + cs.enforce_constraint(a_lc, summand_variable.into(), c_lc)?; + } + + Ok(sum) + } +} diff --git a/folding-schemes/src/frontend/mod.rs b/folding-schemes/src/frontend/mod.rs index 43191e61..a64c8fa3 100644 --- a/folding-schemes/src/frontend/mod.rs +++ b/folding-schemes/src/frontend/mod.rs @@ -4,6 +4,8 @@ use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar}; use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; use ark_std::fmt::Debug; +pub mod alloc; +pub mod logup; pub mod utils; /// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie. diff --git a/folding-schemes/src/transcript/mod.rs b/folding-schemes/src/transcript/mod.rs index de3a0572..e79603eb 100644 --- a/folding-schemes/src/transcript/mod.rs +++ b/folding-schemes/src/transcript/mod.rs @@ -57,6 +57,10 @@ impl> AbsorbNonNativeGadget for [T } pub trait Transcript: CryptographicSponge { + /// `new_with_pp_hash` creates a new transcript / sponge with the given + /// hash of the public parameters. + fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self; + /// `absorb_point` is for absorbing points whose `BaseField` is the field of /// the sponge, i.e., the type `C` of these points should satisfy /// `C::BaseField = F`. @@ -90,6 +94,13 @@ pub trait Transcript: CryptographicSponge { pub trait TranscriptVar: CryptographicSpongeVar { + /// `new_with_pp_hash` creates a new transcript / sponge with the given + /// hash of the public parameters. + fn new_with_pp_hash( + config: &Self::Parameters, + pp_hash: &FpVar, + ) -> Result; + /// `absorb_point` is for absorbing points whose `BaseField` is the field of /// the sponge, i.e., the type `C` of these points should satisfy /// `C::BaseField = F`. diff --git a/folding-schemes/src/transcript/poseidon.rs b/folding-schemes/src/transcript/poseidon.rs index 90709757..15a7f49b 100644 --- a/folding-schemes/src/transcript/poseidon.rs +++ b/folding-schemes/src/transcript/poseidon.rs @@ -6,11 +6,17 @@ use ark_crypto_primitives::sponge::{ use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; -use ark_relations::r1cs::SynthesisError; +use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; use super::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar}; impl Transcript for PoseidonSponge { + fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self { + let mut sponge = Self::new(config); + sponge.absorb(&pp_hash); + sponge + } + // Compatible with the in-circuit `TranscriptVar::absorb_point` fn absorb_point>(&mut self, p: &C) { let (x, y) = p.into_affine().xy().unwrap_or_default(); @@ -38,6 +44,15 @@ impl Transcript for PoseidonSponge { } impl TranscriptVar> for PoseidonSpongeVar { + fn new_with_pp_hash( + config: &Self::Parameters, + pp_hash: &FpVar, + ) -> Result { + let mut sponge = Self::new(ConstraintSystemRef::None, config); + sponge.absorb(&pp_hash)?; + Ok(sponge) + } + fn absorb_point, GC: CurveVar>( &mut self, v: &GC,