sonobe/examples/external_inputs.rs
examples/external_inputs.rsRaw
1#![allow(non_snake_case)]
2#![allow(non_upper_case_globals)]
3#![allow(non_camel_case_types)]
4#![allow(clippy::upper_case_acronyms)]
5
6use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
7use ark_crypto_primitives::{
8 crh::{
9 poseidon::constraints::{CRHGadget, CRHParametersVar},
10 poseidon::CRH,
11 CRHScheme, CRHSchemeGadget,
12 },
13 sponge::{poseidon::PoseidonConfig, Absorb},
14};
15use ark_ff::PrimeField;
16use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
17use ark_r1cs_std::alloc::AllocVar;
18use ark_r1cs_std::fields::fp::FpVar;
19use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
20use core::marker::PhantomData;
21use std::time::Instant;
22
23use folding_schemes::commitment::{kzg::KZG, pedersen::Pedersen};
24use folding_schemes::folding::nova::Nova;
25use folding_schemes::frontend::FCircuit;
26use folding_schemes::{Error, FoldingScheme};
27mod utils;
28use folding_schemes::transcript::poseidon::poseidon_test_config;
29use utils::init_nova_ivc_params;
30
31/// This is the circuit that we want to fold, it implements the FCircuit trait. The parameter z_i
32/// denotes the current state which contains 1 element, and z_{i+1} denotes the next state which we
33/// get by applying the step.
34///
35/// In this example we set the state to be the previous state together with an external input, and
36/// the new state is an array which contains the new state.
37///
38/// This is useful for example if we want to fold multiple verifications of signatures, where the
39/// circuit F checks the signature and is folded for each of the signatures and public keys. To
40/// keep things simpler, the following example does not verify signatures but does a similar
41/// approach with a chain of hashes, where each iteration hashes the previous step output (z_i)
42/// together with an external input (w_i).
43///
44/// w_1 w_2 w_3 w_4
45/// │ │ │ │
46/// ▼ ▼ ▼ ▼
47/// ┌─┐ ┌─┐ ┌─┐ ┌─┐
48/// ─────►│F├────►│F├────►│F├────►│F├────►
49/// z_1 └─┘ z_2 └─┘ z_3 └─┘ z_4 └─┘ z_5
50///
51///
52/// where each F is:
53/// w_i
54/// │ ┌────────────────────┐
55/// │ │FCircuit │
56/// │ │ │
57/// └────►│ h =Hash(z_i[0],w_i)│
58/// ────────►│ │ ├───────►
59/// z_i │ └──►z_{i+1}=[h] │ z_{i+1}
60/// │ │
61/// └────────────────────┘
62///
63/// where each w_i value is set at the external_inputs array.
64///
65/// The last state z_i is used together with the external input w_i as inputs to compute the new
66/// state z_{i+1}.
67#[derive(Clone, Debug)]
68pub struct ExternalInputsCircuits<F: PrimeField>
69where
70 F: Absorb,
71{
72 _f: PhantomData<F>,
73 poseidon_config: PoseidonConfig<F>,
74}
75impl<F: PrimeField> FCircuit<F> for ExternalInputsCircuits<F>
76where
77 F: Absorb,
78{
79 type Params = PoseidonConfig<F>;
80
81 fn new(params: Self::Params) -> Result<Self, Error> {
82 Ok(Self {
83 _f: PhantomData,
84 poseidon_config: params,
85 })
86 }
87 fn state_len(&self) -> usize {
88 1
89 }
90 fn external_inputs_len(&self) -> usize {
91 1
92 }
93
94 /// computes the next state value for the step of F for the given z_i and external_inputs
95 /// z_{i+1}
96 fn step_native(
97 &self,
98 _i: usize,
99 z_i: Vec<F>,
100 external_inputs: Vec<F>,
101 ) -> Result<Vec<F>, Error> {
102 let hash_input: [F; 2] = [z_i[0], external_inputs[0]];
103 let h = CRH::<F>::evaluate(&self.poseidon_config, hash_input).unwrap();
104 Ok(vec![h])
105 }
106
107 /// generates the constraints and returns the next state value for the step of F for the given
108 /// z_i and external_inputs
109 fn generate_step_constraints(
110 &self,
111 cs: ConstraintSystemRef<F>,
112 _i: usize,
113 z_i: Vec<FpVar<F>>,
114 external_inputs: Vec<FpVar<F>>,
115 ) -> Result<Vec<FpVar<F>>, SynthesisError> {
116 let crh_params =
117 CRHParametersVar::<F>::new_constant(cs.clone(), self.poseidon_config.clone())?;
118 let hash_input: [FpVar<F>; 2] = [z_i[0].clone(), external_inputs[0].clone()];
119 let h = CRHGadget::<F>::evaluate(&crh_params, &hash_input)?;
120 Ok(vec![h])
121 }
122}
123
124/// cargo test --example external_inputs
125#[cfg(test)]
126pub mod tests {
127 use super::*;
128 use ark_r1cs_std::R1CSVar;
129 use ark_relations::r1cs::ConstraintSystem;
130
131 // test to check that the ExternalInputsCircuits computes the same values inside and outside the circuit
132 #[test]
133 fn test_f_circuit() {
134 let poseidon_config = poseidon_test_config::<Fr>();
135
136 let cs = ConstraintSystem::<Fr>::new_ref();
137
138 let circuit = ExternalInputsCircuits::<Fr>::new(poseidon_config).unwrap();
139 let z_i = vec![Fr::from(1_u32)];
140 let external_inputs = vec![Fr::from(3_u32)];
141
142 let z_i1 = circuit
143 .step_native(0, z_i.clone(), external_inputs.clone())
144 .unwrap();
145
146 let z_iVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
147 let external_inputsVar =
148 Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(external_inputs)).unwrap();
149
150 let computed_z_i1Var = circuit
151 .generate_step_constraints(cs.clone(), 0, z_iVar, external_inputsVar)
152 .unwrap();
153 assert_eq!(computed_z_i1Var.value().unwrap(), z_i1);
154 }
155}
156
157/// cargo run --release --example external_inputs
158fn main() {
159 let num_steps = 5;
160 let initial_state = vec![Fr::from(1_u32)];
161
162 // prepare the external inputs to be used at each folding step
163 let external_inputs = vec![
164 vec![Fr::from(3_u32)],
165 vec![Fr::from(33_u32)],
166 vec![Fr::from(73_u32)],
167 vec![Fr::from(103_u32)],
168 vec![Fr::from(125_u32)],
169 ];
170 assert_eq!(external_inputs.len(), num_steps);
171
172 let poseidon_config = poseidon_test_config::<Fr>();
173 let F_circuit = ExternalInputsCircuits::<Fr>::new(poseidon_config).unwrap();
174
175 println!("Prepare Nova ProverParams & VerifierParams");
176 let (prover_params, verifier_params, _) =
177 init_nova_ivc_params::<ExternalInputsCircuits<Fr>>(F_circuit.clone());
178
179 /// The idea here is that eventually we could replace the next line chunk that defines the
180 /// `type NOVA = Nova<...>` by using another folding scheme that fulfills the `FoldingScheme`
181 /// trait, and the rest of our code would be working without needing to be updated.
182 type NOVA = Nova<
183 Projective,
184 GVar,
185 Projective2,
186 GVar2,
187 ExternalInputsCircuits<Fr>,
188 KZG<'static, Bn254>,
189 Pedersen<Projective2>,
190 >;
191
192 println!("Initialize FoldingScheme");
193 let mut folding_scheme = NOVA::init(&prover_params, F_circuit, initial_state.clone()).unwrap();
194
195 // compute a step of the IVC
196 for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {
197 let start = Instant::now();
198 folding_scheme
199 .prove_step(external_inputs_at_step.clone())
200 .unwrap();
201 println!("Nova::prove_step {}: {:?}", i, start.elapsed());
202 }
203 println!(
204 "state at last step (after {} iterations): {:?}",
205 num_steps,
206 folding_scheme.state()
207 );
208
209 let (running_instance, incoming_instance, cyclefold_instance) = folding_scheme.instances();
210
211 println!("Run the Nova's IVC verifier");
212 NOVA::verify(
213 verifier_params,
214 initial_state.clone(),
215 folding_scheme.state(), // latest state
216 Fr::from(num_steps as u32),
217 running_instance,
218 incoming_instance,
219 cyclefold_instance,
220 )
221 .unwrap();
222}