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