//! This module provides interfaces to directly prove a step circuit by using Spartan SNARK. //! In particular, it supports any SNARK that implements RelaxedR1CSSNARK trait //! (e.g., with the SNARKs implemented in ppsnark.rs or snark.rs). use crate::{ bellperson::{ r1cs::{NovaShape, NovaWitness}, shape_cs::ShapeCS, solver::SatisfyingAssignment, }, errors::NovaError, r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}, traits::{circuit::StepCircuit, snark::RelaxedR1CSSNARKTrait, Group}, Commitment, CommitmentKey, }; use bellperson::{gadgets::num::AllocatedNum, Circuit, ConstraintSystem, SynthesisError}; use core::marker::PhantomData; use ff::Field; use serde::{Deserialize, Serialize}; struct DirectCircuit> { z_i: Option>, // inputs to the circuit sc: SC, // step circuit to be executed } impl> Circuit for DirectCircuit { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { // obtain the arity information let arity = self.sc.arity(); // Allocate zi. If inputs.zi is not provided, allocate default value 0 let zero = vec![G::Scalar::ZERO; arity]; let z_i = (0..arity) .map(|i| { AllocatedNum::alloc(cs.namespace(|| format!("zi_{i}")), || { Ok(self.z_i.as_ref().unwrap_or(&zero)[i]) }) }) .collect::>, _>>()?; let z_i_plus_one = self.sc.synthesize(&mut cs.namespace(|| "F"), &z_i)?; // inputize both z_i and z_i_plus_one for (j, input) in z_i.iter().enumerate().take(arity) { let _ = input.inputize(cs.namespace(|| format!("input {j}"))); } for (j, output) in z_i_plus_one.iter().enumerate().take(arity) { let _ = output.inputize(cs.namespace(|| format!("output {j}"))); } Ok(()) } } /// A type that holds the prover key #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] pub struct ProverKey where G: Group, S: RelaxedR1CSSNARKTrait, { S: R1CSShape, ck: CommitmentKey, pk: S::ProverKey, } /// A type that holds the verifier key #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] pub struct VerifierKey where G: Group, S: RelaxedR1CSSNARKTrait, { vk: S::VerifierKey, } /// A direct SNARK proving a step circuit #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] pub struct DirectSNARK where G: Group, S: RelaxedR1CSSNARKTrait, C: StepCircuit, { comm_W: Commitment, // commitment to the witness snark: S, // snark proving the witness is satisfying _p: PhantomData, } impl, C: StepCircuit> DirectSNARK { /// Produces prover and verifier keys for the direct SNARK pub fn setup(sc: C) -> Result<(ProverKey, VerifierKey), NovaError> { // construct a circuit that can be synthesized let circuit: DirectCircuit = DirectCircuit { z_i: None, sc }; let mut cs: ShapeCS = ShapeCS::new(); let _ = circuit.synthesize(&mut cs); let (shape, ck) = cs.r1cs_shape(); let (pk, vk) = S::setup(&ck, &shape)?; let pk = ProverKey { S: shape, ck, pk }; let vk = VerifierKey { vk }; Ok((pk, vk)) } /// Produces a proof of satisfiability of the provided circuit pub fn prove(pk: &ProverKey, sc: C, z_i: &[G::Scalar]) -> Result { let mut cs: SatisfyingAssignment = SatisfyingAssignment::new(); let circuit: DirectCircuit = DirectCircuit { z_i: Some(z_i.to_vec()), sc, }; let _ = circuit.synthesize(&mut cs); let (u, w) = cs .r1cs_instance_and_witness(&pk.S, &pk.ck) .map_err(|_e| NovaError::UnSat)?; // convert the instance and witness to relaxed form let (u_relaxed, w_relaxed) = ( RelaxedR1CSInstance::from_r1cs_instance_unchecked(&u.comm_W, &u.X), RelaxedR1CSWitness::from_r1cs_witness(&pk.S, &w), ); // prove the instance using Spartan let snark = S::prove(&pk.ck, &pk.pk, &u_relaxed, &w_relaxed)?; Ok(DirectSNARK { comm_W: u.comm_W, snark, _p: Default::default(), }) } /// Verifies a proof of satisfiability pub fn verify(&self, vk: &VerifierKey, io: &[G::Scalar]) -> Result<(), NovaError> { // construct an instance using the provided commitment to the witness and z_i and z_{i+1} let u_relaxed = RelaxedR1CSInstance::from_r1cs_instance_unchecked(&self.comm_W, io); // verify the snark using the constructed instance self.snark.verify(&vk.vk, &u_relaxed)?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use crate::provider::bn256_grumpkin::bn256; use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError}; use core::marker::PhantomData; use ff::PrimeField; #[derive(Clone, Debug, Default)] struct CubicCircuit { _p: PhantomData, } impl StepCircuit for CubicCircuit where F: PrimeField, { fn arity(&self) -> usize { 1 } fn synthesize>( &self, cs: &mut CS, z: &[AllocatedNum], ) -> Result>, SynthesisError> { // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output. let x = &z[0]; let x_sq = x.square(cs.namespace(|| "x_sq"))?; let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), x)?; let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64)) })?; cs.enforce( || "y = x^3 + x + 5", |lc| { lc + x_cu.get_variable() + x.get_variable() + CS::one() + CS::one() + CS::one() + CS::one() + CS::one() }, |lc| lc + CS::one(), |lc| lc + y.get_variable(), ); Ok(vec![y]) } fn output(&self, z: &[F]) -> Vec { vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)] } } #[test] fn test_direct_snark() { type G = pasta_curves::pallas::Point; type EE = crate::provider::ipa_pc::EvaluationEngine; type S = crate::spartan::snark::RelaxedR1CSSNARK; type Spp = crate::spartan::ppsnark::RelaxedR1CSSNARK; test_direct_snark_with::(); test_direct_snark_with::(); type G2 = bn256::Point; type EE2 = crate::provider::ipa_pc::EvaluationEngine; type S2 = crate::spartan::snark::RelaxedR1CSSNARK; type S2pp = crate::spartan::ppsnark::RelaxedR1CSSNARK; test_direct_snark_with::(); test_direct_snark_with::(); } fn test_direct_snark_with>() { let circuit = CubicCircuit::default(); // produce keys let (pk, vk) = DirectSNARK::::Scalar>>::setup(circuit.clone()).unwrap(); let num_steps = 3; // setup inputs let z0 = vec![::Scalar::ZERO]; let mut z_i = z0; for _i in 0..num_steps { // produce a SNARK let res = DirectSNARK::prove(&pk, circuit.clone(), &z_i); assert!(res.is_ok()); let z_i_plus_one = circuit.output(&z_i); let snark = res.unwrap(); // verify the SNARK let io = z_i .clone() .into_iter() .chain(z_i_plus_one.clone().into_iter()) .collect::>(); let res = snark.verify(&vk, &io); assert!(res.is_ok()); // set input to the next step z_i = z_i_plus_one.clone(); } // sanity: check the claimed output with a direct computation of the same assert_eq!(z_i, vec![::Scalar::from(2460515u64)]); } }