hash-chain-sonobe/src/keccak_chain.rs
src/keccak_chain.rsRaw
1///
2/// This example performs the full flow:
3/// - define the circuit to be folded
4/// - fold the circuit with Nova+CycleFold's IVC
5/// - generate a DeciderEthCircuit final proof
6/// - generate the Solidity contract that verifies the proof
7/// - verify the proof in the EVM
8///
9
10#[cfg(test)]
11mod tests {
12 use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as G1};
13 use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2};
14
15 use ark_groth16::Groth16;
16
17 use ark_ff::PrimeField;
18
19 use std::path::PathBuf;
20 use std::rc::Rc;
21 use std::time::Instant;
22
23 use folding_schemes::{
24 commitment::{kzg::KZG, pedersen::Pedersen},
25 folding::nova::{
26 decider_eth::{prepare_calldata, Decider as DeciderEth},
27 Nova, PreprocessorParam,
28 },
29 frontend::{circom::CircomFCircuit, FCircuit},
30 transcript::poseidon::poseidon_canonical_config,
31 Decider, Error, FoldingScheme,
32 };
33 use solidity_verifiers::{
34 utils::get_function_selector_for_nova_cyclefold_verifier,
35 verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider,
36 NovaCycleFoldVerifierKey,
37 };
38
39 use crate::utils::tests::*;
40
41 // function to compute the next state of the folding via rust-native code (not Circom). Used to
42 // check the Circom values.
43 use tiny_keccak::{Hasher, Keccak};
44 fn rust_native_step<F: PrimeField>(
45 _i: usize,
46 z_i: Vec<F>,
47 _external_inputs: Vec<F>,
48 ) -> Result<Vec<F>, Error> {
49 let b = f_vec_bits_to_bytes(z_i.to_vec());
50 let mut h = Keccak::v256();
51 h.update(&b);
52 let mut z_i1 = [0u8; 32];
53 h.finalize(&mut z_i1);
54 bytes_to_f_vec_bits(z_i1.to_vec())
55 }
56
57 #[test]
58 fn full_flow() {
59 // set how many steps of folding we want to compute
60 let n_steps = 1000;
61
62 // set the initial state
63 let z_0_aux: Vec<u32> = vec![0_u32; 32 * 8];
64 let z_0: Vec<Fr> = z_0_aux.iter().map(|v| Fr::from(*v)).collect::<Vec<Fr>>();
65
66 // initialize the Circom circuit
67 let r1cs_path = PathBuf::from("./circuit/keccak-chain.r1cs");
68 let wasm_path = PathBuf::from("./circuit/keccak-chain_js/keccak-chain.wasm");
69
70 let f_circuit_params = (r1cs_path, wasm_path, 32 * 8, 0);
71 let mut f_circuit = CircomFCircuit::<Fr>::new(f_circuit_params).unwrap();
72 // Note (optional): for more speed, we can set a custom rust-native logic, which will be
73 // used for the `step_native` method instead of extracting the values from the circom
74 // witness:
75 f_circuit.set_custom_step_native(Rc::new(rust_native_step));
76
77 // ----------------
78 // Sanity check
79 // check that the f_circuit produces valid R1CS constraints
80 use ark_r1cs_std::alloc::AllocVar;
81 use ark_r1cs_std::fields::fp::FpVar;
82 use ark_r1cs_std::R1CSVar;
83 use ark_relations::r1cs::ConstraintSystem;
84 let cs = ConstraintSystem::<Fr>::new_ref();
85 let z_0_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap();
86 let z_1_var = f_circuit
87 .generate_step_constraints(cs.clone(), 1, z_0_var, vec![])
88 .unwrap();
89 // check z_1_var against the native z_1
90 let z_1_native = f_circuit.step_native(1, z_0.clone(), vec![]).unwrap();
91 assert_eq!(z_1_var.value().unwrap(), z_1_native);
92 // check that the constraint system is satisfied
93 assert!(cs.is_satisfied().unwrap());
94 // ----------------
95
96 // define type aliases to avoid writting the whole type each time
97 pub type N =
98 Nova<G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>, false>;
99 pub type D = DeciderEth<
100 G1,
101 GVar,
102 G2,
103 GVar2,
104 CircomFCircuit<Fr>,
105 KZG<'static, Bn254>,
106 Pedersen<G2>,
107 Groth16<Bn254>,
108 N,
109 >;
110
111 let poseidon_config = poseidon_canonical_config::<Fr>();
112 let mut rng = rand::rngs::OsRng;
113
114 // prepare the Nova prover & verifier params
115 let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
116 let start = Instant::now();
117 let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
118 println!("Nova params generated: {:?}", start.elapsed());
119
120 // initialize the folding scheme engine, in our case we use Nova
121 let mut nova = N::init(&nova_params, f_circuit.clone(), z_0.clone()).unwrap();
122
123 // prepare the Decider prover & verifier params
124 let start = Instant::now();
125 let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
126 println!("Decider params generated: {:?}", start.elapsed());
127
128 // run n steps of the folding iteration
129 let start_full = Instant::now();
130 for _ in 0..n_steps {
131 let start = Instant::now();
132 nova.prove_step(rng, vec![], None).unwrap();
133 println!(
134 "Nova::prove_step (keccak256 through Circom) {}: {:?}",
135 nova.i,
136 start.elapsed()
137 );
138 }
139 println!(
140 "Nova's all {} steps time: {:?}",
141 n_steps,
142 start_full.elapsed()
143 );
144
145 // perform the hash chain natively in rust (which uses a rust Keccak256 library)
146 let mut z_i_native = z_0.clone();
147 for i in 0..n_steps {
148 z_i_native = rust_native_step(i, z_i_native.clone(), vec![]).unwrap();
149 }
150 // check that the value of the last folding state (nova.z_i) computed through folding, is
151 // equal to the natively computed hash using the rust_native_step method
152 assert_eq!(nova.z_i, z_i_native);
153
154 // ----------------
155 // Sanity check
156 // The following lines contain a sanity check that checks the IVC proof (before going into
157 // the zkSNARK proof)
158 let (running_instance, incoming_instance, cyclefold_instance) = nova.instances();
159 N::verify(
160 nova_params.1, // Nova's verifier params
161 z_0,
162 nova.z_i.clone(),
163 nova.i,
164 running_instance,
165 incoming_instance,
166 cyclefold_instance,
167 )
168 .unwrap();
169 // ----------------
170
171 let rng = rand::rngs::OsRng;
172 let start = Instant::now();
173 let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
174 println!("generated Decider proof: {:?}", start.elapsed());
175
176 let verified = D::verify(
177 decider_vp.clone(),
178 nova.i,
179 nova.z_0.clone(),
180 nova.z_i.clone(),
181 &nova.U_i,
182 &nova.u_i,
183 &proof,
184 )
185 .unwrap();
186 assert!(verified);
187 println!("Decider proof verification: {}", verified);
188
189 // generate the Solidity code that verifies this Decider final proof
190 let function_selector =
191 get_function_selector_for_nova_cyclefold_verifier(nova.z_0.len() * 2 + 1);
192
193 let calldata: Vec<u8> = prepare_calldata(
194 function_selector,
195 nova.i,
196 nova.z_0,
197 nova.z_i,
198 &nova.U_i,
199 &nova.u_i,
200 proof,
201 )
202 .unwrap();
203
204 // prepare the setup params for the solidity verifier
205 let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, f_circuit.state_len()));
206
207 // generate the solidity code
208 let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk);
209
210 /*
211 * Note: since we're proving the Keccak256 (ie. 32 byte size, 256 bits), the number of
212 * inputs is too big for the contract. In a real world use case we would convert the binary
213 * representation into a couple of field elements which would be inputs of the Decider
214 * circuit, and in-circuit we would obtain the binary representation to be used for the
215 * final proof check.
216 *
217 * The following code is commented out for that reason.
218 // verify the proof against the solidity code in the EVM
219 use solidity_verifiers::evm::{compile_solidity, Evm};
220 let nova_cyclefold_verifier_bytecode =
221 compile_solidity(&decider_solidity_code, "NovaDecider");
222 let mut evm = Evm::default();
223 let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
224 let (_, output) = evm.call(verifier_address, calldata.clone());
225 assert_eq!(*output.last().unwrap(), 1);
226 */
227
228 // save smart contract and the calldata
229 println!("storing nova-verifier.sol and the calldata into files");
230 use std::fs;
231 fs::create_dir_all("./solidity").unwrap();
232 fs::write(
233 "./solidity/nova-verifier.sol",
234 decider_solidity_code.clone(),
235 )
236 .unwrap();
237 fs::write("./solidity/solidity-calldata.calldata", calldata.clone()).unwrap();
238 let s = solidity_verifiers::utils::get_formatted_calldata(calldata.clone());
239 fs::write("./solidity/solidity-calldata.inputs", s.join(",\n")).expect("");
240 }
241}