1- use ark_circom:: circom:: { CircomCircuit , R1CS as CircomR1CS } ;
1+ use ark_circom:: circom:: R1CS as CircomR1CS ;
22use ark_ff:: PrimeField ;
3- use ark_r1cs_std:: alloc:: AllocVar ;
4- use ark_r1cs_std:: fields:: fp:: FpVar ;
5- use ark_r1cs_std:: fields:: fp:: FpVar :: Var ;
6- use ark_r1cs_std:: R1CSVar ;
7- use ark_relations:: r1cs:: { ConstraintSynthesizer , ConstraintSystemRef , SynthesisError } ;
3+ use ark_r1cs_std:: {
4+ fields:: fp:: { AllocatedFp , FpVar } ,
5+ R1CSVar ,
6+ } ;
7+ use ark_relations:: {
8+ lc,
9+ r1cs:: { ConstraintSystemRef , SynthesisError , Variable } ,
10+ } ;
811use ark_std:: fmt:: Debug ;
912use folding_schemes:: { frontend:: FCircuit , utils:: PathOrBin , Error } ;
10- use num_bigint:: BigInt ;
13+ use num_bigint:: { BigInt , BigUint } ;
1114
1215pub mod utils;
1316use crate :: utils:: { VecF , VecFpVar } ;
@@ -17,7 +20,7 @@ use utils::CircomWrapper;
1720/// The parameter `EIL` indicates the length of the ExternalInputs vector of field elements.
1821#[ derive( Clone , Debug ) ]
1922pub struct CircomFCircuit < F : PrimeField , const SL : usize , const EIL : usize > {
20- circom_wrapper : CircomWrapper < F > ,
23+ circom_wrapper : CircomWrapper ,
2124 r1cs : CircomR1CS < F > ,
2225}
2326
@@ -54,76 +57,107 @@ impl<F: PrimeField, const SL: usize, const EIL: usize> FCircuit<F> for CircomFCi
5457 #[ cfg( test) ]
5558 assert_eq ! ( external_inputs. 0 . len( ) , EIL ) ;
5659
57- let input_values = self . fpvars_to_bigints ( & z_i) ? ;
60+ let input_values = Self :: fpvars_to_bigints ( & z_i) ;
5861 let mut inputs_map = vec ! [ ( "ivc_input" . to_string( ) , input_values) ] ;
5962
6063 if EIL > 0 {
61- let external_inputs_bi = self . fpvars_to_bigints ( & external_inputs. 0 ) ? ;
64+ let external_inputs_bi = Self :: fpvars_to_bigints ( & external_inputs. 0 ) ;
6265 inputs_map. push ( ( "external_inputs" . to_string ( ) , external_inputs_bi) ) ;
6366 }
6467
68+ // The layout of `witness` is as follows:
69+ // [
70+ // 1, // The constant 1 is implicitly allocated by Arkworks
71+ // ...z_{i + 1}, // The next state marked as `signal output` in the circom circuit
72+ // ...z_i, // The current state marked as `signal input` in the circom circuit
73+ // ...external_inputs, // The optional external inputs marked as `external input` in the circom circuit
74+ // ...aux, // The intermediate witnesses
75+ // ]
76+ // Here, 1, z_i, and external_inputs have already been allocated in the
77+ // constraint system, while z_{i + 1} and aux are yet to be allocated.
6578 let witness = self
6679 . circom_wrapper
67- . extract_witness ( & inputs_map)
80+ . extract_witness ( inputs_map)
6881 . map_err ( |_| SynthesisError :: AssignmentMissing ) ?;
6982
70- // Since public inputs are already allocated variables, we will tell `circom-compat` to not re-allocate those
71- let mut already_allocated_public_inputs = vec ! [ ] ;
72- for var in z_i. iter ( ) {
73- match var {
74- Var ( var) => already_allocated_public_inputs. push ( var. variable ) ,
75- _ => return Err ( SynthesisError :: Unsatisfiable ) , // allocated z_i should be Var
76- }
83+ // In order to convert the indexes of variables in the circom circuit to
84+ // those in the arkworks circuit, we adopt the tricks from
85+ // https://github.com/arnaucube/circom-compat/pull/1
86+
87+ // Since our cs might already have allocated constraints,
88+ // We store a mapping between circom's defined indexes and the newly obtained cs indexes
89+ let mut circom_index_to_cs_index = vec ! [ ] ;
90+
91+ // Constant 1 at idx 0 is already allocated by arkworks
92+ circom_index_to_cs_index. push ( Variable :: One ) ;
93+
94+ // Allocate the next state (1..1 + SL) as witness, and at the same time,
95+ // record the allocated variable's index in `circom_index_to_cs_index`.
96+ // Cf. https://github.com/arnaucube/circom-compat/blob/22c8f5/src/circom/circuit.rs#L56-L86
97+ let mut z_i1 = vec ! [ ] ;
98+ for & w in witness. iter ( ) . skip ( 1 ) . take ( SL ) {
99+ let v = cs. new_witness_variable ( || Ok ( w) ) ?;
100+ circom_index_to_cs_index. push ( v) ;
101+ z_i1. push ( FpVar :: Var ( AllocatedFp :: new ( Some ( w) , v, cs. clone ( ) ) ) ) ;
77102 }
78103
79- // Initializes the CircomCircuit.
80- let circom_circuit = CircomCircuit {
81- r1cs : self . r1cs . clone ( ) ,
82- witness : Some ( witness. clone ( ) ) ,
83- public_inputs_indexes : already_allocated_public_inputs,
84- allocate_inputs_as_witnesses : true ,
85- } ;
104+ // `z_i` and `external_inputs` have already been allocated as witness,
105+ // so we just record their indexes in `circom_index_to_cs_index`.
106+ // Cf. https://github.com/arnaucube/circom-compat/blob/22c8f5/src/circom/circuit.rs#L89-L95
107+ for v in z_i. iter ( ) . chain ( & external_inputs. 0 ) {
108+ match v {
109+ FpVar :: Var ( v) => circom_index_to_cs_index. push ( v. variable ) ,
110+ // safe because `z_i` and `external_inputs` are allocated as
111+ // witness (not constant)
112+ _ => unreachable ! ( ) ,
113+ } ;
114+ }
86115
87- // Generates the constraints for the circom_circuit.
88- circom_circuit. generate_constraints ( cs. clone ( ) ) ?;
116+ // Allocate the remaining aux variables as witness.
117+ // Also, record their indexes in `circom_index_to_cs_index`.
118+ // Cf. https://github.com/arnaucube/circom-compat/blob/22c8f5/src/circom/circuit.rs#L106-L121
119+ for w in witness. into_iter ( ) . skip ( circom_index_to_cs_index. len ( ) ) {
120+ circom_index_to_cs_index. push ( cs. new_witness_variable ( || Ok ( w) ) ?) ;
121+ }
122+
123+ let fold_lc = |lc, & ( i, coeff) | lc + ( coeff, circom_index_to_cs_index[ i] ) ;
89124
90- // TODO: https://github.com/privacy-scaling-explorations/sonobe/issues/104
91- // We disable checking constraints for now
92- // Checks for constraint satisfaction.
93- // if !cs.is_satisfied().unwrap() {
94- // return Err(SynthesisError::Unsatisfiable);
95- // }
125+ // Generates the constraints for the circom_circuit.
126+ for ( a, b, c) in & self . r1cs . constraints {
127+ cs. enforce_constraint (
128+ a. iter ( ) . fold ( lc ! ( ) , fold_lc) ,
129+ b. iter ( ) . fold ( lc ! ( ) , fold_lc) ,
130+ c. iter ( ) . fold ( lc ! ( ) , fold_lc) ,
131+ ) ?;
132+ }
96133
97- // Extracts the z_i1(next state) from the witness vector.
98- let z_i1: Vec < FpVar < F > > =
99- Vec :: < FpVar < F > > :: new_witness ( cs. clone ( ) , || Ok ( witness[ 1 ..1 + SL ] . to_vec ( ) ) ) ?;
134+ #[ cfg( test) ]
135+ if !cs. is_in_setup_mode ( ) && !cs. is_satisfied ( ) ? {
136+ return Err ( SynthesisError :: Unsatisfiable ) ;
137+ }
100138
101139 Ok ( z_i1)
102140 }
103141}
104142
105143impl < F : PrimeField , const SL : usize , const EIL : usize > CircomFCircuit < F , SL , EIL > {
106- fn fpvars_to_bigints ( & self , fpvars : & [ FpVar < F > ] ) -> Result < Vec < BigInt > , SynthesisError > {
107- let mut input_values = Vec :: new ( ) ;
108- // converts each FpVar to PrimeField value, then to num_bigint::BigInt.
109- for fp_var in fpvars. iter ( ) {
110- // extracts the PrimeField value from FpVar.
111- let primefield_value = fp_var. value ( ) ?;
112- // converts the PrimeField value to num_bigint::BigInt.
113- let num_bigint_value = self
114- . circom_wrapper
115- . ark_primefield_to_num_bigint ( primefield_value) ;
116- input_values. push ( num_bigint_value) ;
117- }
118- Ok ( input_values)
144+ fn fpvars_to_bigints ( fpvars : & [ FpVar < F > ] ) -> Vec < BigInt > {
145+ fpvars
146+ . value ( )
147+ . unwrap_or ( vec ! [ F :: zero( ) ; fpvars. len( ) ] )
148+ . into_iter ( )
149+ . map ( Into :: < BigUint > :: into)
150+ . map ( BigInt :: from)
151+ . collect ( )
119152 }
120153}
121154
122155#[ cfg( test) ]
123156pub mod tests {
124157 use super :: * ;
125158 use ark_bn254:: Fr ;
126- use ark_relations:: r1cs:: ConstraintSystem ;
159+ use ark_r1cs_std:: alloc:: AllocVar ;
160+ use ark_relations:: r1cs:: { ConstraintSynthesizer , ConstraintSystem } ;
127161 use std:: path:: PathBuf ;
128162
129163 /// Native implementation of `src/circom/test_folder/cubic_circuit.r1cs`
0 commit comments