|
@ -1,6 +1,11 @@ |
|
|
//! This module implements RelaxedR1CSSNARK traits using a spark-based approach to prove evaluations of
|
|
|
//! This module implements RelaxedR1CSSNARK traits using a spark-based approach to prove evaluations of
|
|
|
//! sparse multilinear polynomials involved in Spartan's sum-check protocol, thereby providing a preprocessing SNARK
|
|
|
//! sparse multilinear polynomials involved in Spartan's sum-check protocol, thereby providing a preprocessing SNARK
|
|
|
use crate::{
|
|
|
use crate::{
|
|
|
|
|
|
bellperson::{
|
|
|
|
|
|
r1cs::{NovaShape, NovaWitness},
|
|
|
|
|
|
shape_cs::ShapeCS,
|
|
|
|
|
|
solver::SatisfyingAssignment,
|
|
|
|
|
|
},
|
|
|
errors::NovaError,
|
|
|
errors::NovaError,
|
|
|
r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness},
|
|
|
r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness},
|
|
|
spartan::{
|
|
|
spartan::{
|
|
@ -10,12 +15,13 @@ use crate::{ |
|
|
PolyEvalInstance, PolyEvalWitness,
|
|
|
PolyEvalInstance, PolyEvalWitness,
|
|
|
},
|
|
|
},
|
|
|
traits::{
|
|
|
traits::{
|
|
|
commitment::CommitmentEngineTrait, evaluation::EvaluationEngineTrait,
|
|
|
|
|
|
|
|
|
circuit::StepCircuit, commitment::CommitmentEngineTrait, evaluation::EvaluationEngineTrait,
|
|
|
snark::RelaxedR1CSSNARKTrait, Group, TranscriptEngineTrait, TranscriptReprTrait,
|
|
|
snark::RelaxedR1CSSNARKTrait, Group, TranscriptEngineTrait, TranscriptReprTrait,
|
|
|
},
|
|
|
},
|
|
|
Commitment, CommitmentKey,
|
|
|
Commitment, CommitmentKey,
|
|
|
};
|
|
|
};
|
|
|
use core::cmp::max;
|
|
|
|
|
|
|
|
|
use bellperson::{gadgets::num::AllocatedNum, Circuit, ConstraintSystem, SynthesisError};
|
|
|
|
|
|
use core::{cmp::max, marker::PhantomData};
|
|
|
use ff::Field;
|
|
|
use ff::Field;
|
|
|
use itertools::concat;
|
|
|
use itertools::concat;
|
|
|
use rayon::prelude::*;
|
|
|
use rayon::prelude::*;
|
|
@ -1315,3 +1321,252 @@ impl> RelaxedR1CSSNARKTrait |
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
// provides direct interfaces to call the SNARK implemented in this module
|
|
|
// provides direct interfaces to call the SNARK implemented in this module
|
|
|
|
|
|
struct SpartanCircuit<G: Group, SC: StepCircuit<G::Scalar>> {
|
|
|
|
|
|
z_i: Option<Vec<G::Scalar>>, // inputs to the circuit
|
|
|
|
|
|
sc: SC, // step circuit to be executed
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl<G: Group, SC: StepCircuit<G::Scalar>> Circuit<G::Scalar> for SpartanCircuit<G, SC> {
|
|
|
|
|
|
fn synthesize<CS: ConstraintSystem<G::Scalar>>(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::<Result<Vec<AllocatedNum<G::Scalar>>, _>>()?;
|
|
|
|
|
|
|
|
|
|
|
|
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 Spartan's prover key
|
|
|
|
|
|
pub struct SpartanProverKey<G, EE>
|
|
|
|
|
|
where
|
|
|
|
|
|
G: Group,
|
|
|
|
|
|
EE: EvaluationEngineTrait<G, CE = G::CE>,
|
|
|
|
|
|
{
|
|
|
|
|
|
F_arity: usize,
|
|
|
|
|
|
S: R1CSShape<G>,
|
|
|
|
|
|
ck: CommitmentKey<G>,
|
|
|
|
|
|
pk: ProverKey<G, EE>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A type that holds Spartan's verifier key
|
|
|
|
|
|
pub struct SpartanVerifierKey<G, EE>
|
|
|
|
|
|
where
|
|
|
|
|
|
G: Group,
|
|
|
|
|
|
EE: EvaluationEngineTrait<G, CE = G::CE>,
|
|
|
|
|
|
{
|
|
|
|
|
|
F_arity: usize,
|
|
|
|
|
|
vk: VerifierKey<G, EE>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A direct SNARK proving a step circuit
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
|
|
#[serde(bound = "")]
|
|
|
|
|
|
pub struct SpartanSNARK<G, EE, C>
|
|
|
|
|
|
where
|
|
|
|
|
|
G: Group,
|
|
|
|
|
|
EE: EvaluationEngineTrait<G, CE = G::CE>,
|
|
|
|
|
|
C: StepCircuit<G::Scalar>,
|
|
|
|
|
|
{
|
|
|
|
|
|
comm_W: Commitment<G>, // commitment to the witness
|
|
|
|
|
|
snark: RelaxedR1CSSNARK<G, EE>, // snark proving the witness is satisfying
|
|
|
|
|
|
_p: PhantomData<C>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, C: StepCircuit<G::Scalar>>
|
|
|
|
|
|
SpartanSNARK<G, EE, C>
|
|
|
|
|
|
{
|
|
|
|
|
|
/// Produces prover and verifier keys for Spartan
|
|
|
|
|
|
pub fn setup(sc: C) -> Result<(SpartanProverKey<G, EE>, SpartanVerifierKey<G, EE>), NovaError> {
|
|
|
|
|
|
let F_arity = sc.arity();
|
|
|
|
|
|
|
|
|
|
|
|
// construct a circuit that can be synthesized
|
|
|
|
|
|
let circuit: SpartanCircuit<G, C> = SpartanCircuit { z_i: None, sc };
|
|
|
|
|
|
|
|
|
|
|
|
let mut cs: ShapeCS<G> = ShapeCS::new();
|
|
|
|
|
|
let _ = circuit.synthesize(&mut cs);
|
|
|
|
|
|
let (S, ck) = cs.r1cs_shape();
|
|
|
|
|
|
|
|
|
|
|
|
let (pk, vk) = RelaxedR1CSSNARK::setup(&ck, &S)?;
|
|
|
|
|
|
|
|
|
|
|
|
let pk = SpartanProverKey { F_arity, S, ck, pk };
|
|
|
|
|
|
|
|
|
|
|
|
let vk = SpartanVerifierKey { F_arity, vk };
|
|
|
|
|
|
|
|
|
|
|
|
Ok((pk, vk))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Produces a proof of satisfiability of the provided circuit
|
|
|
|
|
|
pub fn prove(
|
|
|
|
|
|
pk: &SpartanProverKey<G, EE>,
|
|
|
|
|
|
sc: C,
|
|
|
|
|
|
z_i: Vec<G::Scalar>,
|
|
|
|
|
|
) -> Result<Self, NovaError> {
|
|
|
|
|
|
if z_i.len() != pk.F_arity || sc.output(&z_i).len() != pk.F_arity {
|
|
|
|
|
|
return Err(NovaError::InvalidInitialInputLength);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut cs: SatisfyingAssignment<G> = SatisfyingAssignment::new();
|
|
|
|
|
|
|
|
|
|
|
|
let circuit: SpartanCircuit<G, C> = SpartanCircuit { z_i: Some(z_i), 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 = RelaxedR1CSSNARK::prove(&pk.ck, &pk.pk, &u_relaxed, &w_relaxed)?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(SpartanSNARK {
|
|
|
|
|
|
comm_W: u.comm_W,
|
|
|
|
|
|
snark,
|
|
|
|
|
|
_p: Default::default(),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Verifies a proof of satisfiability
|
|
|
|
|
|
pub fn verify(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
vk: &SpartanVerifierKey<G, EE>,
|
|
|
|
|
|
z_i: Vec<G::Scalar>,
|
|
|
|
|
|
z_i_plus_one: Vec<G::Scalar>,
|
|
|
|
|
|
) -> Result<(), NovaError> {
|
|
|
|
|
|
// check if z_i and z_i_plus_one have lengths according to the provided arity
|
|
|
|
|
|
if z_i.len() != vk.F_arity || z_i_plus_one.len() != vk.F_arity {
|
|
|
|
|
|
return Err(NovaError::InvalidInitialInputLength);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
|
&z_i
|
|
|
|
|
|
.into_iter()
|
|
|
|
|
|
.chain(z_i_plus_one.into_iter())
|
|
|
|
|
|
.collect::<Vec<G::Scalar>>(),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// verify the snark using the constructed instance
|
|
|
|
|
|
self.snark.verify(&vk.vk, &u_relaxed)?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod tests {
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
type G = pasta_curves::pallas::Point;
|
|
|
|
|
|
type EE = crate::provider::ipa_pc::EvaluationEngine<G>;
|
|
|
|
|
|
use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
|
|
|
|
|
|
use core::marker::PhantomData;
|
|
|
|
|
|
use ff::PrimeField;
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
|
|
struct CubicCircuit<F: PrimeField> {
|
|
|
|
|
|
_p: PhantomData<F>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl<F> StepCircuit<F> for CubicCircuit<F>
|
|
|
|
|
|
where
|
|
|
|
|
|
F: PrimeField,
|
|
|
|
|
|
{
|
|
|
|
|
|
fn arity(&self) -> usize {
|
|
|
|
|
|
1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn synthesize<CS: ConstraintSystem<F>>(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
cs: &mut CS,
|
|
|
|
|
|
z: &[AllocatedNum<F>],
|
|
|
|
|
|
) -> Result<Vec<AllocatedNum<F>>, 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<F> {
|
|
|
|
|
|
vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_spartan_snark() {
|
|
|
|
|
|
let circuit = CubicCircuit::default();
|
|
|
|
|
|
|
|
|
|
|
|
// produce keys
|
|
|
|
|
|
let (pk, vk) =
|
|
|
|
|
|
SpartanSNARK::<G, EE, CubicCircuit<<G as Group>::Scalar>>::setup(circuit.clone()).unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
let num_steps = 3;
|
|
|
|
|
|
|
|
|
|
|
|
// setup inputs
|
|
|
|
|
|
let z0 = vec![<G as Group>::Scalar::zero()];
|
|
|
|
|
|
let mut z_i = z0;
|
|
|
|
|
|
|
|
|
|
|
|
for _i in 0..num_steps {
|
|
|
|
|
|
// produce a SNARK
|
|
|
|
|
|
let res = SpartanSNARK::prove(&pk, circuit.clone(), z_i.clone());
|
|
|
|
|
|
assert!(res.is_ok());
|
|
|
|
|
|
|
|
|
|
|
|
let z_i_plus_one = circuit.output(&z_i);
|
|
|
|
|
|
|
|
|
|
|
|
let snark = res.unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
// verify the SNARK
|
|
|
|
|
|
let res = snark.verify(&vk, z_i.clone(), z_i_plus_one.clone());
|
|
|
|
|
|
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![<G as Group>::Scalar::from(2460515u64)]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|