arnaucube
arnaucube/hash-chain-sonobePublic Self-hosted repository
hash-chain-sonobe/src/lib.rs
src/lib.rsRaw
  1#![allow(non_snake_case)]
  2#![allow(non_camel_case_types)]
  3#![allow(clippy::upper_case_acronyms)]
  4///
  5/// This example performs the full flow:
  6/// - define the circuit to be folded
  7/// - fold the circuit with Nova+CycleFold's IVC
  8/// - generate a DeciderEthCircuit final proof
  9/// - generate the Solidity contract that verifies the proof
 10/// - verify the proof in the EVM
 11///
 12
 13#[cfg(test)]
 14mod tests {
 15    use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as G1};
 16    use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2};
 17
 18    use ark_crypto_primitives::snark::SNARK;
 19    use ark_groth16::{Groth16, ProvingKey, VerifyingKey as G16VerifierKey};
 20    use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey;
 21
 22    use ark_ff::{BigInteger, BigInteger256, PrimeField};
 23    use ark_std::Zero;
 24
 25    use std::path::PathBuf;
 26    use std::time::Instant;
 27
 28    use folding_schemes::{
 29        commitment::{
 30            kzg::{ProverKey as KZGProverKey, KZG},
 31            pedersen::Pedersen,
 32            CommitmentScheme,
 33        },
 34        folding::nova::{
 35            decider_eth::{prepare_calldata, Decider as DeciderEth},
 36            decider_eth_circuit::DeciderEthCircuit,
 37            get_r1cs, Nova, ProverParams, VerifierParams,
 38        },
 39        frontend::{circom::CircomFCircuit, FCircuit},
 40        transcript::poseidon::poseidon_test_config,
 41        Decider, FoldingScheme,
 42    };
 43    use solidity_verifiers::{
 44        evm::{compile_solidity, Evm},
 45        utils::get_function_selector_for_nova_cyclefold_verifier,
 46        verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider,
 47        NovaCycleFoldVerifierKey,
 48    };
 49
 50    // This method computes the Nova's Prover & Verifier parameters for the example.
 51    // Warning: this method is only for testing purposes. For a real world use case those parameters
 52    // should be generated carefully (both the PoseidonConfig and the PedersenParams).
 53    #[allow(clippy::type_complexity)]
 54    fn init_nova_ivc_params<FC: FCircuit<Fr>>(
 55        F_circuit: FC,
 56    ) -> (
 57        ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
 58        VerifierParams<G1, G2>,
 59        KZGVerifierKey<Bn254>,
 60    ) {
 61        let mut rng = ark_std::test_rng();
 62        let poseidon_config = poseidon_test_config::<Fr>();
 63
 64        // get the CM & CF_CM len
 65        let (r1cs, cf_r1cs) =
 66            get_r1cs::<G1, GVar, G2, GVar2, FC>(&poseidon_config, F_circuit).unwrap();
 67        let cs_len = r1cs.A.n_rows;
 68        let cf_cs_len = cf_r1cs.A.n_rows;
 69
 70        // let (pedersen_params, _) = Pedersen::<G1>::setup(&mut rng, cf_len).unwrap();
 71        let (kzg_pk, kzg_vk): (KZGProverKey<G1>, KZGVerifierKey<Bn254>) =
 72            KZG::<Bn254>::setup(&mut rng, cs_len).unwrap();
 73        let (cf_pedersen_params, _) = Pedersen::<G2>::setup(&mut rng, cf_cs_len).unwrap();
 74
 75        let fs_prover_params = ProverParams::<G1, G2, KZG<Bn254>, Pedersen<G2>> {
 76            poseidon_config: poseidon_config.clone(),
 77            cs_params: kzg_pk.clone(),
 78            cf_cs_params: cf_pedersen_params,
 79        };
 80        let fs_verifier_params = VerifierParams::<G1, G2> {
 81            poseidon_config: poseidon_config.clone(),
 82            r1cs,
 83            cf_r1cs,
 84        };
 85        (fs_prover_params, fs_verifier_params, kzg_vk)
 86    }
 87
 88    /// Initializes Nova parameters and DeciderEth parameters. Only for test purposes.
 89    #[allow(clippy::type_complexity)]
 90    fn init_ivc_and_decider_params<FC: FCircuit<Fr>>(
 91        f_circuit: FC,
 92    ) -> (
 93        ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
 94        KZGVerifierKey<Bn254>,
 95        ProvingKey<Bn254>,
 96        G16VerifierKey<Bn254>,
 97    ) {
 98        let mut rng = rand::rngs::OsRng;
 99        let start = Instant::now();
100        let (fs_prover_params, _, kzg_vk) = init_nova_ivc_params::<FC>(f_circuit.clone());
101        println!("generated Nova folding params: {:?}", start.elapsed());
102
103        pub type NOVA<FC> = Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>;
104        let z_0 = vec![Fr::zero(); f_circuit.state_len()];
105        let nova = NOVA::init(&fs_prover_params, f_circuit, z_0.clone()).unwrap();
106
107        let decider_circuit =
108            DeciderEthCircuit::<G1, GVar, G2, GVar2, KZG<Bn254>, Pedersen<G2>>::from_nova::<FC>(
109                nova.clone(),
110            )
111            .unwrap();
112        let start = Instant::now();
113        let (g16_pk, g16_vk) =
114            Groth16::<Bn254>::circuit_specific_setup(decider_circuit.clone(), &mut rng).unwrap();
115        println!(
116            "generated G16 (Decider circuit) params: {:?}",
117            start.elapsed()
118        );
119        (fs_prover_params, kzg_vk, g16_pk, g16_vk)
120    }
121
122    fn f_vec_to_bits<F: PrimeField>(v: Vec<F>) -> Vec<bool> {
123        v.iter()
124            .map(|v_i| {
125                if v_i.is_one() {
126                    return true;
127                }
128                return false;
129            })
130            .collect()
131    }
132    // returns the bytes representation of the given vector of finite field elements that represent
133    // bits
134    fn f_vec_to_bytes<F: PrimeField>(v: Vec<F>) -> Vec<u8> {
135        let b = f_vec_to_bits(v);
136        BigInteger256::from_bits_le(&b).to_bytes_le()
137    }
138
139    // function to compute the next state of the folding via rust-native code (not Circom). Used to
140    // check the Circom values.
141    use tiny_keccak::{Hasher, Keccak};
142    fn rust_native_step(z_i: [u8; 32]) -> [u8; 32] {
143        let mut h = Keccak::v256();
144        h.update(&z_i);
145        let mut z_i1 = [0u8; 32];
146        h.finalize(&mut z_i1);
147        z_i1
148    }
149
150    #[test]
151    fn full_flow() {
152        // set the initial state
153        let z_0_aux: Vec<u32> = vec![0_u32; 32 * 8];
154        let z_0: Vec<Fr> = z_0_aux.iter().map(|v| Fr::from(*v)).collect::<Vec<Fr>>();
155
156        // initialize the Circom circuit
157        let r1cs_path = PathBuf::from("./circuit/keccak-chain.r1cs");
158        let wasm_path = PathBuf::from("./circuit/keccak-chain_js/keccak-chain.wasm");
159
160        let f_circuit_params = (r1cs_path, wasm_path, 32 * 8, 0);
161        let f_circuit = CircomFCircuit::<Fr>::new(f_circuit_params).unwrap();
162
163        let (fs_prover_params, kzg_vk, g16_pk, g16_vk) =
164            init_ivc_and_decider_params::<CircomFCircuit<Fr>>(f_circuit.clone());
165
166        pub type NOVA =
167            Nova<G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>>;
168        pub type DECIDERETH_FCircuit = DeciderEth<
169            G1,
170            GVar,
171            G2,
172            GVar2,
173            CircomFCircuit<Fr>,
174            KZG<'static, Bn254>,
175            Pedersen<G2>,
176            Groth16<Bn254>,
177            NOVA,
178        >;
179
180        // initialize the folding scheme engine, in our case we use Nova
181        let mut nova = NOVA::init(&fs_prover_params, f_circuit.clone(), z_0.clone()).unwrap();
182        // run n steps of the folding iteration
183        for _ in 0..3 {
184            let start = Instant::now();
185            nova.prove_step(vec![]).unwrap();
186            println!("Nova::prove_step {}: {:?}", nova.i, start.elapsed());
187        }
188
189        // perform the hash chain natively in rust
190        let z_0_bytes: [u8; 32] = [0u8; 32];
191        let z_1_bytes = rust_native_step(z_0_bytes);
192        let z_2_bytes = rust_native_step(z_1_bytes);
193        let z_3_bytes = rust_native_step(z_2_bytes);
194
195        // check that the value of the last folding state (nova.z_i) computed through folding, is equal to the natively
196        // computed hash using the rust_native_step method
197        let nova_z_i = f_vec_to_bytes(nova.z_i.clone());
198        assert_eq!(nova_z_i, z_3_bytes);
199
200        /*
201        // The following lines contain a sanity check that checks the IVC proof (before going into
202        // the zkSNARK proof)
203        let verifier_params = VerifierParams::<G1, G2> {
204            poseidon_config: nova.poseidon_config.clone(),
205            r1cs: nova.clone().r1cs,
206            cf_r1cs: nova.clone().cf_r1cs,
207        };
208        let (running_instance, incoming_instance, cyclefold_instance) = nova.instances();
209        NOVA::verify(
210            verifier_params,
211            z_0,
212            nova.z_i.clone(),
213            nova.i,
214            running_instance,
215            incoming_instance,
216            cyclefold_instance,
217        )
218        .unwrap();
219         */
220
221        let rng = rand::rngs::OsRng;
222        let start = Instant::now();
223        let proof = DECIDERETH_FCircuit::prove(
224            (g16_pk, fs_prover_params.cs_params.clone()),
225            rng,
226            nova.clone(),
227        )
228        .unwrap();
229        println!("generated Decider proof: {:?}", start.elapsed());
230
231        let verified = DECIDERETH_FCircuit::verify(
232            (g16_vk.clone(), kzg_vk.clone()),
233            nova.i,
234            nova.z_0.clone(),
235            nova.z_i.clone(),
236            &nova.U_i,
237            &nova.u_i,
238            &proof,
239        )
240        .unwrap();
241        assert!(verified);
242        println!("Decider proof verification: {}", verified);
243
244        // generate the Solidity code that verifies this Decider final proof
245        let function_selector =
246            get_function_selector_for_nova_cyclefold_verifier(nova.z_0.len() * 2 + 1);
247
248        let calldata: Vec<u8> = prepare_calldata(
249            function_selector,
250            nova.i,
251            nova.z_0,
252            nova.z_i,
253            &nova.U_i,
254            &nova.u_i,
255            proof,
256        )
257        .unwrap();
258
259        // prepare the setup params for the solidity verifier
260        let nova_cyclefold_vk =
261            NovaCycleFoldVerifierKey::from((g16_vk, kzg_vk, f_circuit.state_len()));
262
263        // generate the solidity code
264        let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk);
265
266        // verify the proof against the solidity code in the EVM
267        let nova_cyclefold_verifier_bytecode =
268            compile_solidity(&decider_solidity_code, "NovaDecider");
269        let mut evm = Evm::default();
270        let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
271        let (_, output) = evm.call(verifier_address, calldata.clone());
272        assert_eq!(*output.last().unwrap(), 1);
273
274        // save smart contract and the calldata
275        println!("storing nova-verifier.sol and the calldata into files");
276        use std::fs;
277        fs::write(
278            "./examples/nova-verifier.sol",
279            decider_solidity_code.clone(),
280        )
281        .unwrap();
282        fs::write("./examples/solidity-calldata.calldata", calldata.clone()).unwrap();
283        let s = solidity_verifiers::utils::get_formatted_calldata(calldata.clone());
284        fs::write("./examples/solidity-calldata.inputs", s.join(",\n")).expect("");
285    }
286}