hash-chain-sonobe/src/sha_chain_onchain.rs
src/sha_chain_onchain.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/// - verify the IVC proof
6/// - generate a DeciderEthCircuit final proof
7/// - generate the Solidity contract that verifies the proof
8/// - verify the proof in the EVM
9///
10
11#[cfg(test)]
12mod tests {
13 use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as G1};
14 use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2};
15
16 use ark_groth16::Groth16;
17
18 use ark_ff::PrimeField;
19
20 use std::time::Instant;
21
22 use ark_crypto_primitives::crh::sha256::constraints::Sha256Gadget;
23 use ark_r1cs_std::fields::fp::FpVar;
24 use ark_r1cs_std::{
25 boolean::Boolean,
26 convert::{ToBitsGadget, ToBytesGadget},
27 uint8::UInt8,
28 };
29 use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
30 use std::marker::PhantomData;
31
32 use folding_schemes::{
33 commitment::{kzg::KZG, pedersen::Pedersen},
34 folding::{
35 nova::{
36 decider_eth::{prepare_calldata, Decider as DeciderEth},
37 Nova, PreprocessorParam,
38 },
39 traits::CommittedInstanceOps,
40 },
41 frontend::FCircuit,
42 transcript::poseidon::poseidon_canonical_config,
43 Decider, Error, FoldingScheme,
44 };
45 use solidity_verifiers::{
46 utils::get_function_selector_for_nova_cyclefold_verifier,
47 verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider,
48 NovaCycleFoldVerifierKey,
49 };
50
51 /// Test circuit to be folded
52 #[derive(Clone, Copy, Debug)]
53 pub struct SHA256FoldStepCircuit<F: PrimeField, const HASHES_PER_STEP: usize> {
54 _f: PhantomData<F>,
55 }
56 impl<F: PrimeField, const HASHES_PER_STEP: usize> FCircuit<F>
57 for SHA256FoldStepCircuit<F, HASHES_PER_STEP>
58 {
59 type Params = ();
60 fn new(_params: Self::Params) -> Result<Self, Error> {
61 Ok(Self { _f: PhantomData })
62 }
63 fn state_len(&self) -> usize {
64 32
65 }
66 fn external_inputs_len(&self) -> usize {
67 0
68 }
69 fn generate_step_constraints(
70 &self,
71 _cs: ConstraintSystemRef<F>,
72 _i: usize,
73 z_i: Vec<FpVar<F>>,
74 _external_inputs: Vec<FpVar<F>>,
75 ) -> Result<Vec<FpVar<F>>, SynthesisError> {
76 let mut b: Vec<UInt8<F>> = z_i
77 .iter()
78 .map(|f| UInt8::<F>::from_bits_le(&f.to_bits_le().unwrap()[..8]))
79 .collect::<Vec<_>>();
80
81 for _ in 0..HASHES_PER_STEP {
82 let mut sha256_var = Sha256Gadget::default();
83 sha256_var.update(&b).unwrap();
84 b = sha256_var.finalize()?.to_bytes_le()?;
85 }
86
87 let z_i1: Vec<FpVar<F>> = b
88 .iter()
89 .map(|e| {
90 let bits = e.to_bits_le().unwrap();
91 Boolean::<F>::le_bits_to_fp(&bits).unwrap()
92 })
93 .collect();
94
95 Ok(z_i1)
96 }
97 }
98
99 #[test]
100 fn full_flow() {
101 // set how many steps of folding we want to compute
102 const N_STEPS: usize = 100;
103 const HASHES_PER_STEP: usize = 10;
104 println!("running Nova folding scheme on SHA256FoldStepCircuit, with N_STEPS={}, HASHES_PER_STEP={}. Total hashes = {}", N_STEPS, HASHES_PER_STEP, N_STEPS* HASHES_PER_STEP);
105
106 // set the initial state
107 // let z_0_aux: Vec<u32> = vec![0_u32; 32 * 8];
108 let z_0_aux: Vec<u8> = vec![0_u8; 32];
109 let z_0: Vec<Fr> = z_0_aux.iter().map(|v| Fr::from(*v)).collect::<Vec<Fr>>();
110
111 let f_circuit = SHA256FoldStepCircuit::<Fr, HASHES_PER_STEP>::new(()).unwrap();
112
113 // ----------------
114 // Sanity check
115 // check that the f_circuit produces valid R1CS constraints
116 use ark_r1cs_std::alloc::AllocVar;
117 use ark_r1cs_std::fields::fp::FpVar;
118 use ark_relations::r1cs::ConstraintSystem;
119 let cs = ConstraintSystem::<Fr>::new_ref();
120 let z_0_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap();
121 let _z_1_var = f_circuit
122 .generate_step_constraints(cs.clone(), 1, z_0_var, vec![])
123 .unwrap();
124 // check that the constraint system is satisfied
125 assert!(cs.is_satisfied().unwrap());
126 println!(
127 "number of constraints of a single instantiation of the SHA256FoldStepCircuit: {}",
128 cs.num_constraints()
129 );
130 // ----------------
131
132 // define type aliases for the FoldingScheme (FS) and Decider (D), to avoid writting the
133 // whole type each time
134 pub type FS = Nova<
135 G1,
136 GVar,
137 G2,
138 GVar2,
139 SHA256FoldStepCircuit<Fr, HASHES_PER_STEP>,
140 KZG<'static, Bn254>,
141 Pedersen<G2>,
142 false,
143 >;
144 pub type D = DeciderEth<
145 G1,
146 GVar,
147 G2,
148 GVar2,
149 SHA256FoldStepCircuit<Fr, HASHES_PER_STEP>,
150 KZG<'static, Bn254>,
151 Pedersen<G2>,
152 Groth16<Bn254>,
153 FS,
154 >;
155
156 let poseidon_config = poseidon_canonical_config::<Fr>();
157 let mut rng = rand::rngs::OsRng;
158
159 // prepare the Nova prover & verifier params
160 let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit);
161 let start = Instant::now();
162 let nova_params = FS::preprocess(&mut rng, &nova_preprocess_params).unwrap();
163 println!("Nova params generated: {:?}", start.elapsed());
164
165 // initialize the folding scheme engine, in our case we use Nova
166 let mut nova = FS::init(&nova_params, f_circuit, z_0.clone()).unwrap();
167
168 // run n steps of the folding iteration
169 let start_full = Instant::now();
170 for _ in 0..N_STEPS {
171 let start = Instant::now();
172 nova.prove_step(rng, vec![], None).unwrap();
173 println!(
174 "Nova::prove_step (sha256) {}: {:?}",
175 nova.i,
176 start.elapsed()
177 );
178 }
179 println!(
180 "Nova's all {} steps time: {:?}",
181 N_STEPS,
182 start_full.elapsed()
183 );
184
185 // ----------------
186 // Sanity check
187 // The following lines contain a sanity check that checks the IVC proof (before going into
188 // the zkSNARK proof)
189 let ivc_proof = nova.ivc_proof();
190 FS::verify(
191 nova_params.1.clone(), // Nova's verifier params
192 ivc_proof,
193 )
194 .unwrap();
195 // ----------------
196
197 // prepare the Decider prover & verifier params
198 let start = Instant::now();
199 let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
200 println!("Decider params generated: {:?}", start.elapsed());
201
202 let rng = rand::rngs::OsRng;
203 let start = Instant::now();
204 let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
205 println!("generated Decider proof: {:?}", start.elapsed());
206
207 let verified = D::verify(
208 decider_vp.clone(),
209 nova.i,
210 nova.z_0.clone(),
211 nova.z_i.clone(),
212 &nova.U_i.get_commitments(),
213 &nova.u_i.get_commitments(),
214 &proof,
215 )
216 .unwrap();
217 assert!(verified);
218 println!("Decider proof verification: {}", verified);
219
220 // generate the Solidity code that verifies this Decider final proof
221 let function_selector =
222 get_function_selector_for_nova_cyclefold_verifier(nova.z_0.len() * 2 + 1);
223
224 let calldata: Vec<u8> = prepare_calldata(
225 function_selector,
226 nova.i,
227 nova.z_0,
228 nova.z_i,
229 &nova.U_i,
230 &nova.u_i,
231 proof,
232 )
233 .unwrap();
234
235 // prepare the setup params for the solidity verifier
236 let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, f_circuit.state_len()));
237
238 // generate the solidity code
239 let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk);
240
241 /*
242 * Note: since we're proving the SHA256 (ie. 32 byte size, 256 bits), the number of inputs
243 * is too big for the contract. In a real world use case we would convert the binary
244 * representation into a couple of field elements which would be inputs of the Decider
245 * circuit, and in-circuit we would obtain the binary representation to be used for the
246 * final proof check.
247 *
248 * The following code is commented out for that reason.
249 // verify the proof against the solidity code in the EVM
250 use solidity_verifiers::evm::{compile_solidity, Evm};
251 let nova_cyclefold_verifier_bytecode =
252 compile_solidity(&decider_solidity_code, "NovaDecider");
253 let mut evm = Evm::default();
254 let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
255 let (_, output) = evm.call(verifier_address, calldata.clone());
256 assert_eq!(*output.last().unwrap(), 1);
257 */
258
259 // save smart contract and the calldata
260 println!("storing nova-verifier.sol and the calldata into files");
261 use std::fs;
262 fs::create_dir_all("./solidity").unwrap();
263 fs::write(
264 "./solidity/nova-verifier.sol",
265 decider_solidity_code.clone(),
266 )
267 .unwrap();
268 fs::write("./solidity/solidity-calldata.calldata", calldata.clone()).unwrap();
269 let s = solidity_verifiers::utils::get_formatted_calldata(calldata.clone());
270 fs::write("./solidity/solidity-calldata.inputs", s.join(",\n")).expect("");
271 }
272}