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::{bits::uint8::UInt8, boolean::Boolean, ToBitsGadget, ToBytesGadget};
17 use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
18 use std::marker::PhantomData;
19 use std::time::Instant;
20
21 use folding_schemes::{
22 commitment::pedersen::Pedersen,
23 folding::nova::{Nova, PreprocessorParam},
24 frontend::FCircuit,
25 transcript::poseidon::poseidon_canonical_config,
26 Error, FoldingScheme,
27 };
28
29 use crate::utils::tests::*;
30
31 /// Test circuit to be folded
32 #[derive(Clone, Copy, Debug)]
33 pub struct SHA256FoldStepCircuit<F: PrimeField, const HASHES_PER_STEP: usize> {
34 _f: PhantomData<F>,
35 }
36 impl<F: PrimeField, const HASHES_PER_STEP: usize> FCircuit<F>
37 for SHA256FoldStepCircuit<F, HASHES_PER_STEP>
38 {
39 type Params = ();
40 fn new(_params: Self::Params) -> Result<Self, Error> {
41 Ok(Self { _f: PhantomData })
42 }
43 fn state_len(&self) -> usize {
44 32
45 }
46 fn external_inputs_len(&self) -> usize {
47 0
48 }
49 fn step_native(
50 &self,
51 _i: usize,
52 z_i: Vec<F>,
53 _external_inputs: Vec<F>,
54 ) -> Result<Vec<F>, Error> {
55 let mut b = f_vec_to_bytes(z_i.to_vec());
56
57 for _ in 0..HASHES_PER_STEP {
58 let mut sha256 = Sha256::default();
59 sha256.update(b);
60 b = sha256.finalize().to_vec();
61 }
62
63 bytes_to_f_vec(b.to_vec()) // z_{i+1}
64 }
65 fn generate_step_constraints(
66 &self,
67 _cs: ConstraintSystemRef<F>,
68 _i: usize,
69 z_i: Vec<FpVar<F>>,
70 _external_inputs: Vec<FpVar<F>>,
71 ) -> Result<Vec<FpVar<F>>, SynthesisError> {
72 let mut b: Vec<UInt8<F>> = z_i
73 .iter()
74 .map(|f| UInt8::<F>::from_bits_le(&f.to_bits_le().unwrap()[..8]))
75 .collect::<Vec<_>>();
76
77 for _ in 0..HASHES_PER_STEP {
78 let mut sha256_var = Sha256Gadget::default();
79 sha256_var.update(&b).unwrap();
80 b = sha256_var.finalize()?.to_bytes()?;
81 }
82
83 let z_i1: Vec<FpVar<F>> = b
84 .iter()
85 .map(|e| {
86 let bits = e.to_bits_le().unwrap();
87 Boolean::<F>::le_bits_to_fp_var(&bits).unwrap()
88 })
89 .collect();
90
91 Ok(z_i1)
92 }
93 }
94
95 #[test]
96 fn full_flow() {
97 // set how many steps of folding we want to compute
98 const N_STEPS: usize = 5;
99 const HASHES_PER_STEP: usize = 20;
100 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);
101
102 // set the initial state
103 // let z_0_aux: Vec<u32> = vec![0_u32; 32 * 8];
104 let z_0_aux: Vec<u8> = vec![0_u8; 32];
105 let z_0: Vec<Fr> = z_0_aux.iter().map(|v| Fr::from(*v)).collect::<Vec<Fr>>();
106
107 let f_circuit = SHA256FoldStepCircuit::<Fr, HASHES_PER_STEP>::new(()).unwrap();
108
109 // ----------------
110 // Sanity check
111 // check that the f_circuit produces valid R1CS constraints
112 use ark_r1cs_std::alloc::AllocVar;
113 use ark_r1cs_std::fields::fp::FpVar;
114 use ark_r1cs_std::R1CSVar;
115 use ark_relations::r1cs::ConstraintSystem;
116 let cs = ConstraintSystem::<Fr>::new_ref();
117 let z_0_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap();
118 let z_1_var = f_circuit
119 .generate_step_constraints(cs.clone(), 1, z_0_var, vec![])
120 .unwrap();
121 // check z_1_var against the native z_1
122 let z_1_native = f_circuit.step_native(1, z_0.clone(), vec![]).unwrap();
123 assert_eq!(z_1_var.value().unwrap(), z_1_native);
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 Pedersen<G1>,
141 Pedersen<G2>,
142 false,
143 >;
144
145 let poseidon_config = poseidon_canonical_config::<Fr>();
146 let mut rng = rand::rngs::OsRng;
147
148 // prepare the Nova prover & verifier params
149 let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit);
150 let start = Instant::now();
151 let nova_params = FS::preprocess(&mut rng, &nova_preprocess_params).unwrap();
152 println!("Nova params generated: {:?}", start.elapsed());
153
154 // initialize the folding scheme engine, in our case we use Nova
155 let mut nova = FS::init(&nova_params, f_circuit, z_0.clone()).unwrap();
156
157 // run n steps of the folding iteration
158 let start_full = Instant::now();
159 for _ in 0..N_STEPS {
160 let start = Instant::now();
161 nova.prove_step(rng, vec![], None).unwrap();
162 println!(
163 "Nova::prove_step (sha256) {}: {:?}",
164 nova.i,
165 start.elapsed()
166 );
167 }
168 println!(
169 "Nova's all {} steps time: {:?}",
170 N_STEPS,
171 start_full.elapsed()
172 );
173
174 // verify the last IVC proof
175 let ivc_proof = nova.ivc_proof();
176 FS::verify(
177 nova_params.1.clone(), // Nova's verifier params
178 ivc_proof,
179 )
180 .unwrap();
181 }
182}