1- /// IPA implements the modified Inner Product Argument described in
2- /// [Halo](https://eprint.iacr.org/2019/1021.pdf). The variable names used follow the paper
3- /// notation in order to make it more readable.
4- ///
5- /// The implementation does the following optimizations in order to reduce the amount of
6- /// constraints in the circuit:
7- /// i. <s, b> computation is done in log time following a modification of the equation 3 in section
8- /// 3.2 from the paper.
9- /// ii. s computation is done in 2^{k+1}-2 instead of k*2^k.
1+ //! IPA implements the modified Inner Product Argument described in
2+ //! [Halo](https://eprint.iacr.org/2019/1021.pdf). The variable names used follow the paper
3+ //! notation in order to make it more readable.
4+ //!
5+ //! The implementation does the following optimizations in order to reduce the amount of
6+ //! constraints in the circuit:
7+ //! i. <s, b> computation is done in log time following a modification of the equation 3 in section
8+ //! 3.2 from the paper.
9+ //! ii. s computation is done in 2^{k+1}-2 instead of k*2^k.
10+
1011use ark_ec:: { AffineRepr , CurveGroup } ;
1112use ark_ff:: { Field , PrimeField } ;
1213use ark_r1cs_std:: {
@@ -30,19 +31,32 @@ use crate::utils::{
3031} ;
3132use crate :: Error ;
3233
34+ /// IPA proof structure containing all components needed for verification
3335#[ derive( Debug , Clone , Eq , PartialEq , CanonicalSerialize , CanonicalDeserialize ) ]
3436pub struct Proof < C : CurveGroup > {
35- a : C :: ScalarField ,
36- l : Vec < C :: ScalarField > ,
37- r : Vec < C :: ScalarField > ,
38- L : Vec < C > ,
39- R : Vec < C > ,
37+ /// Final value of vector a after the folding process
38+ pub a : C :: ScalarField ,
39+ /// Left blinding factors used in each round
40+ pub l : Vec < C :: ScalarField > ,
41+ /// Right blinding factors used in each round
42+ pub r : Vec < C :: ScalarField > ,
43+ /// Left commitments for each round
44+ pub L : Vec < C > ,
45+ /// Right commitments for each round
46+ pub R : Vec < C > ,
4047}
4148
42- /// IPA implements the Inner Product Argument protocol following the CommitmentScheme trait. The
43- /// `H` parameter indicates if to use the commitment in hiding mode or not.
49+ /// Implementation of the Inner Product Argument (IPA) as described in [Halo](https://eprint.iacr.org/2019/1021.pdf).
50+ /// The variable names follow the paper notation to maintain clarity and readability.
51+ ///
52+ /// This implementation includes optimizations to reduce circuit constraints:
53+ /// 1. The `<s, b>` computation is done in log time using a modified version of equation 3 in section 3.2
54+ /// 2. The `s` computation is optimized to take 2^{k+1}-2 steps instead of k*2^k steps
55+ ///
56+ /// The `H` parameter indicates if to use the commitment in hiding mode or not.
4457#[ derive( Debug , Clone , Eq , PartialEq ) ]
4558pub struct IPA < C : CurveGroup , const H : bool = false> {
59+ /// The inner [`CurveGroup`] type
4660 _c : PhantomData < C > ,
4761}
4862
@@ -54,13 +68,6 @@ impl<C: CurveGroup, const H: bool> CommitmentScheme<C, H> for IPA<C, H> {
5468 type ProverChallenge = ( C :: ScalarField , C , Vec < C :: ScalarField > ) ;
5569 type Challenge = ( C :: ScalarField , C , Vec < C :: ScalarField > ) ;
5670
57- fn is_hiding ( ) -> bool {
58- if H {
59- return true ;
60- }
61- false
62- }
63-
6471 fn setup (
6572 mut rng : impl RngCore ,
6673 len : usize ,
@@ -110,6 +117,7 @@ impl<C: CurveGroup, const H: bool> CommitmentScheme<C, H> for IPA<C, H> {
110117 return Err ( Error :: BlindingNotZero ) ;
111118 }
112119 let d = a. len ( ) ;
120+ // TODO (autoparallel): Casting this back into `usize` could be dangerous in 32bit systems (e.g., wasm32)
113121 let k = ( f64:: from ( d as u32 ) . log2 ( ) ) as usize ;
114122
115123 if params. generators . len ( ) < a. len ( ) {
@@ -202,8 +210,8 @@ impl<C: CurveGroup, const H: bool> CommitmentScheme<C, H> for IPA<C, H> {
202210 Ok ( (
203211 Proof {
204212 a : a[ 0 ] ,
205- l : l . clone ( ) ,
206- r : r . clone ( ) ,
213+ l,
214+ r,
207215 L ,
208216 R ,
209217 } ,
@@ -284,9 +292,10 @@ impl<C: CurveGroup, const H: bool> CommitmentScheme<C, H> for IPA<C, H> {
284292 }
285293
286294 // compute b & G from s
287- let s = build_s ( & u, & u_invs, k) ? ;
295+ let s = build_s ( & u, & u_invs, k) ;
288296 // b = <s, b_vec> = <s, [1, x, x^2, ..., x^d-1]>
289297 let b = s_b_inner ( & u, & x) ?;
298+ // TODO (autoparallel): Casting this back into `usize` could be dangerous in 32bit systems (e.g., wasm32)
290299 let d: usize = 2_u64 . pow ( k as u32 ) as usize ;
291300 if params. generators . len ( ) < d {
292301 return Err ( Error :: PedersenParamsLen ( params. generators . len ( ) , d) ) ;
@@ -316,7 +325,10 @@ impl<C: CurveGroup, const H: bool> CommitmentScheme<C, H> for IPA<C, H> {
316325 }
317326}
318327
319- /// Computes s such that
328+ /// Computes the s vector used in IPA verification
329+ ///
330+ /// The resulting s vector has the form:
331+ /// ```text
320332/// s = (
321333/// u₁⁻¹ u₂⁻¹ … uₖ⁻¹,
322334/// u₁ u₂⁻¹ … uₖ⁻¹,
@@ -325,10 +337,15 @@ impl<C: CurveGroup, const H: bool> CommitmentScheme<C, H> for IPA<C, H> {
325337/// ⋮ ⋮ ⋮
326338/// u₁ u₂ … uₖ
327339/// )
328- /// Uses Halo2 approach computing $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} X^{2^i})$,
329- /// taking 2^{k+1}-2.
330- /// src: https://github.com/zcash/halo2/blob/81729eca91ba4755e247f49c3a72a4232864ec9e/halo2_proofs/src/poly/commitment/verifier.rs#L156
331- fn build_s < F : PrimeField > ( u : & [ F ] , u_invs : & [ F ] , k : usize ) -> Result < Vec < F > , Error > {
340+ /// ```
341+ /// Uses the Halo2 approach computing $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} X^{2^i})$,
342+ /// which takes 2^{k+1}-2 steps.
343+ /// src: <https://github.com/zcash/halo2/blob/81729eca91ba4755e247f49c3a72a4232864ec9e/halo2_proofs/src/poly/commitment/verifier.rs#L156>
344+ ///
345+ /// # Errors
346+ ///
347+ /// Returns an error if the vector construction fails.
348+ fn build_s < F : PrimeField > ( u : & [ F ] , u_invs : & [ F ] , k : usize ) -> Vec < F > {
332349 let d: usize = 2_u64 . pow ( k as u32 ) as usize ;
333350 let mut s: Vec < F > = vec ! [ F :: one( ) ; d] ;
334351 for ( len, ( u_j, u_j_inv) ) in u
@@ -347,7 +364,7 @@ fn build_s<F: PrimeField>(u: &[F], u_invs: &[F], k: usize) -> Result<Vec<F>, Err
347364 * s *= u_j;
348365 }
349366 }
350- Ok ( s )
367+ s
351368}
352369
353370/// Computes (in-circuit) s such that
@@ -388,6 +405,12 @@ fn build_s_gadget<F: PrimeField, CF: PrimeField>(
388405 Ok ( s)
389406}
390407
408+ /// Computes the inner product of two vectors
409+ ///
410+ /// # Errors
411+ ///
412+ /// Returns an error if:
413+ /// - The vectors have different lengths
391414fn inner_prod < F : PrimeField > ( a : & [ F ] , b : & [ F ] ) -> Result < F , Error > {
392415 if a. len ( ) != b. len ( ) {
393416 return Err ( Error :: NotSameLength (
@@ -404,12 +427,19 @@ fn inner_prod<F: PrimeField>(a: &[F], b: &[F]) -> Result<F, Error> {
404427 Ok ( c)
405428}
406429
407- // g(x, u_1, u_2, ..., u_k) = <s, b>, naively takes linear, but can compute in log time through
408- // g(x, u_1, u_2, ..., u_k) = \Prod u_i x^{2^i} + u_i^-1
430+ /// Computes g(x, u_1, u_2, ..., u_k) = <s, b> efficiently in log time
431+ ///
432+ /// Rather than computing naively, this uses the formula:
433+ /// g(x, u_1, u_2, ..., u_k) = \Prod u_i x^{2^i} + u_i^-1
434+ ///
435+ /// # Errors
436+ ///
437+ /// Returns an error if:
438+ /// - Computing any inverse fails
409439fn s_b_inner < F : PrimeField > ( u : & [ F ] , x : & F ) -> Result < F , Error > {
410440 let mut c: F = F :: one ( ) ;
411441 let mut x_2_i = * x; // x_2_i is x^{2^i}, starting from x^{2^0}=x
412- for u_i in u. iter ( ) {
442+ for u_i in u {
413443 c *= ( * u_i * x_2_i)
414444 + u_i
415445 . inverse ( )
@@ -427,7 +457,7 @@ fn s_b_inner_gadget<F: PrimeField, CF: PrimeField>(
427457) -> Result < EmulatedFpVar < F , CF > , SynthesisError > {
428458 let mut c: EmulatedFpVar < F , CF > = EmulatedFpVar :: < F , CF > :: one ( ) ;
429459 let mut x_2_i = x. clone ( ) ; // x_2_i is x^{2^i}, starting from x^{2^0}=x
430- for u_i in u. iter ( ) {
460+ for u_i in u {
431461 c *= u_i. clone ( ) * x_2_i. clone ( ) + u_i. inverse ( ) ?;
432462 x_2_i *= x_2_i. clone ( ) ;
433463 }
@@ -467,16 +497,17 @@ where
467497 let r: Vec < EmulatedFpVar < C :: ScalarField , CF < C > > > =
468498 Vec :: new_variable ( cs. clone ( ) , || Ok ( val. borrow ( ) . r . clone ( ) ) , mode) ?;
469499 let L : Vec < GC > = Vec :: new_variable ( cs. clone ( ) , || Ok ( val. borrow ( ) . L . clone ( ) ) , mode) ?;
470- let R : Vec < GC > = Vec :: new_variable ( cs. clone ( ) , || Ok ( val. borrow ( ) . R . clone ( ) ) , mode) ?;
500+ let R : Vec < GC > = Vec :: new_variable ( cs, || Ok ( val. borrow ( ) . R . clone ( ) ) , mode) ?;
471501
472502 Ok ( Self { a, l, r, L , R } )
473503 } )
474504 }
475505}
476506
477- /// IPAGadget implements the circuit that verifies an IPA Proof. The `H` parameter indicates if to
478- /// use the commitment in hiding mode or not, reducing a bit the number of constraints needed in
479- /// the later case.
507+ /// In-circuit IPA verification gadget implementation
508+ ///
509+ /// Provides constraint generation for verifying IPA proofs. The `H` parameter indicates if the
510+ /// commitment is in hiding mode, which affects the number of constraints needed.
480511pub struct IPAGadget < C , GC , const H : bool = false>
481512where
482513 C : CurveGroup ,
@@ -574,16 +605,17 @@ mod tests {
574605
575606 #[ test]
576607 fn test_ipa ( ) -> Result < ( ) , Error > {
577- let _ = test_ipa_opt :: < false > ( ) ?;
578- let _ = test_ipa_opt :: < true > ( ) ?;
608+ test_ipa_opt :: < false > ( ) ?;
609+ test_ipa_opt :: < true > ( ) ?;
579610 Ok ( ( ) )
580611 }
581612 fn test_ipa_opt < const hiding: bool > ( ) -> Result < ( ) , Error > {
582- let mut rng = ark_std:: test_rng ( ) ;
583-
584613 const k: usize = 4 ;
614+ // TODO (autoparallel): Casting into `usize` may be dangerous on 32bit systems (e.g., wasm32)
585615 const d: usize = 2_u64 . pow ( k as u32 ) as usize ;
586616
617+ let mut rng = ark_std:: test_rng ( ) ;
618+
587619 // setup params
588620 let ( params, _) = IPA :: < Projective , hiding > :: setup ( & mut rng, d) ?;
589621
@@ -619,16 +651,17 @@ mod tests {
619651
620652 #[ test]
621653 fn test_ipa_gadget ( ) -> Result < ( ) , Error > {
622- let _ = test_ipa_gadget_opt :: < false > ( ) ?;
623- let _ = test_ipa_gadget_opt :: < true > ( ) ?;
654+ test_ipa_gadget_opt :: < false > ( ) ?;
655+ test_ipa_gadget_opt :: < true > ( ) ?;
624656 Ok ( ( ) )
625657 }
626658 fn test_ipa_gadget_opt < const hiding: bool > ( ) -> Result < ( ) , Error > {
627- let mut rng = ark_std:: test_rng ( ) ;
628-
629659 const k: usize = 3 ;
660+ // TODO (autoparallel): Casting into `usize` may be dangerous on 32bit systems (e.g., wasm32)
630661 const d: usize = 2_u64 . pow ( k as u32 ) as usize ;
631662
663+ let mut rng = ark_std:: test_rng ( ) ;
664+
632665 // setup params
633666 let ( params, _) = IPA :: < Projective , hiding > :: setup ( & mut rng, d) ?;
634667
0 commit comments