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!("Nova's all steps time: {:?}", start_full.elapsed());
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.clone(), vec![]).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 N::verify(
156 nova_params.1, // 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 let rng = rand::rngs::OsRng;
168 let start = Instant::now();
169 let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
170 println!("generated Decider proof: {:?}", start.elapsed());
171
172 let verified = D::verify(
173 decider_vp.clone(),
174 nova.i,
175 nova.z_0.clone(),
176 nova.z_i.clone(),
177 &nova.U_i,
178 &nova.u_i,
179 &proof,
180 )
181 .unwrap();
182 assert!(verified);
183 println!("Decider proof verification: {}", verified);
184
185 // generate the Solidity code that verifies this Decider final proof
186 let function_selector =
187 get_function_selector_for_nova_cyclefold_verifier(nova.z_0.len() * 2 + 1);
188
189 let calldata: Vec<u8> = prepare_calldata(
190 function_selector,
191 nova.i,
192 nova.z_0,
193 nova.z_i,
194 &nova.U_i,
195 &nova.u_i,
196 proof,
197 )
198 .unwrap();
199
200 // prepare the setup params for the solidity verifier
201 let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, f_circuit.state_len()));
202
203 // generate the solidity code
204 let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk);
205
206 /*
207 * Note: since we're proving the Keccak256 (ie. 32 byte size, 256 bits), the number of
208 * inputs is too big for the contract. In a real world use case we would convert the binary
209 * representation into a couple of field elements which would be inputs of the Decider
210 * circuit, and in-circuit we would obtain the binary representation to be used for the
211 * final proof check.
212 *
213 * The following code is commented out for that reason.
214 // verify the proof against the solidity code in the EVM
215 use solidity_verifiers::evm::{compile_solidity, Evm};
216 let nova_cyclefold_verifier_bytecode =
217 compile_solidity(&decider_solidity_code, "NovaDecider");
218 let mut evm = Evm::default();
219 let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
220 let (_, output) = evm.call(verifier_address, calldata.clone());
221 assert_eq!(*output.last().unwrap(), 1);
222 */
223
224 // save smart contract and the calldata
225 println!("storing nova-verifier.sol and the calldata into files");
226 use std::fs;
227 fs::create_dir_all("./solidity").unwrap();
228 fs::write(
229 "./solidity/nova-verifier.sol",
230 decider_solidity_code.clone(),
231 )
232 .unwrap();
233 fs::write("./solidity/solidity-calldata.calldata", calldata.clone()).unwrap();
234 let s = solidity_verifiers::utils::get_formatted_calldata(calldata.clone());
235 fs::write("./solidity/solidity-calldata.inputs", s.join(",\n")).expect("");
236 }
237}