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