diff --git a/folding-schemes/src/arith/ccs/circuits.rs b/folding-schemes/src/arith/ccs/circuits.rs index 5a2e99ab..ebdaeb7a 100644 --- a/folding-schemes/src/arith/ccs/circuits.rs +++ b/folding-schemes/src/arith/ccs/circuits.rs @@ -1,3 +1,9 @@ +//! Circuit implementations for Customizable Constraint Systems (CCS). +//! +//! This module provides the circuit (gadget) variants of CCS components for use in +//! constraint system implementations. These are used when CCS operations need to +//! be performed inside another constraint system. + use super::CCS; use crate::utils::gadgets::SparseMatrixVar; use ark_ff::PrimeField; @@ -8,10 +14,11 @@ use ark_r1cs_std::{ use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_std::borrow::Borrow; -/// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters. +/// [`CCSMatricesVar`] contains the matrices 'M' of the CCS without the rest of CCS parameters. #[derive(Debug, Clone)] pub struct CCSMatricesVar { - // we only need native representation, so the constraint field==F + /// Vector of sparse matrices in their circuit representation + /// We only need native representation, so the constraint field equals F pub M: Vec>>, } diff --git a/folding-schemes/src/arith/ccs/mod.rs b/folding-schemes/src/arith/ccs/mod.rs index 666ef9e2..9758bf85 100644 --- a/folding-schemes/src/arith/ccs/mod.rs +++ b/folding-schemes/src/arith/ccs/mod.rs @@ -1,3 +1,29 @@ +//! Implementation of Customizable Constraint Systems (CCS). +//! +//! This module provides an implementation of CCS as defined in the +//! [CCS paper](https://eprint.iacr.org/2023/552). CCS is a generalization +//! of constraint systems that allows for flexible constraint expressions +//! via configurable matrix operations. +//! +//! # Structure +//! +//! A CCS consists of: +//! * A set of sparse matrices M₁...Mₜ ∈ F^{m×n} +//! * A collection of multisets S₁...Sₘ containing indices into these matrices +//! * A vector of coefficients c₁...cₘ +//! +//! # Example +//! +//! A simple R1CS constraint system can be represented as a CCS with: +//! * Three matrices A, B, C +//! * Two multisets S₁ = {0,1}, S₂ = {2} +//! * Coefficients c₁ = 1, c₂ = -1 +//! +//! # Organization +//! +//! * [`CCS`] - The main CCS type implementing the [`Arith`] trait +//! * [`circuits`] - Circuit implementations for CCS operations + use ark_ff::PrimeField; use ark_std::log2; @@ -42,6 +68,13 @@ pub struct CCS { impl CCS { /// Evaluates the CCS relation at a given vector of assignments `z` + /// + /// # Errors + /// + /// Returns an error if: + /// * Vector operations fail due to mismatched dimensions + /// * Matrix-vector multiplication fails + /// * Vector addition or hadamard multiplication fails pub fn eval_at_z(&self, z: &[F]) -> Result, Error> { let mut result = vec![F::zero(); self.m]; @@ -51,7 +84,7 @@ impl CCS { // complete the hadamard chain let mut hadamard_result = vec![F::one(); self.m]; - for M_j in vec_M_j.into_iter() { + for M_j in vec_M_j { hadamard_result = hadamard(&hadamard_result, &mat_vec_mul(M_j, z)?)?; } @@ -67,7 +100,7 @@ impl CCS { /// returns a tuple containing (w, x) (witness and public inputs respectively) pub fn split_z(&self, z: &[F]) -> (Vec, Vec) { - (z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec()) + (z[self.l + 1..].to_vec(), z[1..=self.l].to_vec()) } } @@ -101,7 +134,7 @@ impl From> for CCS { fn from(r1cs: R1CS) -> Self { let m = r1cs.num_constraints(); let n = r1cs.num_variables(); - CCS { + Self { m, n, l: r1cs.num_public_inputs(), @@ -130,6 +163,7 @@ pub mod tests { pub fn get_test_ccs() -> CCS { get_test_r1cs::().into() } + pub fn get_test_z(input: usize) -> Vec { r1cs_get_test_z(input) } diff --git a/folding-schemes/src/arith/mod.rs b/folding-schemes/src/arith/mod.rs index b5ebd7b8..98bd28d8 100644 --- a/folding-schemes/src/arith/mod.rs +++ b/folding-schemes/src/arith/mod.rs @@ -1,3 +1,48 @@ +//! Trait definitions and implementations for arithmetic constraint systems. +//! +//! This module provides core abstractions for working with different types of arithmetic +//! constraint systems like R1CS (Rank-1 Constraint Systems) and CCS (Customizable Constraint Systems). +//! The key trait [`Arith`] defines a generic interface that constraint systems must implement, +//! allowing the rest of the library to work with different constraint system implementations in a +//! uniform way. +//! +//! # Key Traits +//! +//! * [`Arith`] - Core trait defining operations that constraint systems must support +//! * [`ArithGadget`] - In-circuit counterpart operations for constraint systems +//! * [`ArithSampler`] - Optional trait for sampling random satisfying witness-instance pairs +//! * [`ArithSerializer`] - Serialization support for constraint systems +//! +//! # Modules +//! +//! * [`ccs`] - Implementation of Customizable Constraint Systems (CCS) +//! * [`r1cs`] - Implementation of Rank-1 Constraint Systems (R1CS) +//! +//! # Examples of Supported Systems +//! +//! The module supports various constraint system types including: +//! +//! * Plain R1CS - Traditional rank-1 constraint systems +//! * Nova's Relaxed R1CS - Modified R1CS with relaxation terms +//! * ProtoGalaxy's Relaxed R1CS - Alternative relaxation approach +//! * CCS - Customizable constraint systems with different witness/instance types +//! +//! Each system can define its own witness (`W`) and instance (`U`) types while implementing +//! the common [`Arith`] interface, allowing for flexible constraint system implementations +//! while maintaining a consistent API. +//! +//! # Evaluation Types +//! +//! Different constraint systems can have different evaluation semantics: +//! +//! * Plain R1CS evaluates to `Az ∘ Bz - Cz` +//! * Nova's Relaxed R1CS evaluates to `Az ∘ Bz - uCz` +//! * ProtoGalaxy's version evaluates to `Az ∘ Bz - Cz` +//! * CCS can have various evaluation forms +//! +//! The [`Arith`] trait's associated `Evaluation` type allows each system to define its own +//! evaluation result type while maintaining type safety. + use ark_ec::CurveGroup; use ark_relations::r1cs::SynthesisError; use ark_std::rand::RngCore; @@ -42,6 +87,7 @@ pub mod r1cs; /// elements, [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::lcccs::LCCCS`], /// or [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::cccs::CCCS`]. pub trait Arith: Clone { + /// The type for the output of the relation's evalation. type Evaluation; /// Returns a dummy witness and instance @@ -64,11 +110,16 @@ pub trait Arith: Clone { /// - Evaluating the relaxed R1CS in Nova at `W = (w, e, ...)` and /// `U = (u, x, ...)` returns `Az ∘ Bz - uCz`, where `z = [u, x, w]`. /// - /// - Evaluating the relaxed R1CS in ProtoGalaxy at `W = (w, ...)` and + /// - Evaluating the relaxed R1CS in [`crate::folding::protogalaxy`] at `W = (w, ...)` and /// `U = (x, e, β, ...)` returns `Az ∘ Bz - Cz`, where `z = [1, x, w]`. /// /// However, we use `Self::Evaluation` to represent the evaluation result /// for future extensibility. + /// + /// # Errors + /// Returns an error if: + /// - The dimensions of vectors don't match the expected sizes + /// - The evaluation calculations fail fn eval_relation(&self, w: &W, u: &U) -> Result; /// Checks if the evaluation result is valid. The witness `w` and instance @@ -82,9 +133,14 @@ pub trait Arith: Clone { /// - The evaluation `v` of relaxed R1CS in Nova at satisfying `W` and `U` /// should be equal to the error term `e` in the witness. /// - /// - The evaluation `v` of relaxed R1CS in ProtoGalaxy at satisfying `W` + /// - The evaluation `v` of relaxed R1CS in [`crate::folding::protogalaxy`] at satisfying `W` /// and `U` should satisfy `e = Σ pow_i(β) v_i`, where `e` is the error /// term in the committed instance. + /// + /// # Errors + /// Returns an error if: + /// - The evaluation result is not valid for the given witness and instance + /// - The evaluation result fails to satisfy the constraint system requirements fn check_evaluation(w: &W, u: &U, v: Self::Evaluation) -> Result<(), Error>; /// Checks if witness `w` and instance `u` satisfy the constraint system @@ -92,6 +148,12 @@ pub trait Arith: Clone { /// validity of the evaluation result. /// /// Used only for testing. + /// + /// # Errors + /// Returns an error if: + /// - The evaluation fails (`eval_relation` errors) + /// - The evaluation check fails (`check_evaluation` errors) + /// - The witness and instance do not satisfy the constraint system fn check_relation(&self, w: &W, u: &U) -> Result<(), Error> { let e = self.eval_relation(w, u)?; Self::check_evaluation(w, u, e) @@ -125,6 +187,12 @@ pub trait ArithSerializer { /// [HyperNova]: https://eprint.iacr.org/2023/573.pdf pub trait ArithSampler: Arith { /// Samples a random witness and instance that satisfy the constraint system. + /// + /// # Errors + /// Returns an error if: + /// - Sampling of random values fails + /// - The commitment scheme operations fail + /// - The sampled values do not satisfy the constraint system fn sample_witness_instance>( &self, params: &CS::ProverParams, @@ -135,15 +203,28 @@ pub trait ArithSampler: Arith { /// `ArithGadget` defines the in-circuit counterparts of operations specified in /// `Arith` on constraint systems. pub trait ArithGadget { + /// The type for the output of the relation's evalation. type Evaluation; /// Evaluates the constraint system `self` at witness `w` and instance `u`. /// Returns the evaluation result. + /// + /// # Errors + /// Returns a `SynthesisError` if: + /// - Circuit constraint generation fails + /// - Variable allocation fails + /// - Required witness values are missing fn eval_relation(&self, w: &WVar, u: &UVar) -> Result; /// Generates constraints for enforcing that witness `w` and instance `u` /// satisfy the constraint system `self` by first computing the evaluation /// result and then checking the validity of the evaluation result. + /// + /// # Errors + /// Returns a `SynthesisError` if: + /// - The evaluation fails (`eval_relation` errors) + /// - The enforcement of evaluation constraints fails + /// - Circuit constraint generation fails fn enforce_relation(&self, w: &WVar, u: &UVar) -> Result<(), SynthesisError> { let e = self.eval_relation(w, u)?; Self::enforce_evaluation(w, u, e) @@ -152,5 +233,10 @@ pub trait ArithGadget { /// Generates constraints for enforcing that the evaluation result is valid. /// The witness `w` and instance `u` are also parameters, because the /// validity check may need information contained in `w` and/or `u`. + /// + /// # Errors + /// Returns a `SynthesisError` if: + /// - Circuit constraint generation fails for the evaluation check + /// - The enforcement of evaluation constraints fails fn enforce_evaluation(w: &WVar, u: &UVar, e: Self::Evaluation) -> Result<(), SynthesisError>; } diff --git a/folding-schemes/src/arith/r1cs/circuits.rs b/folding-schemes/src/arith/r1cs/circuits.rs index e870b585..b8f6c574 100644 --- a/folding-schemes/src/arith/r1cs/circuits.rs +++ b/folding-schemes/src/arith/r1cs/circuits.rs @@ -1,3 +1,9 @@ +//! Circuit implementations for Rank-1 Constraint Systems (R1CS). +//! +//! This module provides an in-circuit representation of R1CS for use within other circuits. +//! It includes support for both native field operations and non-native field arithmetic +//! through generic matrix and vector operations. + use crate::{ arith::ArithGadget, utils::gadgets::{EquivalenceGadget, MatrixGadget, SparseMatrixVar, VectorGadget}, @@ -11,19 +17,44 @@ use super::R1CS; /// An in-circuit representation of the `R1CS` struct. /// -/// `M` is for the modulo operation involved in the satisfiability check when -/// the underlying `FVar` is `NonNativeUintVar`. +/// This gadget allows R1CS operations to be performed within another constraint system. +/// +/// # Type Parameters +/// +/// * `M` - Type for the modulo operation in satisfiability checks when `FVar` is `NonNativeUintVar` +/// * `FVar` - Variable type representing field elements in the circuit #[derive(Debug, Clone)] pub struct R1CSMatricesVar { + /// Phantom data for the modulo type _m: PhantomData, + /// In-circuit representation of matrix A pub A: SparseMatrixVar, + /// In-circuit representation of matrix B pub B: SparseMatrixVar, + /// In-circuit representation of matrix C pub C: SparseMatrixVar, } impl> AllocVar, ConstraintF> for R1CSMatricesVar { + /// Creates a new circuit representation of R1CS matrices + /// + /// # Type Parameters + /// + /// * `T` - Type that can be borrowed as `R1CS` + /// + /// # Arguments + /// + /// * `cs` - Constraint system handle + /// * `f` - Function that returns the R1CS to be converted to circuit form + /// * `_mode` - Allocation mode (unused as matrices are allocated as constants) + /// + /// # Errors + /// + /// Returns a `SynthesisError` if: + /// * Matrix conversion to circuit form fails + /// * Circuit variable allocation fails fn new_variable>>( cs: impl Into>, f: impl FnOnce() -> Result, @@ -32,11 +63,12 @@ impl> f().and_then(|val| { let cs = cs.into(); + // TODO (autoparallel): How expensive are these clones? Is there a cheaper way to do this? Ok(Self { _m: PhantomData, A: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().A)?, B: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().B)?, - C: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().C)?, + C: SparseMatrixVar::::new_constant(cs, &val.borrow().C)?, }) }) } @@ -47,6 +79,24 @@ where SparseMatrixVar: MatrixGadget, [FVar]: VectorGadget, { + /// Evaluates the R1CS matrices at given circuit variable assignments + /// + /// # Arguments + /// + /// * `z` - Vector of circuit variables to evaluate at + /// + /// # Returns + /// + /// Returns a tuple (AzBz, uCz) where: + /// * AzBz is the Hadamard product of Az and Bz + /// * uCz is Cz scaled by z[0] + /// + /// # Errors + /// + /// Returns a `SynthesisError` if: + /// * Matrix-vector multiplication fails + /// * Hadamard product computation fails + /// * Vector operations fail pub fn eval_at_z(&self, z: &[FVar]) -> Result<(Vec, Vec), SynthesisError> { // Multiply Cz by z[0] (u) here, allowing this method to be reused for // both relaxed and unrelaxed R1CS. @@ -225,7 +275,7 @@ pub mod tests { impl ConstraintSynthesizer for Sha256TestCircuit { fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { let x = Vec::>::new_witness(cs.clone(), || Ok(self.x))?; - let y = Vec::>::new_input(cs.clone(), || Ok(self.y))?; + let y = Vec::>::new_input(cs, || Ok(self.y))?; let unitVar = UnitVar::default(); let comp_y = as CRHSchemeGadget>::evaluate(&unitVar, &x)?; @@ -289,15 +339,14 @@ pub mod tests { let cs = ConstraintSystem::::new_ref(); let wVar = WitnessVar::new_witness(cs.clone(), || Ok(&w))?; let uVar = CommittedInstanceVar::new_witness(cs.clone(), || Ok(&u))?; - let r1csVar = R1CSMatricesVar::>::new_witness(cs.clone(), || Ok(&r1cs))?; + let r1csVar = R1CSMatricesVar::>::new_witness(cs, || Ok(&r1cs))?; r1csVar.enforce_relation(&wVar, &uVar)?; // non-natively let cs = ConstraintSystem::::new_ref(); let wVar = CycleFoldWitnessVar::new_witness(cs.clone(), || Ok(w))?; let uVar = CycleFoldCommittedInstanceVar::<_, GVar2>::new_witness(cs.clone(), || Ok(u))?; - let r1csVar = - R1CSMatricesVar::>::new_witness(cs.clone(), || Ok(r1cs))?; + let r1csVar = R1CSMatricesVar::>::new_witness(cs, || Ok(r1cs))?; r1csVar.enforce_relation(&wVar, &uVar)?; Ok(()) } diff --git a/folding-schemes/src/arith/r1cs/mod.rs b/folding-schemes/src/arith/r1cs/mod.rs index 683d0888..d8039617 100644 --- a/folding-schemes/src/arith/r1cs/mod.rs +++ b/folding-schemes/src/arith/r1cs/mod.rs @@ -1,3 +1,23 @@ +//! Implementation of Rank-1 Constraint Systems (R1CS). +//! +//! This module provides an implementation of R1CS, which represents arithmetic circuits +//! as a system of bilinear constraints. An R1CS consists of three sparse matrices A, B, C +//! and defines relations of the form: +//! +//! (Az) ∘ (Bz) = Cz +//! +//! where z is a vector containing all circuit variables including: +//! * A constant 1 +//! * Public inputs +//! * Private witness values +//! +//! # Features +//! +//! * Standard R1CS constraint system +//! * Conversion to/from CCS format +//! * Support for relaxed R1CS variants +//! * Extraction from arkworks constraint systems + use ark_ff::PrimeField; use ark_relations::r1cs::ConstraintSystem; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; @@ -12,16 +32,27 @@ use crate::Error; pub mod circuits; +/// Represents a Rank-1 Constraint System with three sparse matrices A, B, C. #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct R1CS { - pub l: usize, // io len + /// Number of public inputs/outputs + pub l: usize, + /// Left matrix A pub A: SparseMatrix, + /// Right matrix B pub B: SparseMatrix, + /// Output matrix C pub C: SparseMatrix, } impl R1CS { /// Evaluates the CCS relation at a given vector of variables `z` + /// + /// # Errors + /// + /// Returns an error if: + /// * The length of z doesn't match the number of columns in the matrices + /// * Matrix operations fail due to dimension mismatches pub fn eval_at_z(&self, z: &[F]) -> Result, Error> { if z.len() != self.A.n_cols { return Err(Error::NotSameLength( @@ -66,15 +97,19 @@ impl ArithSerializer for R1CS { } } +// TODO (autoparallel): Many of these functions could be marked with `#[must_use]`(i.e., just like functions that return `Result` do). impl R1CS { + /// Creates an empty R1CS pub fn empty() -> Self { - R1CS { + Self { l: 0, A: SparseMatrix::empty(), B: SparseMatrix::empty(), C: SparseMatrix::empty(), } } + + /// Creates a random R1CS with given dimensions pub fn rand(rng: &mut R, n_rows: usize, n_cols: usize) -> Self { Self { l: 1, @@ -84,35 +119,39 @@ impl R1CS { } } + /// Returns the number of constraints #[inline] - pub fn num_constraints(&self) -> usize { + pub const fn num_constraints(&self) -> usize { self.A.n_rows } + /// Returns the number of public inputs #[inline] - pub fn num_public_inputs(&self) -> usize { + pub const fn num_public_inputs(&self) -> usize { self.l } + /// Returns the total number of variables #[inline] - pub fn num_variables(&self) -> usize { + pub const fn num_variables(&self) -> usize { self.A.n_cols } + /// Returns the number of witness variables #[inline] - pub fn num_witnesses(&self) -> usize { + pub const fn num_witnesses(&self) -> usize { self.num_variables() - self.num_public_inputs() - 1 } /// returns a tuple containing (w, x) (witness and public inputs respectively) pub fn split_z(&self, z: &[F]) -> (Vec, Vec) { - (z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec()) + (z[self.l + 1..].to_vec(), z[1..=self.l].to_vec()) } } impl From> for R1CS { fn from(ccs: CCS) -> Self { - R1CS:: { + Self { l: ccs.l, A: ccs.M[0].clone(), B: ccs.M[1].clone(), @@ -121,8 +160,14 @@ impl From> for R1CS { } } -/// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format as R1CS +/// extracts arkworks [`ConstraintSystem`] matrices into [`crate::utils::vec::SparseMatrix`] format as R1CS /// struct. +/// +/// # Errors +/// +/// Returns an error if: +/// * The constraint system matrices haven't been generated yet +/// * The conversion between matrix formats fails pub fn extract_r1cs(cs: &ConstraintSystem) -> Result, Error> { let m = cs.to_matrices().ok_or_else(|| { Error::ConversionError( @@ -160,6 +205,12 @@ pub fn extract_r1cs(cs: &ConstraintSystem) -> Result, } /// extracts the witness and the public inputs from arkworks ConstraintSystem. +/// +/// # Returns +/// +/// Returns a tuple (w, x) containing: +/// * w: The witness assignment vector +/// * x: The public input vector (excluding the constant 1) pub fn extract_w_x(cs: &ConstraintSystem) -> (Vec, Vec) { ( cs.witness_assignment.clone(), diff --git a/folding-schemes/src/commitment/ipa.rs b/folding-schemes/src/commitment/ipa.rs index a06949eb..b66ee2cb 100644 --- a/folding-schemes/src/commitment/ipa.rs +++ b/folding-schemes/src/commitment/ipa.rs @@ -1,12 +1,13 @@ -/// IPA implements the modified Inner Product Argument described in -/// [Halo](https://eprint.iacr.org/2019/1021.pdf). The variable names used follow the paper -/// notation in order to make it more readable. -/// -/// The implementation does the following optimizations in order to reduce the amount of -/// constraints in the circuit: -/// i. computation is done in log time following a modification of the equation 3 in section -/// 3.2 from the paper. -/// ii. s computation is done in 2^{k+1}-2 instead of k*2^k. +//! IPA implements the modified Inner Product Argument described in +//! [Halo](https://eprint.iacr.org/2019/1021.pdf). The variable names used follow the paper +//! notation in order to make it more readable. +//! +//! The implementation does the following optimizations in order to reduce the amount of +//! constraints in the circuit: +//! i. computation is done in log time following a modification of the equation 3 in section +//! 3.2 from the paper. +//! ii. s computation is done in 2^{k+1}-2 instead of k*2^k. + use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ @@ -30,23 +31,36 @@ use crate::utils::{ }; use crate::Error; +/// IPA proof structure containing all components needed for verification #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct Proof { - a: C::ScalarField, - l: Vec, - r: Vec, - L: Vec, - R: Vec, + /// Final value of vector a after the folding process + pub a: C::ScalarField, + /// Left blinding factors used in each round + pub l: Vec, + /// Right blinding factors used in each round + pub r: Vec, + /// Left commitments for each round + pub L: Vec, + /// Right commitments for each round + pub R: Vec, } -/// IPA implements the Inner Product Argument protocol following the CommitmentScheme trait. The -/// `H` parameter indicates if to use the commitment in hiding mode or not. +/// Implementation of the Inner Product Argument (IPA) as described in [Halo](https://eprint.iacr.org/2019/1021.pdf). +/// The variable names follow the paper notation to maintain clarity and readability. +/// +/// This implementation includes optimizations to reduce circuit constraints: +/// 1. The `` computation is done in log time using a modified version of equation 3 in section 3.2 +/// 2. The `s` computation is optimized to take 2^{k+1}-2 steps instead of k*2^k steps +/// +/// The `H` parameter indicates if to use the commitment in hiding mode or not. #[derive(Debug, Clone, Eq, PartialEq)] pub struct IPA { + /// The inner [`CurveGroup`] type _c: PhantomData, } -/// Implements the CommitmentScheme trait for IPA +/// Implements the [`CommitmentScheme`]` trait for IPA impl CommitmentScheme for IPA { type ProverParams = PedersenParams; type VerifierParams = PedersenParams; @@ -54,13 +68,6 @@ impl CommitmentScheme for IPA { type ProverChallenge = (C::ScalarField, C, Vec); type Challenge = (C::ScalarField, C, Vec); - fn is_hiding() -> bool { - if H { - return true; - } - false - } - fn setup( mut rng: impl RngCore, len: usize, @@ -110,26 +117,26 @@ impl CommitmentScheme for IPA { return Err(Error::BlindingNotZero); } let d = a.len(); + // TODO (autoparallel): Casting this back into `usize` could be dangerous in 32bit systems (e.g., wasm32) let k = (f64::from(d as u32).log2()) as usize; if params.generators.len() < a.len() { return Err(Error::PedersenParamsLen(params.generators.len(), a.len())); } // blinding factors - let l: Vec; - let r: Vec; - if H { + let (l, r) = if H { let rng = rng.ok_or(Error::MissingRandomness)?; - l = std::iter::repeat_with(|| C::ScalarField::rand(rng)) - .take(k) - .collect(); - r = std::iter::repeat_with(|| C::ScalarField::rand(rng)) - .take(k) - .collect(); + ( + std::iter::repeat_with(|| C::ScalarField::rand(rng)) + .take(k) + .collect(), + std::iter::repeat_with(|| C::ScalarField::rand(rng)) + .take(k) + .collect(), + ) } else { - l = vec![]; - r = vec![]; - } + (vec![], vec![]) + }; transcript.absorb_nonnative(P); let x = transcript.get_challenge(); // challenge value at which we evaluate @@ -202,8 +209,8 @@ impl CommitmentScheme for IPA { Ok(( Proof { a: a[0], - l: l.clone(), - r: r.clone(), + l, + r, L, R, }, @@ -229,8 +236,7 @@ impl CommitmentScheme for IPA { P: &C, // commitment proof: &Self::Proof, ) -> Result<(), Error> { - let (p, _r) = (proof.0.clone(), proof.1); - let k = p.L.len(); + let k = proof.0.L.len(); transcript.absorb_nonnative(P); let x = transcript.get_challenge(); // challenge value at which we evaluate @@ -238,8 +244,8 @@ impl CommitmentScheme for IPA { let U = C::generator().mul(s); let mut u: Vec = vec![C::ScalarField::zero(); k]; for i in (0..k).rev() { - transcript.absorb_nonnative(&p.L[i]); - transcript.absorb_nonnative(&p.R[i]); + transcript.absorb_nonnative(&proof.0.L[i]); + transcript.absorb_nonnative(&proof.0.R[i]); u[i] = transcript.get_challenge(); } let challenge = (x, U, u); @@ -247,6 +253,7 @@ impl CommitmentScheme for IPA { Self::verify_with_challenge(params, challenge, P, proof) } + // TODO (autoparallel): Can remove cloning of `proof.0` as we did in the `verify` method fn verify_with_challenge( params: &Self::VerifierParams, challenge: Self::Challenge, @@ -284,9 +291,10 @@ impl CommitmentScheme for IPA { } // compute b & G from s - let s = build_s(&u, &u_invs, k)?; + let s = build_s(&u, &u_invs, k); // b = = let b = s_b_inner(&u, &x)?; + // TODO (autoparallel): Casting this back into `usize` could be dangerous in 32bit systems (e.g., wasm32) let d: usize = 2_u64.pow(k as u32) as usize; if params.generators.len() < d { return Err(Error::PedersenParamsLen(params.generators.len(), d)); @@ -316,7 +324,10 @@ impl CommitmentScheme for IPA { } } -/// Computes s such that +/// Computes the s vector used in IPA verification +/// +/// The resulting s vector has the form: +/// ```text /// s = ( /// u₁⁻¹ u₂⁻¹ … uₖ⁻¹, /// u₁ u₂⁻¹ … uₖ⁻¹, @@ -325,10 +336,15 @@ impl CommitmentScheme for IPA { /// ⋮ ⋮ ⋮ /// u₁ u₂ … uₖ /// ) -/// Uses Halo2 approach computing $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} X^{2^i})$, -/// taking 2^{k+1}-2. -/// src: https://github.com/zcash/halo2/blob/81729eca91ba4755e247f49c3a72a4232864ec9e/halo2_proofs/src/poly/commitment/verifier.rs#L156 -fn build_s(u: &[F], u_invs: &[F], k: usize) -> Result, Error> { +/// ``` +/// Uses the Halo2 approach computing $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} X^{2^i})$, +/// which takes 2^{k+1}-2 steps. +/// src: +/// +/// # Errors +/// +/// Returns an error if the vector construction fails. +fn build_s(u: &[F], u_invs: &[F], k: usize) -> Vec { let d: usize = 2_u64.pow(k as u32) as usize; let mut s: Vec = vec![F::one(); d]; for (len, (u_j, u_j_inv)) in u @@ -347,7 +363,7 @@ fn build_s(u: &[F], u_invs: &[F], k: usize) -> Result, Err *s *= u_j; } } - Ok(s) + s } /// Computes (in-circuit) s such that @@ -361,12 +377,12 @@ fn build_s(u: &[F], u_invs: &[F], k: usize) -> Result, Err /// ) /// Uses Halo2 approach computing $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} X^{2^i})$, /// taking 2^{k+1}-2. -/// src: https://github.com/zcash/halo2/blob/81729eca91ba4755e247f49c3a72a4232864ec9e/halo2_proofs/src/poly/commitment/verifier.rs#L156 +/// src: fn build_s_gadget( u: &[EmulatedFpVar], u_invs: &[EmulatedFpVar], k: usize, -) -> Result>, SynthesisError> { +) -> Vec> { let d: usize = 2_u64.pow(k as u32) as usize; let mut s: Vec> = vec![EmulatedFpVar::one(); d]; for (len, (u_j, u_j_inv)) in u @@ -385,9 +401,15 @@ fn build_s_gadget( *s *= u_j; } } - Ok(s) + s } +/// Computes the inner product of two vectors +/// +/// # Errors +/// +/// Returns an error if: +/// - The vectors have different lengths fn inner_prod(a: &[F], b: &[F]) -> Result { if a.len() != b.len() { return Err(Error::NotSameLength( @@ -404,12 +426,19 @@ fn inner_prod(a: &[F], b: &[F]) -> Result { Ok(c) } -// g(x, u_1, u_2, ..., u_k) = , naively takes linear, but can compute in log time through -// g(x, u_1, u_2, ..., u_k) = \Prod u_i x^{2^i} + u_i^-1 +/// Computes g(x, u_1, u_2, ..., u_k) = efficiently in log time +/// +/// Rather than computing naively, this uses the formula: +/// g(x, u_1, u_2, ..., u_k) = \Prod u_i x^{2^i} + u_i^-1 +/// +/// # Errors +/// +/// Returns an error if: +/// - Computing any inverse fails fn s_b_inner(u: &[F], x: &F) -> Result { let mut c: F = F::one(); let mut x_2_i = *x; // x_2_i is x^{2^i}, starting from x^{2^0}=x - for u_i in u.iter() { + for u_i in u { c *= (*u_i * x_2_i) + u_i .inverse() @@ -419,30 +448,54 @@ fn s_b_inner(u: &[F], x: &F) -> Result { Ok(c) } -// g(x, u_1, u_2, ..., u_k) = , naively takes linear, but can compute in log time through -// g(x, u_1, u_2, ..., u_k) = \Prod u_i x^{2^i} + u_i^-1 +/// In-circuit implementation of s_b_inner function for verifying IPA proofs +/// +/// Computes g(x, u_1, u_2, ..., u_k) = in logarithmic time using the formula: +/// g(x, u_1, u_2, ..., u_k) = \Prod u_i x^{2^i} + u_i^-1 +/// +/// # Errors +/// +/// Returns a `SynthesisError` if: +/// - Computing any inverse fails +/// - Operations on emulated field variables fail fn s_b_inner_gadget( u: &[EmulatedFpVar], x: &EmulatedFpVar, ) -> Result, SynthesisError> { let mut c: EmulatedFpVar = EmulatedFpVar::::one(); let mut x_2_i = x.clone(); // x_2_i is x^{2^i}, starting from x^{2^0}=x - for u_i in u.iter() { + for u_i in u { c *= u_i.clone() * x_2_i.clone() + u_i.inverse()?; x_2_i *= x_2_i.clone(); } Ok(c) } +/// Type alias for the constraint field of a curve group +/// +/// This represents the base prime field of the base field of the curve group. +/// Used for constraint generation in circuit implementations. pub type CF = <::BaseField as Field>::BasePrimeField; +/// In-circuit variable holding an IPA proof +/// +/// Contains the circuit variable representations of all components needed for IPA proof verification. +/// The type parameters are: +/// - `C`: The curve group for the commitment scheme +/// - `GC`: The variable representation of curve group elements pub struct ProofVar>> { - a: EmulatedFpVar>, - l: Vec>>, - r: Vec>>, - L: Vec, - R: Vec, + /// Final value of vector a after folding + pub a: EmulatedFpVar>, + /// Left blinding factors used in each round + pub l: Vec>>, + /// Right blinding factors used in each round + pub r: Vec>>, + /// Left commitments for each round + pub L: Vec, + /// Right commitments for each round + pub R: Vec, } + impl AllocVar, CF> for ProofVar where C: CurveGroup, @@ -467,16 +520,17 @@ where let r: Vec>> = Vec::new_variable(cs.clone(), || Ok(val.borrow().r.clone()), mode)?; let L: Vec = Vec::new_variable(cs.clone(), || Ok(val.borrow().L.clone()), mode)?; - let R: Vec = Vec::new_variable(cs.clone(), || Ok(val.borrow().R.clone()), mode)?; + let R: Vec = Vec::new_variable(cs, || Ok(val.borrow().R.clone()), mode)?; Ok(Self { a, l, r, L, R }) }) } } -/// IPAGadget implements the circuit that verifies an IPA Proof. The `H` parameter indicates if to -/// use the commitment in hiding mode or not, reducing a bit the number of constraints needed in -/// the later case. +/// In-circuit IPA verification gadget implementation +/// +/// Provides constraint generation for verifying IPA proofs. The `H` parameter indicates if the +/// commitment is in hiding mode, which affects the number of constraints needed. pub struct IPAGadget where C: CurveGroup, @@ -495,6 +549,28 @@ where /// Verify the IPA opening proof, K=log2(d), where d is the degree of the committed polynomial, /// and H indicates if the commitment is in hiding mode and thus uses blinding factors, if not, /// there are some constraints saved. + /// + /// # Arguments + /// + /// * `g` - Commitment scheme generators + /// * `h` - Commitment scheme hiding generator + /// * `x` - Evaluation point challenge + /// * `v` - Value at evaluation point + /// * `P` - Original commitment + /// * `p` - IPA proof + /// * `r` - Blinding factor + /// * `u` - Array of K challenges + /// * `U` - Additional challenge point + /// + /// # Errors + /// + /// Returns a `SynthesisError` if: + /// - The proof's L or R vectors' length does not match K + /// - The number of generators is less than K + /// - Converting field elements to bits fails + /// - Computing inverses fails + /// - Scalar multiplication fails + /// - Field operations like square/multiply fail #[allow(clippy::too_many_arguments)] pub fn verify( g: &[GC], // params.generators @@ -522,7 +598,7 @@ where } // compute b & G from s - let s = build_s_gadget(u, &u_invs, K)?; + let s = build_s_gadget(u, &u_invs, K); // b = = let b = s_b_inner_gadget(u, x)?; // ensure that generators.len() === s.len(): @@ -574,16 +650,17 @@ mod tests { #[test] fn test_ipa() -> Result<(), Error> { - let _ = test_ipa_opt::()?; - let _ = test_ipa_opt::()?; + test_ipa_opt::()?; + test_ipa_opt::()?; Ok(()) } fn test_ipa_opt() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - const k: usize = 4; + // TODO (autoparallel): Casting into `usize` may be dangerous on 32bit systems (e.g., wasm32) const d: usize = 2_u64.pow(k as u32) as usize; + let mut rng = ark_std::test_rng(); + // setup params let (params, _) = IPA::::setup(&mut rng, d)?; @@ -619,16 +696,17 @@ mod tests { #[test] fn test_ipa_gadget() -> Result<(), Error> { - let _ = test_ipa_gadget_opt::()?; - let _ = test_ipa_gadget_opt::()?; + test_ipa_gadget_opt::()?; + test_ipa_gadget_opt::()?; Ok(()) } fn test_ipa_gadget_opt() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - const k: usize = 3; + // TODO (autoparallel): Casting into `usize` may be dangerous on 32bit systems (e.g., wasm32) const d: usize = 2_u64.pow(k as u32) as usize; + let mut rng = ark_std::test_rng(); + // setup params let (params, _) = IPA::::setup(&mut rng, d)?; diff --git a/folding-schemes/src/commitment/kzg.rs b/folding-schemes/src/commitment/kzg.rs index 84bde282..6e3a6bcb 100644 --- a/folding-schemes/src/commitment/kzg.rs +++ b/folding-schemes/src/commitment/kzg.rs @@ -1,10 +1,18 @@ -/// Adaptation of the prover methods and structs from arkworks/poly-commit's KZG10 implementation -/// into the CommitmentScheme trait. -/// -/// The motivation to do so, is that we want to be able to use KZG / Pedersen for committing to -/// vectors indistinctly, and the arkworks KZG10 implementation contains all the methods under the -/// same trait, which requires the Pairing trait, where the prover does not need access to the -/// Pairing but only to G1. +//! KZG polynomial commitment scheme implementation. +//! +//! This module provides an implementation of the KZG polynomial commitment scheme that implements +//! the [`CommitmentScheme`] trait. The implementation is adapted from arkworks' KZG10 implementation +//! to work with just `CurveGroup` rather than requiring full pairing operations for the prover. +//! +//! # Overview +//! +//! The KZG polynomial commitment scheme allows proving evaluations of committed polynomials. +//! This implementation: +//! +//! - Adapts the arkworks KZG10 implementation to work with the [`CommitmentScheme`] trait +//! - Separates prover operations to only require `CurveGroup` operations, not full pairings +//! - Currently only supports non-hiding commitments + use ark_ec::{pairing::Pairing, CurveGroup, VariableBaseMSM}; use ark_ff::PrimeField; use ark_poly::{ @@ -26,11 +34,14 @@ use crate::transcript::Transcript; use crate::utils::vec::poly_from_vec; use crate::Error; -/// ProverKey defines a similar struct as in ark_poly_commit::kzg10::Powers, but instead of -/// depending on the Pairing trait it depends on the CurveGroup trait. +/// Prover key containing powers of group elements needed for KZG polynomial commitments. +/// +/// This is similar to `ark_poly_commit::kzg10::Powers` but depends only on `CurveGroup` +/// rather than requiring pairing operations. #[derive(Debug, Clone, Default, Eq, PartialEq)] pub struct ProverKey<'a, C: CurveGroup> { /// Group elements of the form `β^i G`, for different values of `i`. + /// These are used to commit to polynomial coefficients. pub powers_of_g: Cow<'a, [C::Affine]>, } @@ -70,19 +81,44 @@ impl<'a, C: CurveGroup> Valid for ProverKey<'a, C> { } } +/// Proof of polynomial evaluation at a point. +/// +/// Contains both the claimed evaluation and a KZG proof element. #[derive(Debug, Clone, Default, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct Proof { + /// The claimed evaluation value f(z) pub eval: C::ScalarField, + + /// The proof element π = (f(X) - f(z))/(X - z) pub proof: C, } -/// KZG implements the CommitmentScheme trait for the KZG commitment scheme. +/// KZG polynomial commitment scheme implementation. +/// +/// This implements the [`CommitmentScheme`] trait for KZG polynomial commitments. +/// The type parameter `H` controls whether hiding commitments are used (currently unsupported). +/// +/// # Type Parameters +/// +/// * `'a` - Lifetime of the prover parameters +/// * `E` - The pairing engine +/// * `H` - Whether hiding commitments are used (must be false currently) #[derive(Debug, Clone, Default, Eq, PartialEq)] pub struct KZG<'a, E: Pairing, const H: bool = false> { + /// Inner lifetime accounting _a: PhantomData<&'a ()>, + /// Inner [`Pairing`] friendly curve _e: PhantomData, } +/* +TODO (autoparallel): Okay, I'm noticing something here, basically I think that there should likely be two implementations for `CommitmentScheme`, +one that is hiding, and one that is not (as opposed to the const generic `H` in the trait itself). We could have `HidingCommitmentScheme: CommitmentScheme`. +If impl `CommitmentScheme for KZG<'a, E, H>`, then we can impl `HidingCommitmentScheme for KZG<'a, E, true>`. The implementation of `HidingCommitmentScheme` +would be super straight forward as it would just add the blinding factor to the output of the "super" `CommitmentScheme` `commit` and `prove` methods. Then those +methods on `CommitmentScheme` do not have to take in `blind: Option` or the `dyn Rng`. +*/ + impl<'a, E, const H: bool> CommitmentScheme for KZG<'a, E, H> where E: Pairing, @@ -93,20 +129,12 @@ where type ProverChallenge = E::ScalarField; type Challenge = E::ScalarField; - fn is_hiding() -> bool { - if H { - return true; - } - false - } - - /// setup returns the tuple (ProverKey, VerifierKey). For real world deployments the setup must - /// be computed in the most trustless way possible, usually through a MPC ceremony. fn setup( mut rng: impl RngCore, len: usize, ) -> Result<(Self::ProverParams, Self::VerifierParams), Error> { let len = len.next_power_of_two(); + // TODO (autoparallel): This `expect` will panic when the function itself is capable of returning an error. let universal_params = KZG10::>::setup(len, false, &mut rng) .expect("Setup failed"); @@ -125,7 +153,7 @@ where Ok((powers, vk)) } - /// commit implements the CommitmentScheme commit interface, adapting the implementation from + /// commit implements the [`CommitmentScheme`] commit interface, adapting the implementation from /// https://github.com/arkworks-rs/poly-commit/tree/c724fa666e935bbba8db5a1421603bab542e15ab/poly-commit/src/kzg10/mod.rs#L178 /// with the main difference being the removal of the blinding factors and the no-dependency to /// the Pairing trait. @@ -134,6 +162,7 @@ where v: &[E::ScalarField], _blind: &E::ScalarField, ) -> Result { + // TODO (autoparallel): awk to use `_` prefix here. if !_blind.is_zero() || H { return Err(Error::NotSupportedYet("hiding".to_string())); } @@ -150,8 +179,8 @@ where Ok(commitment) } - /// prove implements the CommitmentScheme prove interface, adapting the implementation from - /// https://github.com/arkworks-rs/poly-commit/tree/c724fa666e935bbba8db5a1421603bab542e15ab/poly-commit/src/kzg10/mod.rs#L307 + /// prove implements the [`CommitmentScheme`] prove interface, adapting the implementation from + /// /// with the main difference being the removal of the blinding factors and the no-dependency to /// the Pairing trait. fn prove( @@ -164,6 +193,7 @@ where ) -> Result { transcript.absorb_nonnative(cm); let challenge = transcript.get_challenge(); + // TODO (autoparallel): awk to use `_` prefix here. Self::prove_with_challenge(params, challenge, v, _blind, _rng) } @@ -174,6 +204,7 @@ where _blind: &E::ScalarField, _rng: Option<&mut dyn RngCore>, ) -> Result { + // TODO (autoparallel): awk to use `_` prefix here. if !_blind.is_zero() || H { return Err(Error::NotSupportedYet("hiding".to_string())); } @@ -251,7 +282,8 @@ where } } -fn check_degree_is_too_large( +/// Helper function to check if polynomial degree exceeds supported length +const fn check_degree_is_too_large( degree: usize, num_powers: usize, ) -> Result<(), ark_poly_commit::error::Error> { @@ -266,6 +298,7 @@ fn check_degree_is_too_large( } } +/// Helper function to skip leading zero coefficients and convert to bigints fn skip_first_zero_coeffs_and_convert_to_bigints>( p: &P, ) -> (usize, Vec) { @@ -277,6 +310,7 @@ fn skip_first_zero_coeffs_and_convert_to_bigints(p: &[F]) -> Vec { ark_std::cfg_iter!(p) .map(|s| s.into_bigint()) diff --git a/folding-schemes/src/commitment/mod.rs b/folding-schemes/src/commitment/mod.rs index a6de1f7d..0f68017c 100644 --- a/folding-schemes/src/commitment/mod.rs +++ b/folding-schemes/src/commitment/mod.rs @@ -1,3 +1,27 @@ +//! Vector commitment schemes implementation. +//! +//! This module provides trait definitions and implementations for various vector commitment schemes, +//! supporting both hiding and non-hiding variants. Implementations include: +//! +//! * Pedersen commitments ([`pedersen`]) +//! * Inner Product Arguments ([`ipa`]) +//! * Kate-Zaverucha-Goldberg commitments ([`kzg`]) +//! +//! # Usage +//! +//! Each commitment scheme implements the [`CommitmentScheme`] trait, providing a uniform interface for: +//! +//! * Parameter setup +//! * Committing to vectors +//! * Generating proofs +//! * Verifying proofs +//! +//! # Features +//! +//! * Configurable hiding/non-hiding modes via const generic parameter +//! * Support for transcript-based and standalone proof generation +//! * Homomorphic properties for commitment combinations + use ark_ec::CurveGroup; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::fmt::Debug; @@ -10,28 +34,86 @@ pub mod ipa; pub mod kzg; pub mod pedersen; -/// CommitmentScheme defines the vector commitment scheme trait. Where `H` indicates if to use the -/// commitment in hiding mode or not. +/// Defines the interface for vector commitment schemes. +/// +/// # Type Parameters +/// +/// * `C` - The curve group used for commitments +/// * `H` - Boolean indicating if the scheme is hiding (`true`) or non-hiding (`false`) +/// +/// Each implementation provides associated types for parameters, proofs, and challenges, +/// along with methods for setup, commitment, proving, and verification. pub trait CommitmentScheme: Clone + Debug { + /// Parameters used by the prover type ProverParams: Clone + Debug + CanonicalSerialize + CanonicalDeserialize; + /// Parameters used by the verifier type VerifierParams: Clone + Debug + CanonicalSerialize + CanonicalDeserialize; + /// Proof type for opening commitments type Proof: Clone + Debug + CanonicalSerialize + CanonicalDeserialize; + /// Challenge type used by the prover type ProverChallenge: Clone + Debug; + /// Challenge type used by the verifier type Challenge: Clone + Debug; - fn is_hiding() -> bool; + /// Returns whether this instantiation is hiding + fn is_hiding() -> bool { + H + } + /// Generates parameters for the commitment scheme + /// + /// # Arguments + /// + /// * `rng` - Random number generator + /// * `len` - Maximum length of vectors to be committed + /// + /// # Errors + /// + /// Returns an error if: + /// * Parameter generation fails + /// * The requested length is invalid fn setup( rng: impl RngCore, len: usize, ) -> Result<(Self::ProverParams, Self::VerifierParams), Error>; + /// Commits to a vector using the given parameters + /// + /// # Arguments + /// + /// * `params` - Prover parameters + /// * `v` - Vector to commit to + /// * `blind` - Blinding factor (must be zero for non-hiding schemes) + /// + /// # Errors + /// + /// Returns an error if: + /// * Vector length exceeds parameter size + /// * Non-zero blinding used in non-hiding mode + /// * Commitment computation fails fn commit( params: &Self::ProverParams, v: &[C::ScalarField], blind: &C::ScalarField, ) -> Result; + /// Generates a proof for a commitment using a transcript + /// + /// # Arguments + /// + /// * `params` - Prover parameters + /// * `transcript` - Transcript for Fiat-Shamir + /// * `cm` - Commitment to prove + /// * `v` - Committed vector + /// * `blind` - Blinding factor used in commitment + /// * `rng` - Optional RNG for randomized proofs + /// + /// # Errors + /// + /// Returns an error if: + /// * Proof generation fails + /// * Parameters are invalid + /// * Vector/commitment mismatch fn prove( params: &Self::ProverParams, transcript: &mut impl Transcript, @@ -41,8 +123,13 @@ pub trait CommitmentScheme: Clone + Debug rng: Option<&mut dyn RngCore>, ) -> Result; - /// same as `prove` but instead of providing a Transcript to use, providing the already - /// computed challenge + /// Generates a proof using a pre-computed challenge + /// + /// Similar to [`prove`](Self::prove) but uses a provided challenge instead of a transcript. + /// + /// # Errors + /// + /// Returns an error if proof generation fails fn prove_with_challenge( params: &Self::ProverParams, challenge: Self::ProverChallenge, @@ -51,6 +138,21 @@ pub trait CommitmentScheme: Clone + Debug rng: Option<&mut dyn RngCore>, ) -> Result; + /// Verifies a proof using a transcript + /// + /// # Arguments + /// + /// * `params` - Verifier parameters + /// * `transcript` - Transcript for Fiat-Shamir + /// * `cm` - Commitment to verify + /// * `proof` - Proof to check + /// + /// # Errors + /// + /// Returns an error if: + /// * The proof is invalid + /// * Parameters are invalid + /// * Verification computation fails fn verify( params: &Self::VerifierParams, transcript: &mut impl Transcript, @@ -58,8 +160,13 @@ pub trait CommitmentScheme: Clone + Debug proof: &Self::Proof, ) -> Result<(), Error>; - /// same as `verify` but instead of providing a Transcript to use, providing the already - /// computed challenge + /// Verifies a proof using a pre-computed challenge + /// + /// Similar to [`verify`](Self::verify) but uses a provided challenge instead of a transcript. + /// + /// # Errors + /// + /// Returns an error if verification fails fn verify_with_challenge( params: &Self::VerifierParams, challenge: Self::Challenge, @@ -102,7 +209,7 @@ mod tests { let (kzg_pk, kzg_vk): (ProverKey, VerifierKey) = KZG::::setup(rng, n)?; // test with Pedersen - let _ = test_homomorphic_property_using_Commitment_trait_opt::>( + test_homomorphic_property_using_Commitment_trait_opt::>( &poseidon_config, &pedersen_params, &pedersen_params, @@ -111,7 +218,7 @@ mod tests { &v_2, )?; // test with IPA - let _ = test_homomorphic_property_using_Commitment_trait_opt::>( + test_homomorphic_property_using_Commitment_trait_opt::>( &poseidon_config, &pedersen_params, &pedersen_params, @@ -120,7 +227,7 @@ mod tests { &v_2, )?; // test with KZG - let _ = test_homomorphic_property_using_Commitment_trait_opt::>( + test_homomorphic_property_using_Commitment_trait_opt::>( &poseidon_config, &kzg_pk, &kzg_vk, diff --git a/folding-schemes/src/commitment/pedersen.rs b/folding-schemes/src/commitment/pedersen.rs index 88a2e4f9..4338803d 100644 --- a/folding-schemes/src/commitment/pedersen.rs +++ b/folding-schemes/src/commitment/pedersen.rs @@ -1,3 +1,12 @@ +//! Pedersen commitment scheme implementation. +//! +//! A Pedersen commitment allows committing to a vector of values with properties like: +//! - **Binding**: The committer cannot change the committed values after committing +//! - **Hiding** (optional): The commitment reveals no information about the committed values +//! +//! This module provides both a basic commitment scheme implementation and circuit-friendly gadgets +//! for verifying commitments inside other proofs. + use ark_ec::CurveGroup; use ark_r1cs_std::{boolean::Boolean, convert::ToBitsGadget, prelude::CurveVar}; use ark_relations::r1cs::SynthesisError; @@ -10,25 +19,38 @@ use crate::transcript::Transcript; use crate::utils::vec::{vec_add, vec_scalar_mul}; use crate::Error; +/// Pedersen proof structure containing commitment opening information #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct Proof { + /// The R commitment value computed during the proof pub R: C, + /// Opening values u = d + v⋅e pub u: Vec, - pub r_u: C::ScalarField, // blind + /// Blinding factor for hiding + pub r_u: C::ScalarField, } +/// Parameters for the Pedersen commitment scheme #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct Params { + /// Generator for blinding factor pub h: C, + /// Generators for committing to values pub generators: Vec, } +/// Pedersen commitment scheme with optional hiding +/// +/// The type parameter H controls whether commitments are hiding: +/// - When H=true, commitments are hiding and use randomness +/// - When H=false, commitments are not hiding and use no randomness #[derive(Debug, Clone, Eq, PartialEq)] pub struct Pedersen { + /// The inner [`CurveGroup`] _c: PhantomData, } -/// Implements the CommitmentScheme trait for Pedersen commitments +/// Implements the [`CommitmentScheme`] trait for Pedersen commitments impl CommitmentScheme for Pedersen { type ProverParams = Params; type VerifierParams = Params; @@ -36,13 +58,6 @@ impl CommitmentScheme for Pedersen { type ProverChallenge = (C::ScalarField, Vec, C, C::ScalarField); type Challenge = C::ScalarField; - fn is_hiding() -> bool { - if H { - return true; - } - false - } - fn setup( mut rng: impl RngCore, len: usize, @@ -77,6 +92,7 @@ impl CommitmentScheme for Pedersen { Ok(params.h.mul(r) + C::msm_unchecked(¶ms.generators[..v.len()], v)) } + // TODO (autoparallel): I'm guessing `_rng` is marked with prefix `_` because it goes unused always and it is planned for the future? Otherwise we should remove that prefix. fn prove( params: &Self::ProverParams, transcript: &mut impl Transcript, @@ -175,13 +191,17 @@ impl CommitmentScheme for Pedersen { } } +/// Gadget for verifying Pedersen commitments in circuits pub struct PedersenGadget where C: CurveGroup, GC: CurveVar>, { + /// The inner constraint field [`CF2`] _cf: PhantomData>, + /// The inner [`CurveGroup`] _c: PhantomData, + /// The inner [`CurveVar`] _gc: PhantomData, } @@ -190,6 +210,20 @@ where C: CurveGroup, GC: CurveVar>, { + /// Creates a Pedersen commitment inside a circuit + /// + /// # Arguments + /// + /// * `h` - Generator for blinding factor + /// * `g` - Generators for values + /// * `v` - Values to commit to, as boolean vectors + /// * `r` - Blinding factor as boolean vector + /// + /// # Errors + /// + /// Returns a `SynthesisError` if: + /// - Converting any vectors to field elements fails + /// - Scalar multiplication fails pub fn commit( h: &GC, g: &[GC], @@ -220,8 +254,8 @@ mod tests { #[test] fn test_pedersen() -> Result<(), Error> { - let _ = test_pedersen_opt::()?; - let _ = test_pedersen_opt::()?; + test_pedersen_opt::()?; + test_pedersen_opt::()?; Ok(()) } fn test_pedersen_opt() -> Result<(), Error> { @@ -255,8 +289,8 @@ mod tests { #[test] fn test_pedersen_circuit() -> Result<(), Error> { - let _ = test_pedersen_circuit_opt::()?; - let _ = test_pedersen_circuit_opt::()?; + test_pedersen_circuit_opt::()?; + test_pedersen_circuit_opt::()?; Ok(()) } fn test_pedersen_circuit_opt() -> Result<(), Error> { @@ -291,7 +325,7 @@ mod tests { let rVar = Vec::>::new_witness(cs.clone(), || Ok(r_bits))?; let gVar = Vec::::new_witness(cs.clone(), || Ok(params.generators))?; let hVar = GVar::new_witness(cs.clone(), || Ok(params.h))?; - let expected_cmVar = GVar::new_witness(cs.clone(), || Ok(cm))?; + let expected_cmVar = GVar::new_witness(cs, || Ok(cm))?; // use the gadget let cmVar = PedersenGadget::::commit(&hVar, &gVar, &vVar, &rVar)?; diff --git a/folding-schemes/src/folding/circuits/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs index 850bfb3a..af6346cc 100644 --- a/folding-schemes/src/folding/circuits/cyclefold.rs +++ b/folding-schemes/src/folding/circuits/cyclefold.rs @@ -52,7 +52,11 @@ impl>> Inputize, CycleFoldCommitted self.u .inputize() .into_iter() - .chain(self.x.iter().flat_map(|x| x.inputize())) + .chain( + self.x + .iter() + .flat_map(super::super::traits::Inputize::inputize), + ) .chain( [ cmE_x, @@ -96,7 +100,7 @@ where let x: Vec>> = Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; - let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?; + let cmW = GC::new_variable(cs, || Ok(val.borrow().cmW), mode)?; Ok(Self { cmE, u, cmW, x }) }) @@ -142,7 +146,7 @@ where self.u.to_native_sponge_field_elements()?, self.x .iter() - .map(|i| i.to_native_sponge_field_elements()) + .map(crate::transcript::AbsorbNonNativeGadget::to_native_sponge_field_elements) .collect::, _>>()? .concat(), cmE_elems, @@ -188,7 +192,7 @@ where pub fn hash, S>>( &self, sponge: &T, - pp_hash: FpVar>, // public params hash + pp_hash: &FpVar>, // public params hash ) -> Result<(FpVar>, Vec>>), SynthesisError> { let mut sponge = sponge.clone(); let U_vec = self.to_native_sponge_field_elements()?; @@ -228,7 +232,7 @@ where let cs = cs.into(); let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; - let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?; + let cmW = GC::new_variable(cs, || Ok(val.borrow().cmW), mode)?; Ok(Self { _c: PhantomData, @@ -267,7 +271,7 @@ where let rE = NonNativeUintVar::new_variable(cs.clone(), || Ok(val.borrow().rE), mode)?; let W = Vec::new_variable(cs.clone(), || Ok(val.borrow().W.clone()), mode)?; - let rW = NonNativeUintVar::new_variable(cs.clone(), || Ok(val.borrow().rW), mode)?; + let rW = NonNativeUintVar::new_variable(cs, || Ok(val.borrow().rW), mode)?; Ok(Self { E, rE, W, rW }) }) @@ -282,22 +286,23 @@ pub struct NIFSFullGadget>> { _gc: PhantomData, } -impl>> NIFSFullGadget +impl NIFSFullGadget where C: CurveGroup, GC: CurveVar>, C::BaseField: PrimeField, { pub fn fold_committed_instance( - r_bits: Vec>>, - cmT: GC, + r_bits: &[Boolean>], + cmT: &GC, ci1: CycleFoldCommittedInstanceVar, // ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method) ci2: CycleFoldCommittedInstanceVar, ) -> Result, SynthesisError> { // r_nonnat is equal to r_bits just that in a different format let r_nonnat = { - let mut bits = r_bits.clone(); + let mut bits = Vec::with_capacity(CF1::::MODULUS_BIT_SIZE as usize); + bits.extend_from_slice(r_bits); bits.resize(CF1::::MODULUS_BIT_SIZE as usize, Boolean::FALSE); NonNativeUintVar::from(&bits) }; @@ -319,12 +324,12 @@ where pub fn verify( // assumes that r_bits is equal to r_nonnat just that in a different format - r_bits: Vec>>, - cmT: GC, + r_bits: &[Boolean>], + cmT: &GC, ci1: CycleFoldCommittedInstanceVar, // ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method) ci2: CycleFoldCommittedInstanceVar, - ci3: CycleFoldCommittedInstanceVar, + ci3: &CycleFoldCommittedInstanceVar, ) -> Result<(), SynthesisError> { let ci = Self::fold_committed_instance(r_bits, cmT, ci1, ci2)?; @@ -376,30 +381,30 @@ where { pub fn get_challenge_native>( transcript: &mut T, - pp_hash: C::BaseField, // public params hash - U_i: CycleFoldCommittedInstance, - u_i: CycleFoldCommittedInstance, - cmT: C, + pp_hash: &C::BaseField, // public params hash + 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_point(&cmT); + transcript.absorb(pp_hash); + transcript.absorb_nonnative(U_i); + transcript.absorb_nonnative(u_i); + transcript.absorb_point(cmT); transcript.squeeze_bits(NOVA_N_BITS_RO) } // 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: GC, + pp_hash: &FpVar, // public params hash + U_i_vec: &[FpVar], + u_i: &CycleFoldCommittedInstanceVar, + cmT: &GC, ) -> Result>, SynthesisError> { - transcript.absorb(&pp_hash)?; + 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) } } @@ -530,7 +535,7 @@ impl>> ConstraintSynthesi // 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)?; + Vec::new_input(cs, || x.value())?.enforce_equal(&x)?; Ok(()) } @@ -606,11 +611,11 @@ where #[allow(clippy::too_many_arguments)] pub fn fold_cyclefold_circuit( transcript: &mut impl Transcript, - cf_r1cs: R1CS, - cf_cs_params: CS2::ProverParams, - pp_hash: C1::ScalarField, // public params hash - cf_W_i: CycleFoldWitness, // witness of the running instance - cf_U_i: CycleFoldCommittedInstance, // running instance + cf_r1cs: &R1CS, + cf_cs_params: &CS2::ProverParams, + pp_hash: &C1::ScalarField, // 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< @@ -626,7 +631,7 @@ pub fn fold_cyclefold_circuit( > where CFG: CycleFoldConfig, - C1: CurveGroup, + C1: CurveGroup, GC1: CurveVar>, C2: CurveGroup, GC2: CurveVar>, @@ -635,7 +640,6 @@ where ::BaseField: PrimeField, C1::ScalarField: Absorb, C2::ScalarField: Absorb, - C1: CurveGroup, { let cs2 = ConstraintSystem::::new_ref(); cf_circuit.generate_constraints(cs2.clone())?; @@ -647,27 +651,22 @@ where 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: CycleFoldCommittedInstance = - cf_w_i.commit::(&cf_cs_params, cf_x_i.clone())?; + let cf_w_i = CycleFoldWitness::::new::(cf_w_i, cf_r1cs.A.n_rows, &mut rng); + let cf_u_i: CycleFoldCommittedInstance = cf_w_i.commit::(cf_cs_params, cf_x_i)?; // compute T* and cmT* for CycleFoldCircuit let (cf_T, cf_cmT) = NIFS::, H>::compute_cyclefold_cmT( - &cf_cs_params, - &cf_r1cs, + cf_cs_params, + cf_r1cs, &cf_w_i, &cf_u_i, - &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, + transcript, &pp_hash, cf_U_i, &cf_u_i, &cf_cmT, ); let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) .expect("cf_r_bits out of bounds"); @@ -748,7 +747,7 @@ pub mod tests { let mut res = Projective::zero(); use ark_std::One; let mut rho_i = Fr::one(); - for point_i in points.iter() { + for point_i in &points { res += point_i.mul(rho_i); rho_i *= rho_Fr; } @@ -785,7 +784,6 @@ pub mod tests { // prepare the committed instances to test in-circuit let ci: Vec> = (0..2) - .into_iter() .map(|_| CommittedInstance:: { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), @@ -823,7 +821,7 @@ pub mod tests { })?; let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT))?; - NIFSFullGadget::::verify(r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var)?; + NIFSFullGadget::::verify(&r_bitsVar, &cmTVar, ci1Var, ci2Var, &ci3Var)?; assert!(cs.is_satisfied()?); Ok(()) } @@ -856,10 +854,10 @@ pub mod tests { 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(), - cmT, + &pp_hash, + &U_i, + &u_i, + &cmT, ); let cs = ConstraintSystem::::new_ref(); @@ -878,10 +876,10 @@ pub mod tests { 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, + &pp_hashVar, + &U_iVar.to_native_sponge_field_elements()?, + &u_iVar, + &cmTVar, )?; assert!(cs.is_satisfied()?); @@ -918,7 +916,7 @@ pub mod tests { let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; let (hVar, _) = U_iVar.hash( &PoseidonSpongeVar::new(cs.clone(), &poseidon_config), - pp_hashVar, + &pp_hashVar, )?; hVar.enforce_equal(&FpVar::new_witness(cs.clone(), || Ok(h))?)?; assert!(cs.is_satisfied()?); diff --git a/folding-schemes/src/folding/circuits/decider/off_chain.rs b/folding-schemes/src/folding/circuits/decider/off_chain.rs index c13aecbd..424a9cff 100644 --- a/folding-schemes/src/folding/circuits/decider/off_chain.rs +++ b/folding-schemes/src/folding/circuits/decider/off_chain.rs @@ -197,7 +197,7 @@ where // 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 (cf_u_i_x, _) = cf_U_i.hash(&sponge, &pp_hash)?; 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}`. diff --git a/folding-schemes/src/folding/circuits/decider/on_chain.rs b/folding-schemes/src/folding/circuits/decider/on_chain.rs index fa116e98..18ee4fca 100644 --- a/folding-schemes/src/folding/circuits/decider/on_chain.rs +++ b/folding-schemes/src/folding/circuits/decider/on_chain.rs @@ -235,7 +235,7 @@ where // 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 (cf_u_i_x, _) = cf_U_i.hash(&sponge, &pp_hash)?; u_i.get_public_inputs().enforce_equal(&[u_i_x, cf_u_i_x])?; #[cfg(feature = "light-test")] diff --git a/folding-schemes/src/folding/circuits/mod.rs b/folding-schemes/src/folding/circuits/mod.rs index 5b6af02b..0736b806 100644 --- a/folding-schemes/src/folding/circuits/mod.rs +++ b/folding-schemes/src/folding/circuits/mod.rs @@ -1,3 +1,36 @@ +//! Circuit implementations and gadgets shared across different folding schemes. +//! +//! # Overview +//! +//! This module provides core circuit components used by various folding schemes including: +//! +//! - [`cyclefold`] - CycleFold circuit implementation that enables cross-curve folding operations +//! - [`decider`] - Decider circuits for verifying folding operations both on-chain and off-chain +//! - [`nonnative`] - Gadgets for working with non-native field arithmetic in circuits +//! - [`sum_check`] - Implementation of sum-check protocol circuits and gadgets +//! - [`utils`] - Common circuit utilities and helper gadgets +//! +//! # Type Aliases +//! +//! The module defines two important type aliases for working with constraint fields: +//! +//! - [`CF1`] - The constraint field used for the main folding circuit, using the scalar field of +//! the main curve where folding occurs +//! - [`CF2`] - The constraint field used for the CycleFold circuit, using the base field which allows +//! native representation of curve points +//! +//! # Architecture +//! +//! The circuits in this module are designed to be: +//! +//! - Reusable across different folding scheme implementations +//! - Composable to build more complex circuits +//! - Efficient by sharing common gadgets and optimizations +//! +//! Most circuits work with two curves in a cycle (C1, C2) where: +//! - C1 is the main curve whose scalar field is used for field operations +//! - C2 is the auxiliary curve used for commitments, with its base field matching C1's scalar field + /// Circuits and gadgets shared across the different folding schemes. use ark_ec::{CurveGroup, PrimeGroup}; use ark_ff::Field; @@ -8,6 +41,9 @@ pub mod nonnative; pub mod sum_check; pub mod utils; +// TODO (autoparallel): nit -- I think the short variable names `CF1` and `CF2` could be made more clear by something +// like `ConstraintFieldPrimary` and `ConstraintFieldSecondary`, but this isn't a hill I'll die on + /// CF1 uses the ScalarField of the given C. CF1 represents the ConstraintField used for the main /// folding circuit which is over E1::Fr, where E1 is the main curve where we do the folding. /// In CF1, the points of C can not be natively represented. diff --git a/folding-schemes/src/folding/hypernova/circuits.rs b/folding-schemes/src/folding/hypernova/circuits.rs index 3db07806..91cc6736 100644 --- a/folding-schemes/src/folding/hypernova/circuits.rs +++ b/folding-schemes/src/folding/hypernova/circuits.rs @@ -754,7 +754,7 @@ where // 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)?; // 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_vec) = cf_U_i.clone().hash(&sponge, &pp_hash)?; // P.2. Construct u_i let u_i = CCCSVar:: { @@ -861,23 +861,24 @@ where // cf_r_bits is denoted by rho* in the paper. let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( &mut transcript, - pp_hash.clone(), - cf_U_i_vec, - cf_u_i.clone(), - cf_cmT.clone(), + &pp_hash, + &cf_U_i_vec, + &cf_u_i, + &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)?; + 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, &pp_hash)?; let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? - .hash(&sponge, pp_hash)?; + .hash(&sponge, &pp_hash)?; 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 @@ -1384,11 +1385,11 @@ mod tests { 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_r1cs, + &cf_pedersen_params, + &pp_hash, + &cf_W_i, // CycleFold running instance witness + &cf_U_i, // CycleFold running instance cf_circuit, &mut rng, )?; diff --git a/folding-schemes/src/folding/hypernova/mod.rs b/folding-schemes/src/folding/hypernova/mod.rs index 09c2790c..4b6fc360 100644 --- a/folding-schemes/src/folding/hypernova/mod.rs +++ b/folding-schemes/src/folding/hypernova/mod.rs @@ -819,11 +819,11 @@ where 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 + &self.cf_r1cs, + &self.cf_cs_pp, + &self.pp_hash, + &self.cf_W_i, // CycleFold running instance witness + &self.cf_U_i, // CycleFold running instance cf_circuit, &mut rng, )?; diff --git a/folding-schemes/src/folding/mod.rs b/folding-schemes/src/folding/mod.rs index 255ca54f..bd8de88b 100644 --- a/folding-schemes/src/folding/mod.rs +++ b/folding-schemes/src/folding/mod.rs @@ -1,3 +1,38 @@ +//! The folding module provides implementations of various folding schemes for recursive SNARKs +//! (Succinct Non-interactive ARguments of Knowledge) and IVC (Incremental Verifiable Computation). +//! +//! # Overview +//! +//! This module contains implementations of different folding schemes including: +//! +//! - [`nova`] - Implementation of the Nova folding scheme +//! - [`hypernova`] - Implementation of the HyperNova folding scheme, which extends Nova to support +//! customizable constraint systems +//! - [`protogalaxy`] - Implementation of the ProtoGalaxy folding scheme +//! +//! The module also provides: +//! +//! - [`circuits`] - Core circuit implementations and gadgets used across different folding schemes +//! - [`traits`] - Common traits and interfaces that folding schemes must implement +//! +//! # Architecture +//! +//! Each folding scheme follows a similar architecture: +//! +//! - Implements the [`FoldingScheme`](crate::FoldingScheme) trait which defines the core +//! interface for initialization, proving steps, and verification +//! - Uses commitment schemes from the [`commitment`](crate::commitment) module +//! - Builds upon the arithmetic backends defined in [`arith`](crate::arith) +//! - Leverages common circuit gadgets from the [`circuits`] module +//! +//! # References +//! +//! The implementations are based on the following academic works: +//! +//! - [Nova: Recursive Zero-Knowledge Arguments from Folding Schemes](https://eprint.iacr.org/2021/370) +//! - [HyperNova: Recursive Arguments for Customizable Constraint Systems](https://eprint.iacr.org/2023/573) +//! - [CycleFold: Folding-scheme-based Recursive Arguments over Different Curves](https://eprint.iacr.org/2023/1192) + pub mod circuits; pub mod hypernova; pub mod nova; @@ -29,15 +64,9 @@ pub mod tests { /// ProtoGalaxy. #[test] fn test_serialize_ivc_nova_hypernova_protogalaxy() -> Result<(), Error> { - let poseidon_config = poseidon_canonical_config::(); type FC = CubicFCircuit; - let f_circuit = FC::new(())?; - // test Nova type N = Nova, Pedersen, false>; - let prep_param = NovaPreprocessorParam::new(poseidon_config.clone(), f_circuit); - test_serialize_ivc_opt::("nova".to_string(), prep_param.clone())?; - // test HyperNova type HN = HyperNova< G1, @@ -51,36 +80,41 @@ pub mod tests { 1, // nu false, >; - test_serialize_ivc_opt::("hypernova".to_string(), prep_param)?; - // test ProtoGalaxy type P = ProtoGalaxy, Pedersen>; + + let poseidon_config = poseidon_canonical_config::(); + + let f_circuit = FC::new(())?; + + let prep_param = NovaPreprocessorParam::new(poseidon_config.clone(), f_circuit); + test_serialize_ivc_opt::("nova", &prep_param)?; + test_serialize_ivc_opt::("hypernova", &prep_param)?; + let prep_param = (poseidon_config, f_circuit); - test_serialize_ivc_opt::("protogalaxy".to_string(), prep_param)?; + test_serialize_ivc_opt::("protogalaxy", &prep_param)?; Ok(()) } fn test_serialize_ivc_opt< - C1: CurveGroup, + C1: CurveGroup, C2: CurveGroup, FC: FCircuit, FS: FoldingScheme, >( - name: String, - prep_param: FS::PreprocessorParam, + name: &str, + prep_param: &FS::PreprocessorParam, ) -> Result<(), Error> where - C1: CurveGroup, C2::BaseField: PrimeField, - FC: FCircuit, { let mut rng = ark_std::test_rng(); let F_circuit = FC::new(())?; - let fs_params = FS::preprocess(&mut rng, &prep_param)?; + let fs_params = FS::preprocess(&mut rng, prep_param)?; let z_0 = vec![C1::ScalarField::from(3_u32)]; - let mut fs = FS::init(&fs_params, F_circuit, z_0.clone())?; + let mut fs = FS::init(&fs_params, F_circuit, z_0)?; // perform multiple IVC steps (internally folding) let num_steps: usize = 3; @@ -99,11 +133,11 @@ pub mod tests { let mut file = std::fs::OpenOptions::new() .create(true) .write(true) - .open(format!("./ivc_proof-{}.serialized", name))?; + .open(format!("./ivc_proof-{name}.serialized"))?; file.write_all(&writer)?; // read the IVCProof from the file deserializing it - let bytes = std::fs::read(format!("./ivc_proof-{}.serialized", name))?; + let bytes = std::fs::read(format!("./ivc_proof-{name}.serialized"))?; let deserialized_ivc_proof = FS::IVCProof::deserialize_compressed(bytes.as_slice())?; // verify deserialized IVCProof FS::verify(fs_params.1.clone(), deserialized_ivc_proof.clone())?; @@ -156,7 +190,7 @@ pub mod tests { FS::IVCProof::deserialize_compressed(ivc_proof_serialized.as_slice())?; // verify the last IVCProof from the recovered from serialization FS - FS::verify(fs_vp_deserialized.clone(), ivc_proof_deserialized)?; + FS::verify(fs_vp_deserialized, ivc_proof_deserialized)?; Ok(()) } diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index 38fb0333..5ca9868e 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -177,7 +177,7 @@ where // 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)?; // 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_vec) = cf_U_i.clone().hash(&sponge, &pp_hash)?; // P.2. Construct u_i let u_i = CommittedInstanceVar { @@ -302,26 +302,31 @@ where // 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(), + &pp_hash, + &cf_U_i_vec, + &cf1_u_i, + &cf1_cmT, )?; // 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, + &cf1_r_bits, + &cf1_cmT, + cf_U_i, + cf1_u_i, )?; // same for cf2_r: let cf2_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( &mut transcript, - pp_hash.clone(), - cf1_U_i1.to_native_sponge_field_elements()?, - cf2_u_i.clone(), - cf2_cmT.clone(), + &pp_hash, + &cf1_U_i1.to_native_sponge_field_elements()?, + &cf2_u_i, + &cf2_cmT, )?; 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_r_bits, + &cf2_cmT, + cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u) cf2_u_i, )?; @@ -329,10 +334,10 @@ where // 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, &pp_hash)?; let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? - .hash(&sponge, pp_hash)?; + .hash(&sponge, &pp_hash)?; 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 diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index 6389bcf2..db74e990 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -523,7 +523,7 @@ where let f_circuit = FC::new(fc_params)?; let cs = ConstraintSystem::::new_ref(); let augmented_F_circuit = - AugmentedFCircuit::::empty(&poseidon_config, f_circuit.clone()); + AugmentedFCircuit::::empty(&poseidon_config, f_circuit); augmented_F_circuit.generate_constraints(cs.clone())?; cs.finalize(); let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; @@ -568,8 +568,8 @@ where let prover_params = ProverParams:: { poseidon_config: prep_param.poseidon_config.clone(), - cs_pp: cs_pp.clone(), - cf_cs_pp: cf_cs_pp.clone(), + cs_pp, + cf_cs_pp, }; let verifier_params = VerifierParams:: { poseidon_config: prep_param.poseidon_config.clone(), @@ -737,7 +737,7 @@ where 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()), + external_inputs: Some(external_inputs), u_i_cmW: Some(self.u_i.cmW), // = dummy U_i: Some(self.U_i.clone()), // = dummy U_i1_cmE: Some(U_i1.cmE), @@ -770,23 +770,23 @@ where }; let cfE_circuit = NovaCycleFoldCircuit:: { _gc: PhantomData, - r_bits: Some(r_bits.clone()), + r_bits: Some(r_bits), points: Some(vec![self.U_i.clone().cmE, cmT]), }; // fold self.cf_U_i + cfW_U -> folded running with cfW let (_cfW_w_i, cfW_u_i, cfW_W_i1, cfW_U_i1, cfW_cmT, _) = self.fold_cyclefold_circuit( &mut transcript, - self.cf_W_i.clone(), // CycleFold running instance witness - self.cf_U_i.clone(), // CycleFold running instance + &self.cf_W_i, // CycleFold running instance witness + &self.cf_U_i, // 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_w_i, cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = self.fold_cyclefold_circuit( &mut transcript, - cfW_W_i1, - cfW_U_i1.clone(), + &cfW_W_i1, + &cfW_U_i1, cfE_circuit, &mut rng, )?; @@ -996,7 +996,7 @@ where impl Nova where - C1: CurveGroup, + C1: CurveGroup, GC1: CurveVar>, C2: CurveGroup, GC2: CurveVar>, @@ -1007,15 +1007,14 @@ where ::BaseField: PrimeField, C1::ScalarField: Absorb, C2::ScalarField: Absorb, - C1: CurveGroup, { // 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_W_i: &CycleFoldWitness, // witness of the running instance + cf_U_i: &CycleFoldCommittedInstance, // running instance cf_circuit: NovaCycleFoldCircuit, rng: &mut impl RngCore, ) -> Result< @@ -1031,9 +1030,9 @@ where > { fold_cyclefold_circuit::, C1, GC1, C2, GC2, CS2, H>( transcript, - self.cf_r1cs.clone(), - self.cf_cs_pp.clone(), - self.pp_hash, + &self.cf_r1cs, + &self.cf_cs_pp, + &self.pp_hash, cf_W_i, cf_U_i, cf_circuit, diff --git a/folding-schemes/src/folding/protogalaxy/circuits.rs b/folding-schemes/src/folding/protogalaxy/circuits.rs index 09f1a2fe..1e98bac1 100644 --- a/folding-schemes/src/folding/protogalaxy/circuits.rs +++ b/folding-schemes/src/folding/protogalaxy/circuits.rs @@ -177,7 +177,7 @@ impl AugmentationGadget { S: CryptographicSponge, >( transcript: &mut PoseidonSpongeVar>, - pp_hash: FpVar>, + pp_hash: &FpVar>, mut cf_U: CycleFoldCommittedInstanceVar, cf_u_cmWs: Vec, cf_u_xs: Vec>>>, @@ -206,14 +206,14 @@ impl AugmentationGadget { let cf_r_bits = CycleFoldChallengeGadget::get_challenge_gadget( transcript, - pp_hash.clone(), - cf_U.to_native_sponge_field_elements()?, - cf_u.clone(), - cmT.clone(), + &pp_hash, + &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)?; + cf_U = NIFSFullGadget::fold_committed_instance(&cf_r_bits, &cmT, cf_U, cf_u)?; } Ok(cf_U) @@ -351,9 +351,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.hash(&sponge, &pp_hash, &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.hash(&sponge, &pp_hash)?; // P.2. Prepare incoming primary instances // P.3. Fold incoming primary instances into the running instance @@ -421,7 +421,7 @@ where 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(), + U_i.phi.y, phi_stars[0].x.clone(), phi_stars[0].y.clone(), ], @@ -440,9 +440,9 @@ where phi_stars[0].x.clone(), phi_stars[0].y.clone(), u_i_phi.x.clone(), - u_i_phi.y.clone(), + u_i_phi.y, U_i1.phi.x.clone(), - U_i1.phi.y.clone(), + U_i1.phi.y, ], ] .concat(); @@ -452,7 +452,7 @@ where let cf_U_i1 = AugmentationGadget::prepare_and_fold_cyclefold::>>( &mut transcript, - pp_hash.clone(), + &pp_hash, cf_U_i, vec![ GC2::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW))?, @@ -466,10 +466,10 @@ where // 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, &pp_hash)?; let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? - .hash(&sponge, pp_hash.clone())?; + .hash(&sponge, &pp_hash)?; 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 @@ -480,7 +480,7 @@ where // 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)?; + FpVar::new_input(cs, || cf_x.value())?.enforce_equal(&cf_x)?; Ok(z_i1) } diff --git a/folding-schemes/src/folding/protogalaxy/mod.rs b/folding-schemes/src/folding/protogalaxy/mod.rs index 8009bc0c..07078a25 100644 --- a/folding-schemes/src/folding/protogalaxy/mod.rs +++ b/folding-schemes/src/folding/protogalaxy/mod.rs @@ -914,16 +914,16 @@ where // fold self.cf_U_i + cf1_U -> folded running with cf1 let (_cf1_w_i, 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 + &self.cf_W_i, // CycleFold running instance witness + &self.cf_U_i, // 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_w_i, 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(), + &cf1_W_i1, + &cf1_U_i1, cf2_circuit, &mut rng, )?; @@ -1121,7 +1121,7 @@ where impl ProtoGalaxy where - C1: CurveGroup, + C1: CurveGroup, GC1: CurveVar>, C2: CurveGroup, GC2: CurveVar>, @@ -1132,15 +1132,14 @@ where ::BaseField: PrimeField, C1::ScalarField: Absorb, C2::ScalarField: Absorb, - C1: CurveGroup, { // 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_W_i: &CycleFoldWitness, // witness of the running instance + cf_U_i: &CycleFoldCommittedInstance, // running instance cf_circuit: ProtoGalaxyCycleFoldCircuit, rng: &mut impl RngCore, ) -> Result< @@ -1156,9 +1155,9 @@ where > { fold_cyclefold_circuit::, C1, GC1, C2, GC2, CS2, false>( transcript, - self.cf_r1cs.clone(), - self.cf_cs_params.clone(), - self.pp_hash, + &self.cf_r1cs, + &self.cf_cs_params, + &self.pp_hash, cf_W_i, cf_U_i, cf_circuit, diff --git a/folding-schemes/src/lib.rs b/folding-schemes/src/lib.rs index 2db8f711..d0bb6450 100644 --- a/folding-schemes/src/lib.rs +++ b/folding-schemes/src/lib.rs @@ -1,6 +1,51 @@ +//! Sonobe is a library implementing various folding schemes for recursive SNARKs (Succinct Non-interactive ARguments of Knowledge) +//! and IVC (Incremental Verifiable Computation). It provides a modular, extensible framework for working with different folding +//! schemes including Nova, HyperNova, and other variants. +//! +//! # Overview +//! +//! The library is built around a core [`FoldingScheme`] trait that defines the interface for implementing different folding +//! schemes. Key features include: +//! +//! * Multiple folding scheme implementations including HyperNova and variants +//! * Support for both single-fold and multi-fold operations +//! * Flexible commitment schemes including Pedersen and KZG commitments +//! * CycleFold support for cross-curve folding operations +//! * Customizable arithmetic backends via the [`Arith`] trait +//! * Support for both R1CS and CCS (Customizable Constraint Systems) +//! +//! # Architecture +//! +//! The library is organized into several key modules: +//! +//! * `arith` - Core arithmetic abstractions including R1CS and CCS implementations +//! * `commitment` - Commitment scheme implementations like Pedersen and KZG +//! * `folding` - Main folding scheme implementations and associated traits +//! * `frontend` - User-facing circuit building interfaces +//! * `transcript` - Transcript handling for Fiat-Shamir transformations +//! +//! # Usage +//! +//! To use a folding scheme, you typically: +//! +//! 1. Define your computation circuit implementing [`FCircuit`] +//! 2. Choose and configure a folding scheme implementation +//! 3. Set up the necessary commitment scheme parameters +//! 4. Initialize the folding scheme with initial state +//! 5. Perform folding operations via `prove_step` +//! +//! # References +//! +//! The library implements protocols from several academic works: +//! +//! * [Nova: Recursive Zero-Knowledge Arguments from Folding Schemes](https://eprint.iacr.org/2021/370) +//! * [HyperNova: Recursive Arguments for Customizable Constraint Systems](https://eprint.iacr.org/2023/573) +//! * [CycleFold: Folding-scheme-based Recursive Arguments over Different Curves](https://eprint.iacr.org/2023/1192) + #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] +#![warn(missing_docs, clippy::missing_docs_in_private_items)] use ark_ec::{pairing::Pairing, CurveGroup}; use ark_ff::PrimeField;