hash-chain-sonobe/src/sha_chain_offchain.rs
src/sha_chain_offchain.rsRaw
1///
2/// This example performs the IVC:
3/// - define the circuit to be folded
4/// - fold the circuit with Nova+CycleFold's IVC
5/// - verify the IVC proof
6///
7
8#[cfg(test)]
9mod tests {
10 use ark_pallas::{constraints::GVar, Fr, Projective as G1};
11 use ark_vesta::{constraints::GVar as GVar2, Projective as G2};
12
13 use ark_crypto_primitives::crh::sha256::{constraints::Sha256Gadget, digest::Digest, Sha256};
14 use ark_ff::PrimeField;
15 use ark_r1cs_std::fields::fp::FpVar;
16 use ark_r1cs_std::{
17 boolean::Boolean,
18 convert::{ToBitsGadget, ToBytesGadget},
19 uint8::UInt8,
20 };
21 use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
22 use std::marker::PhantomData;
23 use std::time::Instant;
24
25 use folding_schemes::{
26 commitment::pedersen::Pedersen,
27 folding::nova::{Nova, PreprocessorParam},
28 frontend::FCircuit,
29 transcript::poseidon::poseidon_canonical_config,
30 Error, FoldingScheme,
31 };
32
33 use crate::utils::tests::*;
34
35 /// Test circuit to be folded
36 #[derive(Clone, Copy, Debug)]
37 pub struct SHA256FoldStepCircuit<F: PrimeField, const HASHES_PER_STEP: usize> {
38 _f: PhantomData<F>,
39 }
40 impl<F: PrimeField, const HASHES_PER_STEP: usize> FCircuit<F>
41 for SHA256FoldStepCircuit<F, HASHES_PER_STEP>
42 {
43 type Params = ();
44 fn new(_params: Self::Params) -> Result<Self, Error> {
45 Ok(Self { _f: PhantomData })
46 }
47 fn state_len(&self) -> usize {
48 32
49 }
50 fn external_inputs_len(&self) -> usize {
51 0
52 }
53 fn step_native(
54 &self,
55 _i: usize,
56 z_i: Vec<F>,
57 _external_inputs: Vec<F>,
58 ) -> Result<Vec<F>, Error> {
59 let mut b = f_vec_to_bytes(z_i.to_vec());
60
61 for _ in 0..HASHES_PER_STEP {
62 let mut sha256 = Sha256::default();
63 sha256.update(b);
64 b = sha256.finalize().to_vec();
65 }
66
67 bytes_to_f_vec(b.to_vec()) // z_{i+1}
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 = 5;
103 const HASHES_PER_STEP: usize = 20;
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_r1cs_std::R1CSVar;
119 use ark_relations::r1cs::ConstraintSystem;
120 let cs = ConstraintSystem::<Fr>::new_ref();
121 let z_0_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap();
122 let z_1_var = f_circuit
123 .generate_step_constraints(cs.clone(), 1, z_0_var, vec![])
124 .unwrap();
125 // check z_1_var against the native z_1
126 let z_1_native = f_circuit.step_native(1, z_0.clone(), vec![]).unwrap();
127 assert_eq!(z_1_var.value().unwrap(), z_1_native);
128 // check that the constraint system is satisfied
129 assert!(cs.is_satisfied().unwrap());
130 println!(
131 "number of constraints of a single instantiation of the SHA256FoldStepCircuit: {}",
132 cs.num_constraints()
133 );
134 // ----------------
135
136 // define type aliases for the FoldingScheme (FS) and Decider (D), to avoid writting the
137 // whole type each time
138 pub type FS = Nova<
139 G1,
140 GVar,
141 G2,
142 GVar2,
143 SHA256FoldStepCircuit<Fr, HASHES_PER_STEP>,
144 Pedersen<G1>,
145 Pedersen<G2>,
146 false,
147 >;
148
149 let poseidon_config = poseidon_canonical_config::<Fr>();
150 let mut rng = rand::rngs::OsRng;
151
152 // prepare the Nova prover & verifier params
153 let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit);
154 let start = Instant::now();
155 let nova_params = FS::preprocess(&mut rng, &nova_preprocess_params).unwrap();
156 println!("Nova params generated: {:?}", start.elapsed());
157
158 // initialize the folding scheme engine, in our case we use Nova
159 let mut nova = FS::init(&nova_params, f_circuit, z_0.clone()).unwrap();
160
161 // run n steps of the folding iteration
162 let start_full = Instant::now();
163 for _ in 0..N_STEPS {
164 let start = Instant::now();
165 nova.prove_step(rng, vec![], None).unwrap();
166 println!(
167 "Nova::prove_step (sha256) {}: {:?}",
168 nova.i,
169 start.elapsed()
170 );
171 }
172 println!(
173 "Nova's all {} steps time: {:?}",
174 N_STEPS,
175 start_full.elapsed()
176 );
177
178 // verify the last IVC proof
179 let ivc_proof = nova.ivc_proof();
180 FS::verify(
181 nova_params.1.clone(), // Nova's verifier params
182 ivc_proof,
183 )
184 .unwrap();
185 }
186}