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}