From f57936307bea3c8815b707be55f010141ec60b92 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Fri, 20 Dec 2024 07:39:49 -0700 Subject: [PATCH] WIP: `commitment` docs --- folding-schemes/src/commitment/ipa.rs | 129 +++++++++++++-------- folding-schemes/src/commitment/kzg.rs | 7 -- folding-schemes/src/commitment/mod.rs | 127 ++++++++++++++++++-- folding-schemes/src/commitment/pedersen.rs | 7 -- 4 files changed, 198 insertions(+), 72 deletions(-) diff --git a/folding-schemes/src/commitment/ipa.rs b/folding-schemes/src/commitment/ipa.rs index a06949eb..0833514c 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,19 +31,32 @@ 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, } @@ -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,6 +117,7 @@ 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() { @@ -202,8 +210,8 @@ impl CommitmentScheme for IPA { Ok(( Proof { a: a[0], - l: l.clone(), - r: r.clone(), + l, + r, L, R, }, @@ -284,9 +292,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 +325,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 +337,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 +364,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 @@ -388,6 +405,12 @@ fn build_s_gadget( Ok(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 +427,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() @@ -427,7 +457,7 @@ fn s_b_inner_gadget( ) -> 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(); } @@ -467,16 +497,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, @@ -574,16 +605,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 +651,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..d9a6d72d 100644 --- a/folding-schemes/src/commitment/kzg.rs +++ b/folding-schemes/src/commitment/kzg.rs @@ -93,13 +93,6 @@ 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( 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..dbb8d019 100644 --- a/folding-schemes/src/commitment/pedersen.rs +++ b/folding-schemes/src/commitment/pedersen.rs @@ -36,13 +36,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,