arnaucube
arnaucube/NovaPublic Self-hosted repository
Nova/src/spartan/direct.rs
src/spartan/direct.rsRaw
  1//! This module provides interfaces to directly prove a step circuit by using Spartan SNARK.
  2//! In particular, it supports any SNARK that implements RelaxedR1CSSNARK trait
  3//! (e.g., with the SNARKs implemented in ppsnark.rs or snark.rs).
  4use crate::{
  5  bellperson::{
  6    r1cs::{NovaShape, NovaWitness},
  7    shape_cs::ShapeCS,
  8    solver::SatisfyingAssignment,
  9  },
 10  errors::NovaError,
 11  r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness},
 12  traits::{circuit::StepCircuit, snark::RelaxedR1CSSNARKTrait, Group},
 13  Commitment, CommitmentKey,
 14};
 15use bellperson::{gadgets::num::AllocatedNum, Circuit, ConstraintSystem, SynthesisError};
 16use core::marker::PhantomData;
 17use ff::Field;
 18use serde::{Deserialize, Serialize};
 19
 20struct DirectCircuit<G: Group, SC: StepCircuit<G::Scalar>> {
 21  z_i: Option<Vec<G::Scalar>>, // inputs to the circuit
 22  sc: SC,                      // step circuit to be executed
 23}
 24
 25impl<G: Group, SC: StepCircuit<G::Scalar>> Circuit<G::Scalar> for DirectCircuit<G, SC> {
 26  fn synthesize<CS: ConstraintSystem<G::Scalar>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
 27    // obtain the arity information
 28    let arity = self.sc.arity();
 29
 30    // Allocate zi. If inputs.zi is not provided, allocate default value 0
 31    let zero = vec![G::Scalar::ZERO; arity];
 32    let z_i = (0..arity)
 33      .map(|i| {
 34        AllocatedNum::alloc(cs.namespace(|| format!("zi_{i}")), || {
 35          Ok(self.z_i.as_ref().unwrap_or(&zero)[i])
 36        })
 37      })
 38      .collect::<Result<Vec<AllocatedNum<G::Scalar>>, _>>()?;
 39
 40    let z_i_plus_one = self.sc.synthesize(&mut cs.namespace(|| "F"), &z_i)?;
 41
 42    // inputize both z_i and z_i_plus_one
 43    for (j, input) in z_i.iter().enumerate().take(arity) {
 44      let _ = input.inputize(cs.namespace(|| format!("input {j}")));
 45    }
 46    for (j, output) in z_i_plus_one.iter().enumerate().take(arity) {
 47      let _ = output.inputize(cs.namespace(|| format!("output {j}")));
 48    }
 49
 50    Ok(())
 51  }
 52}
 53
 54/// A type that holds the prover key
 55#[derive(Clone, Serialize, Deserialize)]
 56#[serde(bound = "")]
 57pub struct ProverKey<G, S>
 58where
 59  G: Group,
 60  S: RelaxedR1CSSNARKTrait<G>,
 61{
 62  S: R1CSShape<G>,
 63  ck: CommitmentKey<G>,
 64  pk: S::ProverKey,
 65}
 66
 67/// A type that holds the verifier key
 68#[derive(Clone, Serialize, Deserialize)]
 69#[serde(bound = "")]
 70pub struct VerifierKey<G, S>
 71where
 72  G: Group,
 73  S: RelaxedR1CSSNARKTrait<G>,
 74{
 75  vk: S::VerifierKey,
 76}
 77
 78/// A direct SNARK proving a step circuit
 79#[derive(Clone, Serialize, Deserialize)]
 80#[serde(bound = "")]
 81pub struct DirectSNARK<G, S, C>
 82where
 83  G: Group,
 84  S: RelaxedR1CSSNARKTrait<G>,
 85  C: StepCircuit<G::Scalar>,
 86{
 87  comm_W: Commitment<G>, // commitment to the witness
 88  snark: S,              // snark proving the witness is satisfying
 89  _p: PhantomData<C>,
 90}
 91
 92impl<G: Group, S: RelaxedR1CSSNARKTrait<G>, C: StepCircuit<G::Scalar>> DirectSNARK<G, S, C> {
 93  /// Produces prover and verifier keys for the direct SNARK
 94  pub fn setup(sc: C) -> Result<(ProverKey<G, S>, VerifierKey<G, S>), NovaError> {
 95    // construct a circuit that can be synthesized
 96    let circuit: DirectCircuit<G, C> = DirectCircuit { z_i: None, sc };
 97
 98    let mut cs: ShapeCS<G> = ShapeCS::new();
 99    let _ = circuit.synthesize(&mut cs);
100    let (shape, ck) = cs.r1cs_shape();
101
102    let (pk, vk) = S::setup(&ck, &shape)?;
103
104    let pk = ProverKey { S: shape, ck, pk };
105
106    let vk = VerifierKey { vk };
107
108    Ok((pk, vk))
109  }
110
111  /// Produces a proof of satisfiability of the provided circuit
112  pub fn prove(pk: &ProverKey<G, S>, sc: C, z_i: &[G::Scalar]) -> Result<Self, NovaError> {
113    let mut cs: SatisfyingAssignment<G> = SatisfyingAssignment::new();
114
115    let circuit: DirectCircuit<G, C> = DirectCircuit {
116      z_i: Some(z_i.to_vec()),
117      sc,
118    };
119
120    let _ = circuit.synthesize(&mut cs);
121    let (u, w) = cs
122      .r1cs_instance_and_witness(&pk.S, &pk.ck)
123      .map_err(|_e| NovaError::UnSat)?;
124
125    // convert the instance and witness to relaxed form
126    let (u_relaxed, w_relaxed) = (
127      RelaxedR1CSInstance::from_r1cs_instance_unchecked(&u.comm_W, &u.X),
128      RelaxedR1CSWitness::from_r1cs_witness(&pk.S, &w),
129    );
130
131    // prove the instance using Spartan
132    let snark = S::prove(&pk.ck, &pk.pk, &u_relaxed, &w_relaxed)?;
133
134    Ok(DirectSNARK {
135      comm_W: u.comm_W,
136      snark,
137      _p: Default::default(),
138    })
139  }
140
141  /// Verifies a proof of satisfiability
142  pub fn verify(&self, vk: &VerifierKey<G, S>, io: &[G::Scalar]) -> Result<(), NovaError> {
143    // construct an instance using the provided commitment to the witness and z_i and z_{i+1}
144    let u_relaxed = RelaxedR1CSInstance::from_r1cs_instance_unchecked(&self.comm_W, io);
145
146    // verify the snark using the constructed instance
147    self.snark.verify(&vk.vk, &u_relaxed)?;
148
149    Ok(())
150  }
151}
152
153#[cfg(test)]
154mod tests {
155  use super::*;
156  use crate::provider::bn256_grumpkin::bn256;
157  use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
158  use core::marker::PhantomData;
159  use ff::PrimeField;
160
161  #[derive(Clone, Debug, Default)]
162  struct CubicCircuit<F: PrimeField> {
163    _p: PhantomData<F>,
164  }
165
166  impl<F> StepCircuit<F> for CubicCircuit<F>
167  where
168    F: PrimeField,
169  {
170    fn arity(&self) -> usize {
171      1
172    }
173
174    fn synthesize<CS: ConstraintSystem<F>>(
175      &self,
176      cs: &mut CS,
177      z: &[AllocatedNum<F>],
178    ) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
179      // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output.
180      let x = &z[0];
181      let x_sq = x.square(cs.namespace(|| "x_sq"))?;
182      let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), x)?;
183      let y = AllocatedNum::alloc(cs.namespace(|| "y"), || {
184        Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64))
185      })?;
186
187      cs.enforce(
188        || "y = x^3 + x + 5",
189        |lc| {
190          lc + x_cu.get_variable()
191            + x.get_variable()
192            + CS::one()
193            + CS::one()
194            + CS::one()
195            + CS::one()
196            + CS::one()
197        },
198        |lc| lc + CS::one(),
199        |lc| lc + y.get_variable(),
200      );
201
202      Ok(vec![y])
203    }
204
205    fn output(&self, z: &[F]) -> Vec<F> {
206      vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)]
207    }
208  }
209
210  #[test]
211  fn test_direct_snark() {
212    type G = pasta_curves::pallas::Point;
213    type EE = crate::provider::ipa_pc::EvaluationEngine<G>;
214    type S = crate::spartan::snark::RelaxedR1CSSNARK<G, EE>;
215    type Spp = crate::spartan::ppsnark::RelaxedR1CSSNARK<G, EE>;
216    test_direct_snark_with::<G, S>();
217    test_direct_snark_with::<G, Spp>();
218
219    type G2 = bn256::Point;
220    type EE2 = crate::provider::ipa_pc::EvaluationEngine<G2>;
221    type S2 = crate::spartan::snark::RelaxedR1CSSNARK<G2, EE2>;
222    type S2pp = crate::spartan::ppsnark::RelaxedR1CSSNARK<G2, EE2>;
223    test_direct_snark_with::<G2, S2>();
224    test_direct_snark_with::<G2, S2pp>();
225  }
226
227  fn test_direct_snark_with<G: Group, S: RelaxedR1CSSNARKTrait<G>>() {
228    let circuit = CubicCircuit::default();
229
230    // produce keys
231    let (pk, vk) =
232      DirectSNARK::<G, S, CubicCircuit<<G as Group>::Scalar>>::setup(circuit.clone()).unwrap();
233
234    let num_steps = 3;
235
236    // setup inputs
237    let z0 = vec![<G as Group>::Scalar::ZERO];
238    let mut z_i = z0;
239
240    for _i in 0..num_steps {
241      // produce a SNARK
242      let res = DirectSNARK::prove(&pk, circuit.clone(), &z_i);
243      assert!(res.is_ok());
244
245      let z_i_plus_one = circuit.output(&z_i);
246
247      let snark = res.unwrap();
248
249      // verify the SNARK
250      let io = z_i
251        .clone()
252        .into_iter()
253        .chain(z_i_plus_one.clone().into_iter())
254        .collect::<Vec<_>>();
255      let res = snark.verify(&vk, &io);
256      assert!(res.is_ok());
257
258      // set input to the next step
259      z_i = z_i_plus_one.clone();
260    }
261
262    // sanity: check the claimed output with a direct computation of the same
263    assert_eq!(z_i, vec![<G as Group>::Scalar::from(2460515u64)]);
264  }
265}