arnaucube
arnaucube/sonobePublic Self-hosted repository
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_canonical_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_canonical_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_canonical_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}