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: &[F],
47 _external_inputs: &[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 = 50;
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, &[])
88 .unwrap();
89 // check z_1_var against the native z_1
90 let z_1_native = f_circuit.step_native(1, &z_0, &[]).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 for the FoldingScheme (FS) and Decider (D), to avoid writting the
97 // whole type each time
98 pub type FS =
99 Nova<G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>, false>;
100 pub type D = DeciderEth<
101 G1,
102 GVar,
103 G2,
104 GVar2,
105 CircomFCircuit<Fr>,
106 KZG<'static, Bn254>,
107 Pedersen<G2>,
108 Groth16<Bn254>,
109 FS,
110 >;
111
112 let poseidon_config = poseidon_canonical_config::<Fr>();
113 let mut rng = rand::rngs::OsRng;
114
115 // prepare the Nova prover & verifier params
116 let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
117 let start = Instant::now();
118 let nova_params = FS::preprocess(&mut rng, &nova_preprocess_params).unwrap();
119 println!("Nova params generated: {:?}", start.elapsed());
120
121 // initialize the folding scheme engine, in our case we use Nova
122 let mut nova = FS::init(&nova_params, f_circuit.clone(), z_0.clone()).unwrap();
123
124 // run n steps of the folding iteration
125 let start_full = Instant::now();
126 for _ in 0..n_steps {
127 let start = Instant::now();
128 nova.prove_step(rng, vec![], None).unwrap();
129 println!(
130 "Nova::prove_step (keccak256 through Circom) {}: {:?}",
131 nova.i,
132 start.elapsed()
133 );
134 }
135 println!(
136 "Nova's all {} steps time: {:?}",
137 n_steps,
138 start_full.elapsed()
139 );
140
141 // perform the hash chain natively in rust (which uses a rust Keccak256 library)
142 let mut z_i_native = z_0.clone();
143 for i in 0..n_steps {
144 z_i_native = rust_native_step(i, &z_i_native, &[]).unwrap();
145 }
146 // check that the value of the last folding state (nova.z_i) computed through folding, is
147 // equal to the natively computed hash using the rust_native_step method
148 assert_eq!(nova.z_i, z_i_native);
149
150 // ----------------
151 // Sanity check
152 // The following lines contain a sanity check that checks the IVC proof (before going into
153 // the zkSNARK proof)
154 let (running_instance, incoming_instance, cyclefold_instance) = nova.instances();
155 FS::verify(
156 nova_params.1.clone(), // Nova's verifier params
157 z_0,
158 nova.z_i.clone(),
159 nova.i,
160 running_instance,
161 incoming_instance,
162 cyclefold_instance,
163 )
164 .unwrap();
165 // ----------------
166
167 // prepare the Decider prover & verifier params
168 let start = Instant::now();
169 let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
170 println!("Decider params generated: {:?}", start.elapsed());
171
172 let rng = rand::rngs::OsRng;
173 let start = Instant::now();
174 let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
175 println!("generated Decider proof: {:?}", start.elapsed());
176
177 let verified = D::verify(
178 decider_vp.clone(),
179 nova.i,
180 nova.z_0.clone(),
181 nova.z_i.clone(),
182 &nova.U_i,
183 &nova.u_i,
184 &proof,
185 )
186 .unwrap();
187 assert!(verified);
188 println!("Decider proof verification: {}", verified);
189
190 // generate the Solidity code that verifies this Decider final proof
191 let function_selector =
192 get_function_selector_for_nova_cyclefold_verifier(nova.z_0.len() * 2 + 1);
193
194 let calldata: Vec<u8> = prepare_calldata(
195 function_selector,
196 nova.i,
197 nova.z_0,
198 nova.z_i,
199 &nova.U_i,
200 &nova.u_i,
201 proof,
202 )
203 .unwrap();
204
205 // prepare the setup params for the solidity verifier
206 let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, f_circuit.state_len()));
207
208 // generate the solidity code
209 let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk);
210
211 /*
212 * Note: since we're proving the Keccak256 (ie. 32 byte size, 256 bits), the number of
213 * inputs is too big for the contract. In a real world use case we would convert the binary
214 * representation into a couple of field elements which would be inputs of the Decider
215 * circuit, and in-circuit we would obtain the binary representation to be used for the
216 * final proof check.
217 *
218 * The following code is commented out for that reason.
219 // verify the proof against the solidity code in the EVM
220 use solidity_verifiers::evm::{compile_solidity, Evm};
221 let nova_cyclefold_verifier_bytecode =
222 compile_solidity(&decider_solidity_code, "NovaDecider");
223 let mut evm = Evm::default();
224 let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
225 let (_, output) = evm.call(verifier_address, calldata.clone());
226 assert_eq!(*output.last().unwrap(), 1);
227 */
228
229 // save smart contract and the calldata
230 println!("storing nova-verifier.sol and the calldata into files");
231 use std::fs;
232 fs::create_dir_all("./solidity").unwrap();
233 fs::write(
234 "./solidity/nova-verifier.sol",
235 decider_solidity_code.clone(),
236 )
237 .unwrap();
238 fs::write("./solidity/solidity-calldata.calldata", calldata.clone()).unwrap();
239 let s = solidity_verifiers::utils::get_formatted_calldata(calldata.clone());
240 fs::write("./solidity/solidity-calldata.inputs", s.join(",\n")).expect("");
241 }
242}