diff --git a/examples/external_inputs.rs b/examples/external_inputs.rs index 41925e98..1df27ad2 100644 --- a/examples/external_inputs.rs +++ b/examples/external_inputs.rs @@ -7,8 +7,7 @@ use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; use ark_crypto_primitives::{ crh::{ poseidon::constraints::{CRHGadget, CRHParametersVar}, - poseidon::CRH, - CRHScheme, CRHSchemeGadget, + CRHSchemeGadget, }, sponge::{poseidon::PoseidonConfig, Absorb}, }; @@ -88,20 +87,6 @@ where fn external_inputs_len(&self) -> usize { 1 } - - /// computes the next state value for the step of F for the given z_i and external_inputs - /// z_{i+1} - fn step_native( - &self, - _i: usize, - z_i: Vec, - external_inputs: Vec, - ) -> Result, Error> { - let hash_input: [F; 2] = [z_i[0], external_inputs[0]]; - let h = CRH::::evaluate(&self.poseidon_config, hash_input).unwrap(); - Ok(vec![h]) - } - /// generates the constraints and returns the next state value for the step of F for the given /// z_i and external_inputs fn generate_step_constraints( @@ -123,9 +108,20 @@ where #[cfg(test)] pub mod tests { use super::*; + use ark_crypto_primitives::crh::{poseidon::CRH, CRHScheme}; use ark_r1cs_std::R1CSVar; use ark_relations::r1cs::ConstraintSystem; + fn external_inputs_step_native( + z_i: Vec, + external_inputs: Vec, + poseidon_config: &PoseidonConfig, + ) -> Vec { + let hash_input: [F; 2] = [z_i[0], external_inputs[0]]; + let h = CRH::::evaluate(poseidon_config, hash_input).unwrap(); + vec![h] + } + // test to check that the ExternalInputsCircuit computes the same values inside and outside the circuit #[test] fn test_f_circuit() -> Result<(), Error> { @@ -133,11 +129,12 @@ pub mod tests { let cs = ConstraintSystem::::new_ref(); - let circuit = ExternalInputsCircuit::::new(poseidon_config)?; + let circuit = ExternalInputsCircuit::::new(poseidon_config.clone())?; let z_i = vec![Fr::from(1_u32)]; let external_inputs = vec![Fr::from(3_u32)]; - let z_i1 = circuit.step_native(0, z_i.clone(), external_inputs.clone())?; + let z_i1 = + external_inputs_step_native(z_i.clone(), external_inputs.clone(), &poseidon_config); let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i))?; let external_inputsVar = Vec::>::new_witness(cs.clone(), || Ok(external_inputs))?; diff --git a/examples/full_flow.rs b/examples/full_flow.rs index a3860244..4c302f2e 100644 --- a/examples/full_flow.rs +++ b/examples/full_flow.rs @@ -55,14 +55,6 @@ impl FCircuit for CubicFCircuit { fn external_inputs_len(&self) -> usize { 0 } - fn step_native( - &self, - _i: usize, - z_i: Vec, - _external_inputs: Vec, - ) -> Result, Error> { - Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)]) - } fn generate_step_constraints( &self, cs: ConstraintSystemRef, diff --git a/examples/multi_inputs.rs b/examples/multi_inputs.rs index b11f89ac..e3cdea53 100644 --- a/examples/multi_inputs.rs +++ b/examples/multi_inputs.rs @@ -40,24 +40,6 @@ impl FCircuit for MultiInputsFCircuit { fn external_inputs_len(&self) -> usize { 0 } - - /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new - /// z_{i+1} - fn step_native( - &self, - _i: usize, - z_i: Vec, - _external_inputs: Vec, - ) -> Result, Error> { - let a = z_i[0] + F::from(4_u32); - let b = z_i[1] + F::from(40_u32); - let c = z_i[2] * F::from(4_u32); - let d = z_i[3] * F::from(40_u32); - let e = z_i[4] + F::from(100_u32); - - Ok(vec![a, b, c, d, e]) - } - /// generates the constraints for the step of F for the given z_i fn generate_step_constraints( &self, @@ -86,6 +68,16 @@ pub mod tests { use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; use ark_relations::r1cs::ConstraintSystem; + fn multi_inputs_step_native(z_i: Vec) -> Vec { + let a = z_i[0] + F::from(4_u32); + let b = z_i[1] + F::from(40_u32); + let c = z_i[2] * F::from(4_u32); + let d = z_i[3] * F::from(40_u32); + let e = z_i[4] + F::from(100_u32); + + vec![a, b, c, d, e] + } + // test to check that the MultiInputsFCircuit computes the same values inside and outside the circuit #[test] fn test_f_circuit() -> Result<(), Error> { @@ -100,7 +92,7 @@ pub mod tests { Fr::from(1_u32), ]; - let z_i1 = circuit.step_native(0, z_i.clone(), vec![])?; + let z_i1 = multi_inputs_step_native(z_i.clone()); let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i))?; let computed_z_i1Var = diff --git a/examples/sha256.rs b/examples/sha256.rs index 5ff35a55..02c978be 100644 --- a/examples/sha256.rs +++ b/examples/sha256.rs @@ -4,13 +4,10 @@ #![allow(clippy::upper_case_acronyms)] use ark_crypto_primitives::crh::{ - sha256::{ - constraints::{Sha256Gadget, UnitVar}, - Sha256, - }, - CRHScheme, CRHSchemeGadget, + sha256::constraints::{Sha256Gadget, UnitVar}, + CRHSchemeGadget, }; -use ark_ff::{BigInteger, PrimeField, ToConstraintField}; +use ark_ff::PrimeField; use ark_r1cs_std::{ convert::{ToBytesGadget, ToConstraintFieldGadget}, fields::fp::FpVar, @@ -49,21 +46,6 @@ impl FCircuit for Sha256FCircuit { fn external_inputs_len(&self) -> usize { 0 } - - /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new - /// z_{i+1} - fn step_native( - &self, - _i: usize, - z_i: Vec, - _external_inputs: Vec, - ) -> Result, Error> { - let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap(); - let out: Vec = out_bytes.to_field_elements().unwrap(); - - Ok(vec![out[0]]) - } - /// generates the constraints for the step of F for the given z_i fn generate_step_constraints( &self, @@ -83,9 +65,18 @@ impl FCircuit for Sha256FCircuit { #[cfg(test)] pub mod tests { use super::*; + use ark_crypto_primitives::crh::{sha256::Sha256, CRHScheme}; + use ark_ff::{BigInteger, ToConstraintField}; use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; use ark_relations::r1cs::ConstraintSystem; + fn sha256_step_native(z_i: Vec) -> Vec { + let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap(); + let out: Vec = out_bytes.to_field_elements().unwrap(); + + vec![out[0]] + } + // test to check that the Sha256FCircuit computes the same values inside and outside the circuit #[test] fn test_f_circuit() -> Result<(), Error> { @@ -94,7 +85,7 @@ pub mod tests { let circuit = Sha256FCircuit::::new(())?; let z_i = vec![Fr::from(1_u32)]; - let z_i1 = circuit.step_native(0, z_i.clone(), vec![])?; + let z_i1 = sha256_step_native(z_i.clone()); let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i))?; let computed_z_i1Var = diff --git a/experimental-frontends/src/circom/mod.rs b/experimental-frontends/src/circom/mod.rs index f0bdd0cb..4c62e01f 100644 --- a/experimental-frontends/src/circom/mod.rs +++ b/experimental-frontends/src/circom/mod.rs @@ -8,29 +8,10 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisE use ark_std::fmt::Debug; use folding_schemes::{frontend::FCircuit, utils::PathOrBin, Error}; use num_bigint::BigInt; -use std::fmt; -use std::rc::Rc; pub mod utils; use utils::CircomWrapper; -type ClosurePointer = Rc, Vec) -> Result, Error>>; - -#[derive(Clone)] -struct CustomStepNative { - func: ClosurePointer, -} - -impl fmt::Debug for CustomStepNative { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Function pointer: {:?}", - std::any::type_name::, Vec) -> Result, Error>>() - ) - } -} - /// Define CircomFCircuit #[derive(Clone, Debug)] pub struct CircomFCircuit { @@ -38,55 +19,6 @@ pub struct CircomFCircuit { pub state_len: usize, pub external_inputs_len: usize, r1cs: CircomR1CS, - custom_step_native_code: Option>, -} - -impl CircomFCircuit { - pub fn set_custom_step_native(&mut self, func: ClosurePointer) { - self.custom_step_native_code = Some(CustomStepNative:: { func }); - } - - pub fn execute_custom_step_native( - &self, - _i: usize, - z_i: Vec, - external_inputs: Vec, - ) -> Result, Error> { - if let Some(code) = &self.custom_step_native_code { - (code.func)(_i, z_i, external_inputs) - } else { - #[cfg(test)] - assert_eq!(z_i.len(), self.state_len()); - #[cfg(test)] - assert_eq!(external_inputs.len(), self.external_inputs_len()); - - let inputs_bi = z_i - .iter() - .map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val)) - .collect::>(); - let mut inputs_map = vec![("ivc_input".to_string(), inputs_bi)]; - - if self.external_inputs_len() > 0 { - let external_inputs_bi = external_inputs - .iter() - .map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val)) - .collect::>(); - inputs_map.push(("external_inputs".to_string(), external_inputs_bi)); - } - - // Computes witness - let witness = self - .circom_wrapper - .extract_witness(&inputs_map) - .map_err(|e| { - Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e)) - })?; - - // Extracts the z_i1(next state) from the witness vector. - let z_i1 = witness[1..1 + self.state_len()].to_vec(); - Ok(z_i1) - } - } } impl FCircuit for CircomFCircuit { @@ -103,7 +35,6 @@ impl FCircuit for CircomFCircuit { state_len, external_inputs_len, r1cs, - custom_step_native_code: None, }) } @@ -114,15 +45,6 @@ impl FCircuit for CircomFCircuit { self.external_inputs_len } - fn step_native( - &self, - _i: usize, - z_i: Vec, - external_inputs: Vec, - ) -> Result, Error> { - self.execute_custom_step_native(_i, z_i, external_inputs) - } - fn generate_step_constraints( &self, cs: ConstraintSystemRef, @@ -208,18 +130,35 @@ pub mod tests { use ark_relations::r1cs::ConstraintSystem; use std::path::PathBuf; + /// Native implementation of `src/circom/test_folder/cubic_circuit.r1cs` + fn cubic_step_native(z_i: Vec) -> Vec { + let z = z_i[0]; + vec![z * z * z + z + F::from(5)] + } + + /// Native implementation of `src/circom/test_folder/with_external_inputs.r1cs` + fn external_inputs_step_native(z_i: Vec, external_inputs: Vec) -> Vec { + let temp1 = z_i[0] * z_i[0]; + let temp2 = z_i[0] * external_inputs[0]; + vec![temp1 * z_i[0] + temp2 + external_inputs[1]] + } + + /// Native implementation of `src/circom/test_folder/no_external_inputs.r1cs` + fn no_external_inputs_step_native(z_i: Vec) -> Vec { + let temp1 = z_i[0] * z_i[1]; + let temp2 = temp1 * z_i[2]; + vec![ + temp1 * z_i[0], + temp1 * z_i[1] + temp1, + temp1 * z_i[2] + temp2, + ] + } + // Tests the step_native function of CircomFCircuit. #[test] fn test_circom_step_native() -> Result<(), Error> { - let r1cs_path = PathBuf::from("./src/circom/test_folder/cubic_circuit.r1cs"); - let wasm_path = - PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - - let circom_fcircuit = - CircomFCircuit::::new((r1cs_path.into(), wasm_path.into(), 1, 0))?; // state_len:1, external_inputs_len:0 - let z_i = vec![Fr::from(3u32)]; - let z_i1 = circom_fcircuit.step_native(1, z_i, vec![])?; + let z_i1 = cubic_step_native(z_i); assert_eq!(z_i1, vec![Fr::from(35u32)]); Ok(()) } @@ -259,7 +198,7 @@ pub mod tests { let wrapper_circuit = folding_schemes::frontend::utils::WrapperCircuit { FC: circom_fcircuit.clone(), z_i: Some(z_i.clone()), - z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![])?), + z_i1: Some(cubic_step_native(z_i)), }; let cs = ConstraintSystem::::new_ref(); @@ -282,7 +221,7 @@ pub mod tests { let external_inputs = vec![Fr::from(6u32), Fr::from(7u32)]; // run native step - let z_i1_native = circom_fcircuit.step_native(1, z_i.clone(), external_inputs.clone())?; + let z_i1_native = external_inputs_step_native(z_i.clone(), external_inputs.clone()); // run gadget step let z_i_var = Vec::>::new_witness(cs.clone(), || Ok(z_i))?; @@ -327,7 +266,7 @@ pub mod tests { let z_i_var = Vec::>::new_witness(cs.clone(), || Ok(z_i.clone()))?; // run native step - let z_i1_native = circom_fcircuit.step_native(1, z_i.clone(), vec![])?; + let z_i1_native = no_external_inputs_step_native(z_i.clone()); // run gadget step let z_i1_var = circom_fcircuit.generate_step_constraints(cs.clone(), 1, z_i_var, vec![])?; @@ -352,20 +291,15 @@ pub mod tests { let wasm_path = PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - let mut circom_fcircuit = + let circom_fcircuit = CircomFCircuit::::new((r1cs_path.into(), wasm_path.into(), 1, 0))?; // state_len:1, external_inputs_len:0 - circom_fcircuit.set_custom_step_native(Rc::new(|_i, z_i, _external| { - let z = z_i[0]; - Ok(vec![z * z * z + z + Fr::from(5)]) - })); - // Allocates z_i1 by using step_native function. let z_i = vec![Fr::from(3_u32)]; let wrapper_circuit = folding_schemes::frontend::utils::WrapperCircuit { FC: circom_fcircuit.clone(), z_i: Some(z_i.clone()), - z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![])?), + z_i1: Some(cubic_step_native(z_i)), }; let cs = ConstraintSystem::::new_ref(); diff --git a/experimental-frontends/src/noir/mod.rs b/experimental-frontends/src/noir/mod.rs index 4c7f58b8..1fcaa5b8 100644 --- a/experimental-frontends/src/noir/mod.rs +++ b/experimental-frontends/src/noir/mod.rs @@ -77,69 +77,6 @@ impl FCircuit for NoirFCircuit { self.external_inputs_len } - fn step_native( - &self, - _i: usize, - z_i: Vec, - external_inputs: Vec, // inputs that are not part of the state - ) -> Result, Error> { - let mut acvm = ACVM::new( - &StubbedBlackBoxSolver, - &self.circuit.opcodes, - WitnessMap::new(), - &[], - &[], - ); - - self.circuit - .public_parameters - .0 - .iter() - .map(|witness| { - let idx: usize = witness.as_usize(); - let value = z_i[idx].to_string(); - let witness = AcvmWitness(witness.witness_index()); - let f = GenericFieldElement::::try_from_str(&value) - .ok_or(SynthesisError::Unsatisfiable)?; - acvm.overwrite_witness(witness, f); - Ok(()) - }) - .collect::, SynthesisError>>()?; - - // write witness values for external_inputs - self.circuit - .private_parameters - .iter() - .map(|witness| { - let idx = witness.as_usize() - z_i.len(); - let value = external_inputs[idx].to_string(); - let f = GenericFieldElement::::try_from_str(&value) - .ok_or(SynthesisError::Unsatisfiable)?; - acvm.overwrite_witness(AcvmWitness(witness.witness_index()), f); - Ok(()) - }) - .collect::, SynthesisError>>()?; - let _ = acvm.solve(); - - let witness_map = acvm.finalize(); - - // get the z_{i+1} output state - let assigned_z_i1 = self - .circuit - .return_values - .0 - .iter() - .map(|witness| { - let noir_field_element = witness_map - .get(witness) - .ok_or(SynthesisError::AssignmentMissing)?; - Ok(noir_field_element.into_repr()) - }) - .collect::, SynthesisError>>()?; - - Ok(assigned_z_i1) - } - fn generate_step_constraints( &self, cs: ConstraintSystemRef, @@ -233,6 +170,7 @@ impl FCircuit for NoirFCircuit { #[cfg(test)] mod tests { use ark_bn254::Fr; + use ark_ff::PrimeField; use ark_r1cs_std::R1CSVar; use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar}; use ark_relations::r1cs::ConstraintSystem; @@ -241,20 +179,18 @@ mod tests { use crate::noir::NoirFCircuit; + /// Native implementation of `src/noir/test_folder/test_circuit` + fn external_inputs_step_native(z_i: Vec, external_inputs: Vec) -> Vec { + let xx = external_inputs[0] * z_i[0]; + let yy = external_inputs[1] * z_i[1]; + vec![xx, yy] + } + #[test] fn test_step_native() -> Result<(), Error> { - let cur_path = env::current_dir()?; - let noirfcircuit = NoirFCircuit::new(( - cur_path - .join("src/noir/test_folder/test_circuit/target/test_circuit.json") - .into(), - 2, - 2, - ))?; let inputs = vec![Fr::from(2), Fr::from(5)]; - let res = noirfcircuit.step_native(0, inputs.clone(), inputs); - assert!(res.is_ok()); - assert_eq!(res?, vec![Fr::from(4), Fr::from(25)]); + let res = external_inputs_step_native(inputs.clone(), inputs); + assert_eq!(res, vec![Fr::from(4), Fr::from(25)]); Ok(()) } diff --git a/experimental-frontends/src/noname/mod.rs b/experimental-frontends/src/noname/mod.rs index c48acb3f..5ec6ed18 100644 --- a/experimental-frontends/src/noname/mod.rs +++ b/experimental-frontends/src/noname/mod.rs @@ -47,32 +47,6 @@ impl FCircuit for NonameFCircuit { self.external_inputs_len } - fn step_native( - &self, - _i: usize, - z_i: Vec, - external_inputs: Vec, - ) -> Result, Error> { - let wtns_external_inputs = - NonameInputs::from((&external_inputs, "external_inputs".to_string())); - let wtns_ivc_inputs = NonameInputs::from((&z_i, "ivc_inputs".to_string())); - - let noname_witness = self - .circuit - .generate_witness(wtns_ivc_inputs.0, wtns_external_inputs.0) - .map_err(|e| Error::WitnessCalculationError(e.to_string()))?; - - let z_i1_end_index = z_i.len() + 1; - let assigned_z_i1 = (1..z_i1_end_index) - .map(|idx| { - let value: BigUint = Into::into(noname_witness.witness[idx]); - F::from(value) - }) - .collect(); - - Ok(assigned_z_i1) - } - fn generate_step_constraints( &self, cs: ConstraintSystemRef, @@ -117,6 +91,7 @@ impl FCircuit for NonameFCircuit { mod tests { use ark_bn254::Fr; + use ark_ff::PrimeField; use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar}; use noname::backends::r1cs::R1csBn254Field; @@ -125,6 +100,14 @@ mod tests { use super::NonameFCircuit; use ark_relations::r1cs::ConstraintSystem; + /// Native implementation of `NONAME_CIRCUIT_EXTERNAL_INPUTS` + fn external_inputs_step_native(z_i: Vec, external_inputs: Vec) -> Vec { + let xx = external_inputs[0] + z_i[0]; + let yy = external_inputs[1] * z_i[1]; + assert_eq!(yy, xx); + vec![xx, yy] + } + const NONAME_CIRCUIT_EXTERNAL_INPUTS: &str = "fn main(pub ivc_inputs: [Field; 2], external_inputs: [Field; 2]) -> [Field; 2] { let xx = external_inputs[0] + ivc_inputs[0]; @@ -158,7 +141,7 @@ mod tests { ivc_inputs_var, external_inputs_var, )?; - let z_i1_native = circuit.step_native(0, inputs_public, inputs_private)?; + let z_i1_native = external_inputs_step_native(inputs_public, inputs_private); assert_eq!(z_i1[0].value()?, z_i1_native[0]); assert_eq!(z_i1[1].value()?, z_i1_native[1]); diff --git a/folding-schemes/src/arith/r1cs/circuits.rs b/folding-schemes/src/arith/r1cs/circuits.rs index a8cee6b1..e870b585 100644 --- a/folding-schemes/src/arith/r1cs/circuits.rs +++ b/folding-schemes/src/arith/r1cs/circuits.rs @@ -126,7 +126,9 @@ pub mod tests { }, }; use crate::frontend::{ - utils::{CubicFCircuit, CustomFCircuit, WrapperCircuit}, + utils::{ + cubic_step_native, custom_step_native, CubicFCircuit, CustomFCircuit, WrapperCircuit, + }, FCircuit, }; use crate::Error; @@ -209,7 +211,7 @@ pub mod tests { let circuit = WrapperCircuit::> { FC: cubic_circuit, z_i: Some(z_i.clone()), - z_i1: Some(cubic_circuit.step_native(0, z_i, vec![])?), + z_i1: Some(cubic_step_native(z_i)), }; test_relaxed_r1cs_gadget(circuit) @@ -253,25 +255,26 @@ pub mod tests { let circuit = WrapperCircuit::> { FC: custom_circuit, z_i: Some(z_i.clone()), - z_i1: Some(custom_circuit.step_native(0, z_i, vec![])?), + z_i1: Some(custom_step_native(z_i, n_constraints)), }; test_relaxed_r1cs_gadget(circuit) } #[test] fn test_relaxed_r1cs_nonnative_circuit() -> Result<(), Error> { + let n_constraints = 10; let rng = &mut thread_rng(); let cs = ConstraintSystem::::new_ref(); // in practice we would use CycleFoldCircuit, but is a very big circuit (when computed // non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a // custom circuit. - let custom_circuit = CustomFCircuit::::new(10)?; + let custom_circuit = CustomFCircuit::::new(n_constraints)?; let z_i = vec![Fq::from(5_u32)]; let circuit = WrapperCircuit::> { FC: custom_circuit, z_i: Some(z_i.clone()), - z_i1: Some(custom_circuit.step_native(0, z_i, vec![])?), + z_i1: Some(custom_step_native(z_i, n_constraints)), }; circuit.generate_constraints(cs.clone())?; cs.finalize(); diff --git a/folding-schemes/src/folding/circuits/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs index 555b8f6b..850bfb3a 100644 --- a/folding-schemes/src/folding/circuits/cyclefold.rs +++ b/folding-schemes/src/folding/circuits/cyclefold.rs @@ -9,6 +9,7 @@ use ark_r1cs_std::{ eq::EqGadget, fields::fp::FpVar, prelude::CurveVar, + R1CSVar, }; use ark_relations::r1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, @@ -443,8 +444,6 @@ pub struct CycleFoldCircuit>, /// points to be folded in the CycleFoldCircuit pub points: Option>, - /// public inputs (cf_u_{i+1}.x) - pub x: Option>>, } impl>> CycleFoldCircuit { @@ -454,7 +453,6 @@ impl>> CycleFoldCircuit>> ConstraintSynthesi p_folded = p_folded.scalar_mul_le(r_bits.iter())? + points[i].clone(); } - let x = Vec::new_input(cs.clone(), || { - Ok(self.x.unwrap_or(vec![CF2::::zero(); CFG::IO_LEN])) - })?; - #[cfg(test)] - assert_eq!(x.len(), CFG::IO_LEN); // non-constrained sanity check - // 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: @@ -520,13 +512,25 @@ impl>> ConstraintSynthesi .flatten() .collect(); - let computed_x = [ + let x = [ r_fp, points_aux, p_folded.to_constraint_field()?[..2].to_vec(), ] .concat(); - computed_x.enforce_equal(&x)?; + #[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)?; Ok(()) } @@ -607,7 +611,6 @@ pub fn fold_cyclefold_circuit( pp_hash: C1::ScalarField, // public params hash cf_W_i: CycleFoldWitness, // witness of the running instance cf_U_i: CycleFoldCommittedInstance, // running instance - cf_u_i_x: Vec, cf_circuit: CycleFoldCircuit, mut rng: impl RngCore, ) -> Result< @@ -639,9 +642,6 @@ where let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let (cf_w_i, cf_x_i) = extract_w_x::(&cs2); - if cf_x_i != cf_u_i_x { - return Err(Error::NotEqual); - } #[cfg(test)] assert_eq!(cf_x_i.len(), CFG::IO_LEN); @@ -767,10 +767,11 @@ pub mod tests { _gc: PhantomData, r_bits: Some(rho_bits), points: Some(points), - x: Some(x.clone()), }; cf_circuit.generate_constraints(cs.clone())?; assert!(cs.is_satisfied()?); + // `instance_assignment[0]` is the constant term 1 + assert_eq!(&cs.borrow().unwrap().instance_assignment[1..], &x); Ok(()) } diff --git a/folding-schemes/src/folding/hypernova/circuits.rs b/folding-schemes/src/folding/hypernova/circuits.rs index f65be0cb..3db07806 100644 --- a/folding-schemes/src/folding/hypernova/circuits.rs +++ b/folding-schemes/src/folding/hypernova/circuits.rs @@ -505,13 +505,11 @@ pub struct AugmentedFCircuit< pub(super) us: Option>>, // other u_i's to be folded that are not the main incoming instance pub(super) U_i1_C: Option, // U_{i+1}.C pub(super) F: FC, // F circuit - pub(super) x: Option>, // public input (u_{i+1}.x[0]) pub(super) nimfs_proof: Option>, // cyclefold verifier on C1 pub(super) cf_u_i_cmW: Option, // input, cf_u_i.cmW pub(super) cf_U_i: Option>, // input, RelaxedR1CS CycleFold instance - pub(super) cf_x: Option>, // public input (cf_u_{i+1}.x[1]) pub(super) cf_cmT: Option, } @@ -552,11 +550,9 @@ where us: None, U_i1_C: None, F: F_circuit, - x: None, nimfs_proof: None, cf_u_i_cmW: None, cf_U_i: None, - cf_x: None, cf_cmT: None, }) } @@ -642,12 +638,10 @@ where us: Some(us), U_i1_C: Some(U_i1.C), F: self.F.clone(), - x: Some(C1::ScalarField::zero()), nimfs_proof: Some(nimfs_proof), // cyclefold values cf_u_i_cmW: None, cf_U_i: None, - cf_x: None, cf_cmT: None, }; @@ -690,20 +684,18 @@ where } } -impl ConstraintSynthesizer> - for AugmentedFCircuit +impl AugmentedFCircuit where - C1: CurveGroup, + C1: CurveGroup, C2: CurveGroup, GC2: CurveVar>, FC: FCircuit>, - ::BaseField: PrimeField, - ::BaseField: PrimeField, - C1::ScalarField: Absorb, - C2::ScalarField: Absorb, - C1: CurveGroup, + C2::BaseField: PrimeField + Absorb, { - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { + pub fn compute_next_state( + self, + cs: ConstraintSystemRef>, + ) -> Result>>, SynthesisError> { let pp_hash = FpVar::>::new_witness(cs.clone(), || { Ok(self.pp_hash.unwrap_or_else(CF1::::zero)) })?; @@ -816,8 +808,17 @@ where &z_0, &z_i1, )?; - let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?; - x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?; + let x = is_basecase.select(&u_i1_x_base, &u_i1_x)?; + // 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(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(); @@ -877,12 +878,33 @@ where let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? .hash(&sponge, pp_hash)?; - let cf_x = FpVar::new_input(cs.clone(), || { - Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?)) - })?; - cf_x.enforce_equal(&is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?)?; + 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 + // allocate a public input and enforce that its value is indeed `cf_x`. + // While comparing `cf_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 `cf_x` computed in-circuit. + FpVar::new_input(cs.clone(), || cf_x.value())?.enforce_equal(&cf_x)?; + + Ok(z_i1) + } +} - Ok(()) +impl ConstraintSynthesizer> + for AugmentedFCircuit +where + C1: CurveGroup, + C2: CurveGroup, + GC2: CurveVar>, + FC: FCircuit>, + C2::BaseField: PrimeField + Absorb, +{ + fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { + self.compute_next_state(cs).map(|_| ()) } } @@ -910,9 +932,8 @@ mod tests { }, traits::CommittedInstanceOps, }, - frontend::utils::CubicFCircuit, + frontend::utils::{cubic_step_native, CubicFCircuit}, transcript::poseidon::poseidon_canonical_config, - utils::get_cm_coordinates, }; #[test] @@ -1263,19 +1284,22 @@ mod tests { let all_Ws = [vec![W_i.clone()], Ws].concat(); let all_ws = [vec![w_i.clone()], ws].concat(); - let z_i1 = F_circuit.step_native(i, z_i.clone(), vec![])?; + let z_i1 = cubic_step_native(z_i.clone()); let (U_i1, W_i1); + let u_i1_x; + let cf_u_i1_x; + if i == 0 { W_i1 = Witness::::dummy(&ccs); U_i1 = LCCCS::dummy(&ccs); - let u_i1_x = U_i1.hash(&sponge, pp_hash, Fr::one(), &z_0, &z_i1); + u_i1_x = U_i1.hash(&sponge, pp_hash, Fr::one(), &z_0, &z_i1); // hash the initial (dummy) CycleFold instance, which is used as the 2nd public // input in the AugmentedFCircuit - let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); + cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); augmented_f_circuit = AugmentedFCircuit::< Projective, @@ -1301,13 +1325,11 @@ mod tests { us: Some(us.clone()), U_i1_C: Some(U_i1.C), F: F_circuit, - x: Some(u_i1_x), nimfs_proof: None, // cyclefold values cf_u_i_cmW: None, cf_U_i: None, - cf_x: Some(cf_u_i1_x), cf_cmT: None, }; } else { @@ -1327,28 +1349,11 @@ mod tests { // sanity check: check the folded instance relation ccs.check_relation(&W_i1, &U_i1)?; - let u_i1_x = U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), &z_0, &z_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(); - let rho_Fq = Fq::from_bigint(BigInteger::from_bits_le(&rho_bits)) - .ok_or(Error::OutOfBounds)?; // CycleFold part: - // get the vector used as public inputs 'x' in the CycleFold circuit - let cf_u_i_x = [ - vec![rho_Fq], - get_cm_coordinates(&U_i.C), - Us.iter() - .flat_map(|Us_i| get_cm_coordinates(&Us_i.C)) - .collect(), - get_cm_coordinates(&u_i.C), - us.iter() - .flat_map(|us_i| get_cm_coordinates(&us_i.C)) - .collect(), - get_cm_coordinates(&U_i1.C), - ] - .concat(); - let cf_circuit = HyperNovaCycleFoldCircuit:: { _gc: PhantomData, r_bits: Some(rho_bits.clone()), @@ -1361,7 +1366,6 @@ mod tests { ] .concat(), ), - x: Some(cf_u_i_x.clone()), }; // ensure that the CycleFoldCircuit is well defined @@ -1385,14 +1389,13 @@ mod tests { pp_hash, cf_W_i.clone(), // CycleFold running instance witness cf_U_i.clone(), // CycleFold running instance - cf_u_i_x, // CycleFold incoming instance cf_circuit, &mut rng, )?; // hash the CycleFold folded instance, which is used as the 2nd public input in the // AugmentedFCircuit - let cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, pp_hash); + cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, pp_hash); augmented_f_circuit = AugmentedFCircuit::< Projective, @@ -1418,13 +1421,11 @@ mod tests { us: Some(us.clone()), U_i1_C: Some(U_i1.C), F: F_circuit, - x: Some(u_i1_x), nimfs_proof: Some(nimfs_proof), // cyclefold values cf_u_i_cmW: Some(cf_u_i.cmW), cf_U_i: Some(cf_U_i), - cf_x: Some(cf_u_i1_x), cf_cmT: Some(cf_cmT), }; @@ -1441,7 +1442,7 @@ mod tests { assert!(cs.is_satisfied()?); let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::(&cs); // includes 1 and public inputs - assert_eq!(r1cs_x_i1[0], augmented_f_circuit.x.unwrap()); + assert_eq!(r1cs_x_i1[0], u_i1_x); let r1cs_z = [vec![Fr::one()], r1cs_x_i1.clone(), r1cs_w_i1.clone()].concat(); // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we // assign them directly to w_i, u_i. @@ -1455,8 +1456,8 @@ mod tests { // sanity checks assert_eq!(w_i.w, r1cs_w_i1); assert_eq!(u_i.x, r1cs_x_i1); - assert_eq!(u_i.x[0], augmented_f_circuit.x.unwrap()); - assert_eq!(u_i.x[1], augmented_f_circuit.cf_x.unwrap()); + 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); // u_i is already u_i1 at this point, check that has the expected value at x[0] diff --git a/folding-schemes/src/folding/hypernova/mod.rs b/folding-schemes/src/folding/hypernova/mod.rs index a956464e..09c2790c 100644 --- a/folding-schemes/src/folding/hypernova/mod.rs +++ b/folding-schemes/src/folding/hypernova/mod.rs @@ -5,7 +5,7 @@ use ark_crypto_primitives::sponge::{ }; use ark_ec::CurveGroup; use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::prelude::CurveVar; +use ark_r1cs_std::{prelude::CurveVar, R1CSVar}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, SerializationError}; use ark_std::{fmt::Debug, marker::PhantomData, rand::RngCore, One, Zero}; @@ -39,7 +39,7 @@ use crate::folding::{ }; use crate::frontend::FCircuit; use crate::transcript::poseidon::poseidon_canonical_config; -use crate::utils::{get_cm_coordinates, pp_hash}; +use crate::utils::pp_hash; use crate::Error; use crate::{ arith::{ @@ -386,21 +386,9 @@ where ]; let us = vec![u_i.clone(); NU - 1]; - let z_i1 = self - .F - .step_native(0, state.clone(), external_inputs.clone())?; - // compute u_{i+1}.x let U_i1 = LCCCS::dummy(&self.ccs); - let u_i1_x = U_i1.hash( - &sponge, - self.pp_hash, - C1::ScalarField::one(), // i+1, where i=0 - &self.z_0, - &z_i1, - ); - - let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, self.pp_hash); + let augmented_f_circuit = AugmentedFCircuit:: { _c2: PhantomData, _gc2: PhantomData, @@ -418,13 +406,11 @@ where us: Some(us), U_i1_C: Some(U_i1.C), F: self.F.clone(), - x: Some(u_i1_x), nimfs_proof: None, // cyclefold values cf_u_i_cmW: None, cf_U_i: None, - cf_x: Some(cf_u_i1_x), cf_cmT: None, }; @@ -437,9 +423,6 @@ where let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::(&cs); // includes 1 and public inputs - #[cfg(test)] - assert_eq!(r1cs_x_i1[0], u_i1_x); - let r1cs_z = [ vec![C1::ScalarField::one()], r1cs_x_i1.clone(), @@ -678,9 +661,6 @@ where } } - // `sponge` is for digest computation. - let sponge = PoseidonSponge::::new(&self.poseidon_config); - let (Us, Ws, us, ws) = if MU > 1 || NU > 1 { let other_instances = other_instances.ok_or(Error::MissingOtherInstances(MU, NU))?; @@ -757,12 +737,6 @@ where i_usize = usize::from_le_bytes(i_bytes); } - let z_i1 = self - .F - .step_native(i_usize, self.z_i.clone(), external_inputs.clone())?; - - // u_{i+1}.x[1] = H(cf_U_{i+1}) - let cf_u_i1_x: C1::ScalarField; let (U_i1, mut W_i1); if self.i == C1::ScalarField::zero() { @@ -770,18 +744,6 @@ where W_i1.r_w = self.W_i.r_w; U_i1 = LCCCS::dummy(&self.ccs); - let u_i1_x = U_i1.hash( - &sponge, - self.pp_hash, - C1::ScalarField::one(), - &self.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 = self.cf_U_i.hash_cyclefold(&sponge, self.pp_hash); - augmented_f_circuit = AugmentedFCircuit:: { _c2: PhantomData, _gc2: PhantomData, @@ -799,13 +761,11 @@ where us: Some(us), U_i1_C: Some(U_i1.C), F: self.F.clone(), - x: Some(u_i1_x), nimfs_proof: None, // cyclefold values cf_u_i_cmW: None, cf_U_i: None, - cf_x: Some(cf_u_i1_x), cf_cmT: None, }; } else { @@ -834,43 +794,12 @@ where #[cfg(test)] self.ccs.check_relation(&W_i1, &U_i1)?; - let u_i1_x = U_i1.hash( - &sponge, - self.pp_hash, - self.i + C1::ScalarField::one(), - &self.z_0, - &z_i1, - ); - let rho_bits = rho.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); - let rho_Fq = C1::BaseField::from(::BigInt::from_bits_le( - &rho_bits, - )); // CycleFold part: - // get the vector used as public inputs 'x' in the CycleFold circuit. - // Place the random values and the points coordinates as the public input x: - // In Nova, this is: x == [r, p1, p2, p3]. - // In multifolding schemes such as HyperNova, this is: - // computed_x = [r, p_0, p_1, p_2, ..., p_n], - // where each p_i is in fact p_i.to_constraint_field() - let cf_u_i_x = [ - vec![rho_Fq], - all_Us - .iter() - .flat_map(|Us_i| get_cm_coordinates(&Us_i.C)) - .collect(), - all_us - .iter() - .flat_map(|us_i| get_cm_coordinates(&us_i.C)) - .collect(), - get_cm_coordinates(&U_i1.C), - ] - .concat(); - let cf_circuit = HyperNovaCycleFoldCircuit:: { _gc: PhantomData, - r_bits: Some(rho_bits.clone()), + r_bits: Some(rho_bits), points: Some( [ all_Us.iter().map(|Us_i| Us_i.C).collect::>(), @@ -878,7 +807,6 @@ where ] .concat(), ), - x: Some(cf_u_i_x.clone()), }; let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = fold_cyclefold_circuit::< @@ -896,13 +824,10 @@ where self.pp_hash, self.cf_W_i.clone(), // CycleFold running instance witness self.cf_U_i.clone(), // CycleFold running instance - cf_u_i_x, cf_circuit, &mut rng, )?; - cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, self.pp_hash); - augmented_f_circuit = AugmentedFCircuit:: { _c2: PhantomData, _gc2: PhantomData, @@ -920,13 +845,11 @@ where us: Some(us), U_i1_C: Some(U_i1.C), F: self.F.clone(), - x: Some(u_i1_x), nimfs_proof: Some(nimfs_proof), // cyclefold values cf_u_i_cmW: Some(cf_u_i.cmW), cf_U_i: Some(self.cf_U_i.clone()), - cf_x: Some(cf_u_i1_x), cf_cmT: Some(cf_cmT), }; @@ -936,7 +859,9 @@ where } let cs = ConstraintSystem::::new_ref(); - augmented_f_circuit.generate_constraints(cs.clone())?; + let z_i1 = augmented_f_circuit + .compute_next_state(cs.clone())? + .value()?; let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; #[cfg(test)] @@ -961,7 +886,7 @@ where // set values for next iteration self.i += C1::ScalarField::one(); // assign z_{i+1} into z_i - self.z_i = z_i1.clone(); + self.z_i = z_i1; self.U_i = U_i1.clone(); self.W_i = W_i1.clone(); diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index 48aa2025..38fb0333 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -69,8 +69,7 @@ pub struct AugmentedFCircuit< pub(super) U_i1_cmE: Option, pub(super) U_i1_cmW: Option, pub(super) cmT: Option, - pub(super) F: FC, // F circuit - pub(super) x: Option>, // public input (u_{i+1}.x[0]) + 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 @@ -80,7 +79,6 @@ pub struct AugmentedFCircuit< pub(super) cf_U_i: Option>, // input pub(super) cf1_cmT: Option, pub(super) cf2_cmT: Option, - pub(super) cf_x: Option>, // public input (u_{i+1}.x[1]) } impl>, FC: FCircuit>> @@ -102,31 +100,28 @@ impl>, FC: FCircuit ConstraintSynthesizer> for AugmentedFCircuit +impl AugmentedFCircuit where - C1: CurveGroup, + C1: CurveGroup, C2: CurveGroup, GC2: CurveVar>, FC: FCircuit>, - ::BaseField: PrimeField, - ::BaseField: PrimeField, - C1::ScalarField: Absorb, - C2::ScalarField: Absorb, - C1: CurveGroup, + C2::BaseField: PrimeField + Absorb, { - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { + pub fn compute_next_state( + self, + cs: ConstraintSystemRef>, + ) -> Result>>, SynthesisError> { let pp_hash = FpVar::>::new_witness(cs.clone(), || { Ok(self.pp_hash.unwrap_or_else(CF1::::zero)) })?; @@ -249,8 +244,17 @@ where &z_0, &z_i1, )?; - let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?; - x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?; + let x = is_basecase.select(&u_i1_x_base, &u_i1_x)?; + // 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(cs.clone(), || x.value())?.enforce_equal(&x)?; // CycleFold part // C.1. Compute cf1_u_i.x and cf2_u_i.x @@ -329,12 +333,32 @@ where let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? .hash(&sponge, pp_hash)?; - let cf_x = FpVar::new_input(cs.clone(), || { - Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?)) - })?; - cf_x.enforce_equal(&is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?)?; + 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 + // allocate a public input and enforce that its value is indeed `cf_x`. + // While comparing `cf_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 `cf_x` computed in-circuit. + FpVar::new_input(cs.clone(), || cf_x.value())?.enforce_equal(&cf_x)?; + + Ok(z_i1) + } +} - Ok(()) +impl ConstraintSynthesizer> for AugmentedFCircuit +where + C1: CurveGroup, + C2: CurveGroup, + GC2: CurveVar>, + FC: FCircuit>, + C2::BaseField: PrimeField + Absorb, +{ + fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { + self.compute_next_state(cs).map(|_| ()) } } diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index 53577a5c..6389bcf2 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -10,7 +10,7 @@ use ark_crypto_primitives::sponge::{ }; use ark_ec::CurveGroup; use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::prelude::CurveVar; +use ark_r1cs_std::{prelude::CurveVar, R1CSVar}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid}; use ark_std::fmt::Debug; @@ -34,7 +34,7 @@ use crate::FoldingScheme; use crate::{ arith::r1cs::{extract_r1cs, extract_w_x, R1CS}, constants::NOVA_N_BITS_RO, - utils::{get_cm_coordinates, pp_hash}, + utils::pp_hash, }; use crate::{arith::Arith, commitment::CommitmentScheme}; use decider_eth_circuit::WitnessVar; @@ -714,10 +714,6 @@ where i_usize = usize::from_le_bytes(i_bytes); } - let z_i1 = self - .F - .step_native(i_usize, self.z_i.clone(), external_inputs.clone())?; - // fold Nova instances let (W_i1, U_i1, cmT, r_bits): (Witness, CommittedInstance, C1, Vec) = NIFS::, H>::prove( @@ -730,23 +726,8 @@ where &self.w_i, &self.u_i, )?; - let r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&r_bits)) - .ok_or(Error::OutOfBounds)?; - - // folded instance output (public input, x) - // u_{i+1}.x[0] = H(i+1, z_0, z_{i+1}, U_{i+1}) - let u_i1_x = U_i1.hash( - &sponge, - self.pp_hash, - self.i + C1::ScalarField::one(), - &self.z_0, - &z_i1, - ); - // u_{i+1}.x[1] = H(cf_U_{i+1}) - let cf_u_i1_x: C1::ScalarField; if self.i == C1::ScalarField::zero() { - cf_u_i1_x = self.cf_U_i.hash_cyclefold(&sponge, self.pp_hash); // base case augmented_F_circuit = AugmentedFCircuit:: { _gc2: PhantomData, @@ -763,13 +744,11 @@ where U_i1_cmW: Some(U_i1.cmW), cmT: Some(cmT), F: self.F.clone(), - x: Some(u_i1_x), cf1_u_i_cmW: None, cf2_u_i_cmW: None, cf_U_i: None, cf1_cmT: None, cf2_cmT: None, - cf_x: Some(cf_u_i1_x), }; #[cfg(test)] @@ -784,35 +763,15 @@ where } } else { // CycleFold part: - // get the vector used as public inputs 'x' in the CycleFold circuit - // cyclefold circuit for cmW - let cfW_u_i_x = [ - vec![r_Fq], - get_cm_coordinates(&self.U_i.cmW), - get_cm_coordinates(&self.u_i.cmW), - get_cm_coordinates(&U_i1.cmW), - ] - .concat(); - // cyclefold circuit for cmE - let cfE_u_i_x = [ - vec![r_Fq], - get_cm_coordinates(&self.U_i.cmE), - get_cm_coordinates(&cmT), - get_cm_coordinates(&U_i1.cmE), - ] - .concat(); - let cfW_circuit = NovaCycleFoldCircuit:: { _gc: PhantomData, r_bits: Some(r_bits.clone()), points: Some(vec![self.U_i.clone().cmW, self.u_i.clone().cmW]), - x: Some(cfW_u_i_x.clone()), }; let cfE_circuit = NovaCycleFoldCircuit:: { _gc: PhantomData, r_bits: Some(r_bits.clone()), points: Some(vec![self.U_i.clone().cmE, cmT]), - x: Some(cfE_u_i_x.clone()), }; // fold self.cf_U_i + cfW_U -> folded running with cfW @@ -820,7 +779,6 @@ where &mut transcript, self.cf_W_i.clone(), // CycleFold running instance witness self.cf_U_i.clone(), // CycleFold running instance - cfW_u_i_x, cfW_circuit, &mut rng, )?; @@ -829,13 +787,10 @@ where &mut transcript, cfW_W_i1, cfW_U_i1.clone(), - cfE_u_i_x, cfE_circuit, &mut rng, )?; - cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, self.pp_hash); - augmented_F_circuit = AugmentedFCircuit:: { _gc2: PhantomData, poseidon_config: self.poseidon_config.clone(), @@ -851,14 +806,12 @@ where U_i1_cmW: Some(U_i1.cmW), cmT: Some(cmT), F: self.F.clone(), - x: Some(u_i1_x), // 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), - cf_x: Some(cf_u_i1_x), }; self.cf_W_i = cf_W_i1; @@ -876,16 +829,15 @@ where let cs = ConstraintSystem::::new_ref(); - augmented_F_circuit.generate_constraints(cs.clone())?; + let z_i1 = augmented_F_circuit + .compute_next_state(cs.clone())? + .value()?; #[cfg(test)] assert!(cs.is_satisfied()?); let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let (w_i1, x_i1) = extract_w_x::(&cs); - if x_i1[0] != u_i1_x || x_i1[1] != cf_u_i1_x { - return Err(Error::NotEqual); - } #[cfg(test)] if x_i1.len() != 2 { @@ -1064,7 +1016,6 @@ where transcript: &mut T, cf_W_i: CycleFoldWitness, // witness of the running instance cf_U_i: CycleFoldCommittedInstance, // running instance - cf_u_i_x: Vec, cf_circuit: NovaCycleFoldCircuit, rng: &mut impl RngCore, ) -> Result< @@ -1085,7 +1036,6 @@ where self.pp_hash, cf_W_i, cf_U_i, - cf_u_i_x, cf_circuit, rng, ) diff --git a/folding-schemes/src/folding/protogalaxy/circuits.rs b/folding-schemes/src/folding/protogalaxy/circuits.rs index f51721b6..09f1a2fe 100644 --- a/folding-schemes/src/folding/protogalaxy/circuits.rs +++ b/folding-schemes/src/folding/protogalaxy/circuits.rs @@ -255,7 +255,6 @@ pub struct AugmentedFCircuit< pub(super) U_i1_phi: C1, pub(super) F_coeffs: Vec>, pub(super) K_coeffs: Vec>, - pub(super) x: Option>, // public input (u_{i+1}.x[0]) pub(super) phi_stars: Vec, @@ -264,7 +263,6 @@ pub struct AugmentedFCircuit< pub(super) cf_U_i: CycleFoldCommittedInstance, // input pub(super) cf1_cmT: C2, pub(super) cf2_cmT: C2, - pub(super) cf_x: Option>, // public input (u_{i+1}.x[1]) } impl>, FC: FCircuit>> @@ -297,19 +295,17 @@ impl>, FC: FCircuit::zero(); d * k + 1], phi_stars: vec![C1::zero(); k], F: F_circuit, - x: None, // cyclefold values cf1_u_i_cmW: C2::zero(), cf2_u_i_cmW: C2::zero(), cf_U_i: cf_u_dummy, cf1_cmT: C2::zero(), cf2_cmT: C2::zero(), - cf_x: None, } } } -impl ConstraintSynthesizer> for AugmentedFCircuit +impl AugmentedFCircuit where C1: CurveGroup, C2: CurveGroup, @@ -317,7 +313,10 @@ where FC: FCircuit>, C2::BaseField: PrimeField + Absorb, { - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { + pub fn compute_next_state( + self, + cs: ConstraintSystemRef>, + ) -> Result>>, SynthesisError> { 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))?; @@ -391,8 +390,17 @@ where &z_0, &z_i1, )?; - let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?; - x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?; + let x = is_basecase.select(&u_i1_x_base, &u_i1_x)?; + // 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(cs.clone(), || x.value())?.enforce_equal(&x)?; // CycleFold part // C.1. Compute cf1_u_i.x and cf2_u_i.x @@ -462,12 +470,32 @@ where let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? .hash(&sponge, pp_hash.clone())?; - let cf_x = FpVar::new_input(cs.clone(), || { - Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?)) - })?; - cf_x.enforce_equal(&is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?)?; + 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 + // allocate a public input and enforce that its value is indeed `cf_x`. + // While comparing `cf_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 `cf_x` computed in-circuit. + FpVar::new_input(cs.clone(), || cf_x.value())?.enforce_equal(&cf_x)?; + + Ok(z_i1) + } +} - Ok(()) +impl ConstraintSynthesizer> for AugmentedFCircuit +where + C1: CurveGroup, + C2: CurveGroup, + GC2: CurveVar>, + FC: FCircuit>, + C2::BaseField: PrimeField + Absorb, +{ + fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { + self.compute_next_state(cs).map(|_| ()) } } diff --git a/folding-schemes/src/folding/protogalaxy/mod.rs b/folding-schemes/src/folding/protogalaxy/mod.rs index 35fccfa5..8009bc0c 100644 --- a/folding-schemes/src/folding/protogalaxy/mod.rs +++ b/folding-schemes/src/folding/protogalaxy/mod.rs @@ -38,7 +38,7 @@ use crate::{ }, frontend::{utils::DummyCircuit, FCircuit}, transcript::poseidon::poseidon_canonical_config, - utils::{get_cm_coordinates, pp_hash}, + utils::pp_hash, Error, FoldingScheme, }; @@ -860,31 +860,7 @@ where let i_bn: BigUint = self.i.into(); let i_usize: usize = i_bn.try_into().map_err(|_| Error::MaxStep)?; - let z_i1 = self - .F - .step_native(i_usize, self.z_i.clone(), external_inputs.clone())?; - - // folded instance output (public input, x) - // u_{i+1}.x[0] = H(i+1, z_0, z_{i+1}, U_{i+1}) - let u_i1_x: C1::ScalarField; - // u_{i+1}.x[1] = H(cf_U_{i+1}) - let cf_u_i1_x: C1::ScalarField; - if self.i.is_zero() { - // Take extra care of the base case - // `U_{i+1}` (i.e., `U_1`) is fixed to `U_dummy`, so we just use - // `self.U_i = U_0 = U_dummy`. - u_i1_x = self.U_i.hash( - &sponge, - self.pp_hash, - self.i + C1::ScalarField::one(), - &self.z_0, - &z_i1, - ); - // `cf_U_{i+1}` (i.e., `cf_U_1`) is fixed to `cf_U_dummy`, so we - // just use `self.cf_U_i = cf_U_0 = cf_U_dummy`. - cf_u_i1_x = self.cf_U_i.hash_cyclefold(&sponge, self.pp_hash); - augmented_F_circuit = AugmentedFCircuit::empty( &self.poseidon_config, self.F.clone(), @@ -913,7 +889,6 @@ where )?; // CycleFold part: - // get the vector used as public inputs 'x' in the CycleFold circuit 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); @@ -921,43 +896,19 @@ where // cyclefold circuit for enforcing: // 0 + U_i.phi * L_evals[0] == phi_stars[0] - let cf1_u_i_x = [ - r0_bits - .chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1) - .map(::BigInt::from_bits_le) - .map(C1::BaseField::from) - .collect::>(), - get_cm_coordinates(&C1::zero()), - get_cm_coordinates(&self.U_i.phi), - get_cm_coordinates(&aux.phi_stars[0]), - ] - .concat(); let cf1_circuit = ProtoGalaxyCycleFoldCircuit:: { _gc: PhantomData, r_bits: Some(r0_bits), points: Some(vec![C1::zero(), self.U_i.phi]), - x: Some(cf1_u_i_x.clone()), }; // 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_u_i_x = [ - r1_bits - .chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1) - .map(::BigInt::from_bits_le) - .map(C1::BaseField::from) - .collect::>(), - get_cm_coordinates(&aux.phi_stars[0]), - get_cm_coordinates(&self.u_i.phi), - get_cm_coordinates(&U_i1.phi), - ] - .concat(); let cf2_circuit = ProtoGalaxyCycleFoldCircuit:: { _gc: PhantomData, r_bits: Some(r1_bits), points: Some(vec![aux.phi_stars[0], self.u_i.phi]), - x: Some(cf2_u_i_x.clone()), }; // fold self.cf_U_i + cf1_U -> folded running with cf1 @@ -965,7 +916,6 @@ where &mut transcript_prover, self.cf_W_i.clone(), // CycleFold running instance witness self.cf_U_i.clone(), // CycleFold running instance - cf1_u_i_x, cf1_circuit, &mut rng, )?; @@ -974,21 +924,10 @@ where &mut transcript_prover, cf1_W_i1, cf1_U_i1.clone(), - cf2_u_i_x, cf2_circuit, &mut rng, )?; - // Derive `u_{i+1}.x[0], u_{i+1}.x[1]` by hashing folded instances - u_i1_x = U_i1.hash( - &sponge, - self.pp_hash, - self.i + C1::ScalarField::one(), - &self.z_0, - &z_i1, - ); - cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, self.pp_hash); - augmented_F_circuit = AugmentedFCircuit { _gc2: PhantomData, poseidon_config: self.poseidon_config.clone(), @@ -1005,14 +944,12 @@ where K_coeffs: proof.K_coeffs.clone(), phi_stars: aux.phi_stars, F: self.F.clone(), - x: Some(u_i1_x), // cyclefold values cf1_u_i_cmW: cf1_u_i.cmW, cf2_u_i_cmW: cf2_u_i.cmW, cf_U_i: self.cf_U_i.clone(), cf1_cmT, cf2_cmT, - cf_x: Some(cf_u_i1_x), }; #[cfg(test)] @@ -1042,16 +979,15 @@ where let cs = ConstraintSystem::::new_ref(); - augmented_F_circuit.generate_constraints(cs.clone())?; + let z_i1 = augmented_F_circuit + .compute_next_state(cs.clone())? + .value()?; #[cfg(test)] assert!(cs.is_satisfied()?); let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let (w_i1, x_i1) = extract_w_x::(&cs); - if x_i1[0] != u_i1_x || x_i1[1] != cf_u_i1_x { - return Err(Error::NotEqual); - } #[cfg(test)] if x_i1.len() != 2 { @@ -1205,7 +1141,6 @@ where transcript: &mut PoseidonSponge, cf_W_i: CycleFoldWitness, // witness of the running instance cf_U_i: CycleFoldCommittedInstance, // running instance - cf_u_i_x: Vec, cf_circuit: ProtoGalaxyCycleFoldCircuit, rng: &mut impl RngCore, ) -> Result< @@ -1226,7 +1161,6 @@ where self.pp_hash, cf_W_i, cf_U_i, - cf_u_i_x, cf_circuit, rng, ) diff --git a/folding-schemes/src/frontend/mod.rs b/folding-schemes/src/frontend/mod.rs index 688abc26..b5a352f5 100644 --- a/folding-schemes/src/frontend/mod.rs +++ b/folding-schemes/src/frontend/mod.rs @@ -24,17 +24,6 @@ pub trait FCircuit: Clone + Debug { /// are optional, and in case no external inputs are used, this method should return 0. fn external_inputs_len(&self) -> usize; - /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new - /// z_{i+1} - fn step_native( - // this method uses self, so that each FCircuit implementation (and different frontends) - // can hold a state if needed to store data to compute the next state. - &self, - i: usize, - z_i: Vec, - external_inputs: Vec, // inputs that are not part of the state - ) -> Result, Error>; - /// generates the constraints for the step of F for the given z_i fn generate_step_constraints( // this method uses self, so that each FCircuit implementation (and different frontends) @@ -53,7 +42,7 @@ pub mod tests { use ark_bn254::Fr; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; - use utils::{CubicFCircuit, CustomFCircuit, WrapperCircuit}; + use utils::{custom_step_native, CubicFCircuit, CustomFCircuit, WrapperCircuit}; #[test] fn test_testfcircuit() -> Result<(), Error> { @@ -79,7 +68,7 @@ pub mod tests { let wrapper_circuit = WrapperCircuit::> { FC: custom_circuit, z_i: Some(z_i.clone()), - z_i1: Some(custom_circuit.step_native(0, z_i, vec![])?), + z_i1: Some(custom_step_native(z_i, n_constraints)), }; wrapper_circuit.generate_constraints(cs.clone())?; assert_eq!(cs.num_constraints(), n_constraints); diff --git a/folding-schemes/src/frontend/utils.rs b/folding-schemes/src/frontend/utils.rs index 24b0cdd7..d296301f 100644 --- a/folding-schemes/src/frontend/utils.rs +++ b/folding-schemes/src/frontend/utils.rs @@ -33,14 +33,6 @@ impl FCircuit for DummyCircuit { fn external_inputs_len(&self) -> usize { self.external_inputs_len } - fn step_native( - &self, - _i: usize, - _z_i: Vec, - _external_inputs: Vec, - ) -> Result, Error> { - Ok(vec![F::zero(); self.state_len]) - } fn generate_step_constraints( &self, cs: ConstraintSystemRef, @@ -74,14 +66,6 @@ impl FCircuit for CubicFCircuit { fn external_inputs_len(&self) -> usize { 0 } - fn step_native( - &self, - _i: usize, - z_i: Vec, - _external_inputs: Vec, - ) -> Result, Error> { - Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)]) - } fn generate_step_constraints( &self, cs: ConstraintSystemRef, @@ -96,6 +80,13 @@ impl FCircuit for CubicFCircuit { } } +/// Native implementation of `CubicFCircuit` +#[cfg(test)] +pub fn cubic_step_native(z_i: Vec) -> Vec { + let z = z_i[0]; + vec![z * z * z + z + F::from(5)] +} + /// CustomFCircuit is a circuit that has the number of constraints specified in the /// `n_constraints` parameter. Note that the generated circuit will have very sparse matrices. #[derive(Clone, Copy, Debug)] @@ -119,18 +110,6 @@ impl FCircuit for CustomFCircuit { fn external_inputs_len(&self) -> usize { 0 } - fn step_native( - &self, - _i: usize, - z_i: Vec, - _external_inputs: Vec, - ) -> Result, Error> { - let mut z_i1 = z_i[0]; - for _ in 0..self.n_constraints - 1 { - z_i1 = z_i1.square(); - } - Ok(vec![z_i1]) - } fn generate_step_constraints( &self, _cs: ConstraintSystemRef, @@ -147,6 +126,16 @@ impl FCircuit for CustomFCircuit { } } +/// Native implementation of `CubicFCircuit` +#[cfg(test)] +pub fn custom_step_native(z_i: Vec, n_constraints: usize) -> Vec { + let mut z_i1 = z_i[0]; + for _ in 0..n_constraints - 1 { + z_i1 = z_i1.square(); + } + vec![z_i1] +} + /// WrapperCircuit is a circuit that wraps any circuit that implements the FCircuit trait. This /// is used to test the `FCircuit.generate_step_constraints` method. This is a similar wrapping /// than the one done in the `AugmentedFCircuit`, but without adding all the extra constraints diff --git a/solidity-verifiers/src/verifiers/nova_cyclefold.rs b/solidity-verifiers/src/verifiers/nova_cyclefold.rs index 559c3cce..1286fd81 100644 --- a/solidity-verifiers/src/verifiers/nova_cyclefold.rs +++ b/solidity-verifiers/src/verifiers/nova_cyclefold.rs @@ -208,14 +208,6 @@ mod tests { fn external_inputs_len(&self) -> usize { 0 } - fn step_native( - &self, - _i: usize, - z_i: Vec, - _external_inputs: Vec, - ) -> Result, Error> { - Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)]) - } fn generate_step_constraints( &self, cs: ConstraintSystemRef, @@ -251,24 +243,6 @@ mod tests { fn external_inputs_len(&self) -> usize { 0 } - - /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new - /// z_{i+1} - fn step_native( - &self, - _i: usize, - z_i: Vec, - _external_inputs: Vec, - ) -> Result, Error> { - let a = z_i[0] + F::from(4_u32); - let b = z_i[1] + F::from(40_u32); - let c = z_i[2] * F::from(4_u32); - let d = z_i[3] * F::from(40_u32); - let e = z_i[4] + F::from(100_u32); - - Ok(vec![a, b, c, d, e]) - } - /// generates the constraints for the step of F for the given z_i fn generate_step_constraints( &self,