diff --git a/Cargo.toml b/Cargo.toml index 89994ef..2b004fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ thiserror = "1.0" halo2curves = { version = "0.4.0", features = ["derive_serde"] } group = "0.13.0" once_cell = "1.18.0" +circom-scotia = "0.1.2" [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] pasta-msm = { version = "0.1.4" } diff --git a/examples/cube/cube.circom b/examples/cube/cube.circom new file mode 100644 index 0000000..ddc2639 --- /dev/null +++ b/examples/cube/cube.circom @@ -0,0 +1,10 @@ +pragma circom 2.0.6; + +template cube() { + signal input x; + signal input y; + signal x_sq <== x * x; + y === x_sq * x; +} + +component main { public [x, y] } = cube(); \ No newline at end of file diff --git a/examples/cube/cube.r1cs b/examples/cube/cube.r1cs new file mode 100644 index 0000000..843e421 Binary files /dev/null and b/examples/cube/cube.r1cs differ diff --git a/examples/cube/cube.wasm b/examples/cube/cube.wasm new file mode 100644 index 0000000..ae0dbb9 Binary files /dev/null and b/examples/cube/cube.wasm differ diff --git a/src/circom/mod.rs b/src/circom/mod.rs new file mode 100644 index 0000000..a497d39 --- /dev/null +++ b/src/circom/mod.rs @@ -0,0 +1,125 @@ +//! This module provides an interface to generate spartan proofs with circom circuits. +use std::path::PathBuf; + +use crate::{ + errors::SpartanError, + traits::{self, snark::RelaxedR1CSSNARKTrait, Group}, + ProverKey, VerifierKey, SNARK, +}; + +use bellpepper_core::{Circuit, ConstraintSystem, SynthesisError}; +use ff::PrimeField; + +use circom_scotia::{r1cs::R1CS, reader::load_r1cs, witness::WitnessCalculator}; + +/// Wrapper around an R1CS object to implement the Circuit trait +#[derive(Clone, Debug)] +pub struct SpartanCircuit { + r1cs: R1CS, + witness: Option>, // this is [1 || inputs || witnesses] +} + +impl SpartanCircuit { + fn new(r1cs_path: PathBuf) -> Self { + SpartanCircuit { + r1cs: load_r1cs(r1cs_path), + witness: None, + } + } + + fn compute_witness(&mut self, input: Vec<(String, Vec)>, wtns_path: PathBuf) { + let mut witness_calculator = WitnessCalculator::new(wtns_path).unwrap(); + let witness = witness_calculator + .calculate_witness(input.clone(), true) + .expect("msg"); + + self.witness = Some(witness); + } +} + +impl Circuit for SpartanCircuit { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { + let _ = circom_scotia::synthesize(cs, self.r1cs.clone(), self.witness).unwrap(); + + Ok(()) + } +} + +/// Produces prover and verifier keys +pub fn setup>( + r1cs_path: PathBuf, +) -> (ProverKey, VerifierKey) { + SNARK::::Scalar>>::setup(SpartanCircuit::new(r1cs_path)) + .unwrap() +} + +/// Produces proof in the form of a SNARK object +pub fn prove>( + pk: ProverKey, + r1cs_path: PathBuf, + wtns_path: PathBuf, + input: Vec<(String, Vec<::Scalar>)>, +) -> Result::Scalar>>, SpartanError> { + let mut circuit = SpartanCircuit::new(r1cs_path); + circuit.compute_witness(input, wtns_path); + SNARK::prove(&pk, circuit.clone()) +} + +#[cfg(test)] +mod test { + use super::{prove, setup}; + use crate::{provider::bn256_grumpkin::bn256, traits::Group}; + use std::env::current_dir; + + #[test] + fn test_spartan_snark() { + type G = bn256::Point; + type EE = crate::provider::ipa_pc::EvaluationEngine; + type S = crate::spartan::snark::RelaxedR1CSSNARK; + + let root = current_dir().unwrap().join("examples/cube"); + let r1cs_path = root.join("cube.r1cs"); + let wtns_path = root.join("cube.wasm"); + + let arg_x = ("x".into(), vec![::Scalar::from(2)]); + let arg_y = ("y".into(), vec![::Scalar::from(8)]); + let input = vec![arg_x, arg_y]; + + let io: Vec<::Scalar> = input.iter().flat_map(|v| v.1.iter().cloned()).collect(); + + let (pk, vk) = setup::(r1cs_path.clone()); + + let res = prove::(pk, r1cs_path, wtns_path, input); + assert!(res.is_ok()); + + let snark = res.unwrap(); + assert!(snark.verify(&vk, &io).is_ok()); + } + + #[test] + fn test_spartan_snark_fail() { + type G = bn256::Point; + type EE = crate::provider::ipa_pc::EvaluationEngine; + type S = crate::spartan::snark::RelaxedR1CSSNARK; + + let root = current_dir().unwrap().join("examples/cube"); + let r1cs_path = root.join("cube.r1cs"); + let wtns_path = root.join("cube.wasm"); + + let (pk, vk) = setup::(r1cs_path.clone()); + + // setting y to 9 shouldn't satisfy + let arg_x = ("x".into(), vec![::Scalar::from(2)]); + let arg_y = ("y".into(), vec![::Scalar::from(9)]); + let input = vec![arg_x, arg_y]; + + let io: Vec<::Scalar> = input.iter().flat_map(|v| v.1.iter().cloned()).collect(); + + let res = prove::(pk, r1cs_path, wtns_path, input); + assert!(res.is_ok()); + + let snark = res.unwrap(); + // check that it fails + assert!(snark.verify(&vk, &io).is_err()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 5a60502..d4dacb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ mod digest; mod r1cs; // public modules +pub mod circom; pub mod errors; pub mod provider; pub mod spartan;