Merge pull request #2 from zcash/universal-circuits

Implementation of generalized PLONK
This commit is contained in:
ebfull 2020-08-27 15:21:44 -06:00 committed by GitHub
commit 4c9a05ba74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 946 additions and 397 deletions

View file

@ -313,6 +313,29 @@ pub fn compute_inner_product<F: Field>(a: &[F], b: &[F]) -> F {
acc acc
} }
/// Divides polynomial `a` in `X` by `X - b` with
/// no remainder.
pub fn kate_division<'a, F: Field, I: IntoIterator<Item = &'a F>>(a: I, mut b: F) -> Vec<F>
where
I::IntoIter: DoubleEndedIterator + ExactSizeIterator,
{
b = -b;
let a = a.into_iter();
let mut q = vec![F::zero(); a.len() - 1];
let mut tmp = F::zero();
for (q, r) in q.iter_mut().rev().zip(a.rev()) {
let mut lead_coeff = *r;
lead_coeff.sub_assign(&tmp);
*q = lead_coeff;
tmp = lead_coeff;
tmp.mul_assign(&b);
}
q
}
/// This simple utility function will parallelize an operation that is to be /// This simple utility function will parallelize an operation that is to be
/// performed over a mutable slice. /// performed over a mutable slice.
pub fn parallelize<T: Send, F: Fn(&mut [T], usize) + Send + Clone>(v: &mut [T], f: F) { pub fn parallelize<T: Send, F: Fn(&mut [T], usize) + Send + Clone>(v: &mut [T], f: F) {

View file

@ -23,45 +23,29 @@ pub use verifier::*;
use domain::EvaluationDomain; use domain::EvaluationDomain;
// TODO: remove this
const GATE_DEGREE: u32 = 3;
/// This is a structured reference string (SRS) that is (deterministically) /// This is a structured reference string (SRS) that is (deterministically)
/// computed from a specific circuit and parameters for the polynomial /// computed from a specific circuit and parameters for the polynomial
/// commitment scheme. /// commitment scheme.
#[derive(Debug)] #[derive(Debug)]
pub struct SRS<C: CurveAffine> { pub struct SRS<C: CurveAffine> {
sa: (Vec<C::Scalar>, Vec<C::Scalar>),
sb: (Vec<C::Scalar>, Vec<C::Scalar>),
sc: (Vec<C::Scalar>, Vec<C::Scalar>),
sd: (Vec<C::Scalar>, Vec<C::Scalar>),
sm: (Vec<C::Scalar>, Vec<C::Scalar>),
sa_commitment: C,
sb_commitment: C,
sc_commitment: C,
sd_commitment: C,
sm_commitment: C,
domain: EvaluationDomain<C::Scalar>, domain: EvaluationDomain<C::Scalar>,
fixed_commitments: Vec<C>,
fixed_polys: Vec<Vec<C::Scalar>>,
fixed_cosets: Vec<Vec<C::Scalar>>,
meta: MetaCircuit<C::Scalar>,
} }
/// This is an object which represents a (Turbo)PLONK proof. /// This is an object which represents a (Turbo)PLONK proof.
// This structure must never allow points at infinity.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Proof<C: CurveAffine> { pub struct Proof<C: CurveAffine> {
a_commitment: C, advice_commitments: Vec<C>,
b_commitment: C,
c_commitment: C,
d_commitment: C,
h_commitments: Vec<C>, h_commitments: Vec<C>,
a_eval_x: C::Scalar, advice_evals: Vec<C::Scalar>,
b_eval_x: C::Scalar, fixed_evals: Vec<C::Scalar>,
c_eval_x: C::Scalar, h_evals: Vec<C::Scalar>,
d_eval_x: C::Scalar, f_commitment: C,
sa_eval_x: C::Scalar, q_evals: Vec<C::Scalar>,
sb_eval_x: C::Scalar,
sc_eval_x: C::Scalar,
sd_eval_x: C::Scalar,
sm_eval_x: C::Scalar,
h_evals_x: Vec<C::Scalar>,
opening: OpeningProof<C>, opening: OpeningProof<C>,
} }
@ -77,6 +61,8 @@ pub enum Error {
IncompatibleParams, IncompatibleParams,
/// The constraint system is not satisfied. /// The constraint system is not satisfied.
ConstraintSystemFailure, ConstraintSystemFailure,
/// Out of bounds index passed to a backend
BoundsFailure,
} }
fn hash_point<C: CurveAffine, H: Hasher<C::Base>>( fn hash_point<C: CurveAffine, H: Hasher<C::Base>>(
@ -98,32 +84,186 @@ fn test_proving() {
use crate::arithmetic::{EqAffine, Field, Fp, Fq}; use crate::arithmetic::{EqAffine, Field, Fp, Fq};
use crate::polycommit::Params; use crate::polycommit::Params;
use crate::transcript::DummyHash; use crate::transcript::DummyHash;
use std::marker::PhantomData;
const K: u32 = 5; const K: u32 = 5;
// Initialize the polynomial commitment parameters // Initialize the polynomial commitment parameters
let params: Params<EqAffine> = Params::new::<DummyHash<Fq>>(K); let params: Params<EqAffine> = Params::new::<DummyHash<Fq>>(K);
struct PLONKConfig {
a: AdviceWire,
b: AdviceWire,
c: AdviceWire,
sa: FixedWire,
sb: FixedWire,
sc: FixedWire,
sm: FixedWire,
}
#[derive(Copy, Clone)]
struct Variable(AdviceWire, usize);
trait StandardCS<FF: Field> {
fn raw_multiply<F>(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error>
where
F: FnOnce() -> Result<(FF, FF, FF), Error>;
fn raw_add<F>(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error>
where
F: FnOnce() -> Result<(FF, FF, FF), Error>;
}
struct MyCircuit<F: Field> { struct MyCircuit<F: Field> {
a: Option<F>, a: Option<F>,
} }
struct StandardPLONK<'a, F: Field, CS: ConstraintSystem<F> + 'a> {
cs: &'a mut CS,
config: PLONKConfig,
current_gate: usize,
_marker: PhantomData<F>,
}
impl<'a, FF: Field, CS: ConstraintSystem<FF>> StandardPLONK<'a, FF, CS> {
fn new(cs: &'a mut CS, config: PLONKConfig) -> Self {
StandardPLONK {
cs,
config,
current_gate: 0,
_marker: PhantomData,
}
}
}
impl<'a, FF: Field, CS: ConstraintSystem<FF>> StandardCS<FF> for StandardPLONK<'a, FF, CS> {
fn raw_multiply<F>(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error>
where
F: FnOnce() -> Result<(FF, FF, FF), Error>,
{
let index = self.current_gate;
self.current_gate += 1;
let mut value = None;
self.cs.assign_advice(self.config.a, index, || {
value = Some(f()?);
Ok(value.ok_or(Error::SynthesisError)?.0)
})?;
self.cs.assign_advice(self.config.b, index, || {
Ok(value.ok_or(Error::SynthesisError)?.1)
})?;
self.cs.assign_advice(self.config.c, index, || {
Ok(value.ok_or(Error::SynthesisError)?.2)
})?;
self.cs
.assign_fixed(self.config.sa, index, || Ok(FF::zero()))?;
self.cs
.assign_fixed(self.config.sb, index, || Ok(FF::zero()))?;
self.cs
.assign_fixed(self.config.sc, index, || Ok(FF::one()))?;
self.cs
.assign_fixed(self.config.sm, index, || Ok(FF::one()))?;
Ok((
Variable(self.config.a, index),
Variable(self.config.b, index),
Variable(self.config.c, index),
))
}
fn raw_add<F>(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error>
where
F: FnOnce() -> Result<(FF, FF, FF), Error>,
{
let index = self.current_gate;
self.current_gate += 1;
let mut value = None;
self.cs.assign_advice(self.config.a, index, || {
value = Some(f()?);
Ok(value.ok_or(Error::SynthesisError)?.0)
})?;
self.cs.assign_advice(self.config.b, index, || {
Ok(value.ok_or(Error::SynthesisError)?.1)
})?;
self.cs.assign_advice(self.config.c, index, || {
Ok(value.ok_or(Error::SynthesisError)?.2)
})?;
self.cs
.assign_fixed(self.config.sa, index, || Ok(FF::one()))?;
self.cs
.assign_fixed(self.config.sb, index, || Ok(FF::one()))?;
self.cs
.assign_fixed(self.config.sc, index, || Ok(FF::one()))?;
self.cs
.assign_fixed(self.config.sm, index, || Ok(FF::zero()))?;
Ok((
Variable(self.config.a, index),
Variable(self.config.b, index),
Variable(self.config.c, index),
))
}
}
impl<F: Field> Circuit<F> for MyCircuit<F> { impl<F: Field> Circuit<F> for MyCircuit<F> {
fn synthesize(&self, cs: &mut impl ConstraintSystem<F>) -> Result<(), Error> { type Config = PLONKConfig;
fn configure(meta: &mut MetaCircuit<F>) -> PLONKConfig {
let a = meta.advice_wire();
let b = meta.advice_wire();
let c = meta.advice_wire();
let sa = meta.fixed_wire();
let sb = meta.fixed_wire();
let sc = meta.fixed_wire();
let sm = meta.fixed_wire();
meta.create_gate(|meta| {
let a = meta.query_advice(a, 0);
let b = meta.query_advice(b, 0);
let c = meta.query_advice(c, 0);
let sa = meta.query_fixed(sa, 0);
let sb = meta.query_fixed(sb, 0);
let sc = meta.query_fixed(sc, 0);
let sm = meta.query_fixed(sm, 0);
a.clone() * sa + b.clone() * sb + a * b * sm + (c * sc * (-F::one()))
});
PLONKConfig {
a,
b,
c,
sa,
sb,
sc,
sm,
}
}
fn synthesize(
&self,
cs: &mut impl ConstraintSystem<F>,
config: PLONKConfig,
) -> Result<(), Error> {
let mut cs = StandardPLONK::new(cs, config);
for _ in 0..10 { for _ in 0..10 {
let (_, _, _, _) = cs.multiply(|| { let mut a_squared = None;
let a = self.a.ok_or(Error::SynthesisError)?; let (_, _, _) = cs.raw_multiply(|| {
let a2 = a.square(); a_squared = self.a.map(|a| a.square());
Ok((a, a, a2)) Ok((
self.a.ok_or(Error::SynthesisError)?,
self.a.ok_or(Error::SynthesisError)?,
a_squared.ok_or(Error::SynthesisError)?,
))
})?; })?;
//cs.copy(a, b); let (_, _, _) = cs.raw_add(|| {
let (_, _, _, _) = cs.add(|| { let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2));
let a = self.a.ok_or(Error::SynthesisError)?; Ok((
let a2 = a.square(); self.a.ok_or(Error::SynthesisError)?,
let a3 = a + a2; a_squared.ok_or(Error::SynthesisError)?,
Ok((a, a2, a3)) fin.ok_or(Error::SynthesisError)?,
))
})?; })?;
//cs.copy(a, d);
//cs.copy(c, e);
} }
Ok(()) Ok(())
@ -134,8 +274,10 @@ fn test_proving() {
a: Some((-Fp::from_u64(2) + Fp::ROOT_OF_UNITY).pow(&[100, 0, 0, 0])), a: Some((-Fp::from_u64(2) + Fp::ROOT_OF_UNITY).pow(&[100, 0, 0, 0])),
}; };
let empty_circuit: MyCircuit<Fp> = MyCircuit { a: None };
// Initialize the SRS // Initialize the SRS
let srs = SRS::generate(&params, &circuit).expect("SRS generation should not fail"); let srs = SRS::generate(&params, &empty_circuit).expect("SRS generation should not fail");
// Create a proof // Create a proof
let proof = Proof::create::<DummyHash<Fq>, DummyHash<Fp>, _>(&params, &srs, &circuit) let proof = Proof::create::<DummyHash<Fq>, DummyHash<Fp>, _>(&params, &srs, &circuit)

View file

@ -1,56 +1,37 @@
use super::Error; use core::cmp::max;
use core::ops::{Add, Mul};
use std::collections::HashMap;
use super::Error;
use crate::arithmetic::Field; use crate::arithmetic::Field;
/// This represents a PLONK wire, which could be a fixed (selector) wire or an use super::domain::Rotation;
/// advice wire. /// This represents a wire which has a fixed (permanent) value
#[derive(Debug)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Wire { pub struct FixedWire(pub usize);
/// A wires
A(usize), /// This represents a wire which has a witness-specific value
/// B wires #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
B(usize), pub struct AdviceWire(pub usize);
/// C wires
C(usize),
/// D wires
D(usize),
}
/// This trait allows a [`Circuit`] to direct some backend to assign a witness /// This trait allows a [`Circuit`] to direct some backend to assign a witness
/// for a constraint system. /// for a constraint system.
pub trait ConstraintSystem<F: Field> { pub trait ConstraintSystem<F: Field> {
/// Creates a gate. /// Assign an advice wire value (witness)
fn create_gate( fn assign_advice(
&mut self, &mut self,
sa: F, wire: AdviceWire,
sb: F, row: usize,
sc: F, to: impl FnOnce() -> Result<F, Error>,
sd: F, ) -> Result<(), Error>;
sm: F,
f: impl Fn() -> Result<(F, F, F, F), Error>,
) -> Result<(Wire, Wire, Wire, Wire), Error>;
/// a * b - c = 0 /// Assign a fixed value
fn multiply( fn assign_fixed(
&mut self, &mut self,
f: impl Fn() -> Result<(F, F, F), Error>, wire: FixedWire,
) -> Result<(Wire, Wire, Wire, Wire), Error> { row: usize,
self.create_gate(F::zero(), F::zero(), F::one(), F::zero(), F::one(), || { to: impl FnOnce() -> Result<F, Error>,
let (a, b, c) = f()?; ) -> Result<(), Error>;
Ok((a, b, c, F::zero()))
})
}
/// a + b - c = 0
fn add(
&mut self,
f: impl Fn() -> Result<(F, F, F), Error>,
) -> Result<(Wire, Wire, Wire, Wire), Error> {
self.create_gate(F::one(), F::one(), F::one(), F::zero(), F::zero(), || {
let (a, b, c) = f()?;
Ok((a, b, c, F::zero()))
})
}
// fn copy(&mut self, left: Wire, right: Wire); // fn copy(&mut self, left: Wire, right: Wire);
} }
@ -59,8 +40,186 @@ pub trait ConstraintSystem<F: Field> {
/// backend prover can ask the circuit to synthesize using some given /// backend prover can ask the circuit to synthesize using some given
/// [`ConstraintSystem`] implementation. /// [`ConstraintSystem`] implementation.
pub trait Circuit<F: Field> { pub trait Circuit<F: Field> {
/// This is a configuration object that stores things like wires.
type Config;
/// The circuit is given an opportunity to describe the exact gate
/// arrangement, wire arrangement, etc.
fn configure(meta: &mut MetaCircuit<F>) -> Self::Config;
/// Given the provided `cs`, synthesize the circuit. The concrete type of /// Given the provided `cs`, synthesize the circuit. The concrete type of
/// the caller will be different depending on the context, and they may or /// the caller will be different depending on the context, and they may or
/// may not expect to have a witness present. /// may not expect to have a witness present.
fn synthesize(&self, cs: &mut impl ConstraintSystem<F>) -> Result<(), Error>; fn synthesize(
&self,
cs: &mut impl ConstraintSystem<F>,
config: Self::Config,
) -> Result<(), Error>;
}
/// Low-degree polynomial representing an identity that must hold over the committed wires.
#[derive(Clone, Debug)]
pub enum Polynomial<F> {
/// This is a fixed wire queried at a certain relative location
Fixed(usize),
/// This is an advice (witness) wire queried at a certain relative location
Advice(usize),
/// This is the sum of two polynomials
Sum(Box<Polynomial<F>>, Box<Polynomial<F>>),
/// This is the product of two polynomials
Product(Box<Polynomial<F>>, Box<Polynomial<F>>),
/// This is a scaled polynomial
Scaled(Box<Polynomial<F>>, F),
}
impl<F: Field> Polynomial<F> {
/// Evaluate the polynomial using the provided closures to perform the
/// operations.
pub fn evaluate<T>(
&self,
fixed_wire: &impl Fn(usize) -> T,
advice_wire: &impl Fn(usize) -> T,
sum: &impl Fn(T, T) -> T,
product: &impl Fn(T, T) -> T,
scaled: &impl Fn(T, F) -> T,
) -> T {
match self {
Polynomial::Fixed(index) => fixed_wire(*index),
Polynomial::Advice(index) => advice_wire(*index),
Polynomial::Sum(a, b) => {
let a = a.evaluate(fixed_wire, advice_wire, sum, product, scaled);
let b = b.evaluate(fixed_wire, advice_wire, sum, product, scaled);
sum(a, b)
}
Polynomial::Product(a, b) => {
let a = a.evaluate(fixed_wire, advice_wire, sum, product, scaled);
let b = b.evaluate(fixed_wire, advice_wire, sum, product, scaled);
product(a, b)
}
Polynomial::Scaled(a, f) => {
let a = a.evaluate(fixed_wire, advice_wire, sum, product, scaled);
scaled(a, *f)
}
}
}
/// Compute the degree of this polynomial
pub fn degree(&self) -> usize {
match self {
Polynomial::Fixed(_) => 1,
Polynomial::Advice(_) => 1,
Polynomial::Sum(a, b) => max(a.degree(), b.degree()),
Polynomial::Product(a, b) => a.degree() + b.degree(),
Polynomial::Scaled(poly, _) => poly.degree(),
}
}
}
impl<F> Add for Polynomial<F> {
type Output = Polynomial<F>;
fn add(self, rhs: Polynomial<F>) -> Polynomial<F> {
Polynomial::Sum(Box::new(self), Box::new(rhs))
}
}
impl<F> Mul for Polynomial<F> {
type Output = Polynomial<F>;
fn mul(self, rhs: Polynomial<F>) -> Polynomial<F> {
Polynomial::Product(Box::new(self), Box::new(rhs))
}
}
impl<F> Mul<F> for Polynomial<F> {
type Output = Polynomial<F>;
fn mul(self, rhs: F) -> Polynomial<F> {
Polynomial::Scaled(Box::new(self), rhs)
}
}
/// Represents an index into a vector where each entry corresponds to a distinct
/// point that polynomials are queried at.
#[derive(Copy, Clone, Debug)]
pub struct PointIndex(pub usize);
/// This is a description of the circuit environment, such as the gate, wire and
/// permutation arrangements.
#[derive(Debug, Clone)]
pub struct MetaCircuit<F> {
pub(crate) num_fixed_wires: usize,
pub(crate) num_advice_wires: usize,
// permutations: Vec<Vec<Wire>>,
pub(crate) gates: Vec<Polynomial<F>>,
pub(crate) advice_queries: Vec<(AdviceWire, Rotation)>,
pub(crate) fixed_queries: Vec<(FixedWire, Rotation)>,
// Mapping from a witness vector rotation to the index in the point vector.
pub(crate) rotations: HashMap<Rotation, PointIndex>,
}
impl<F: Field> Default for MetaCircuit<F> {
fn default() -> MetaCircuit<F> {
let mut rotations = HashMap::new();
rotations.insert(Rotation::default(), PointIndex(0));
MetaCircuit {
num_fixed_wires: 0,
num_advice_wires: 0,
gates: vec![],
fixed_queries: Vec::new(),
advice_queries: Vec::new(),
rotations,
}
}
}
impl<F: Field> MetaCircuit<F> {
/// Query a fixed wire at a relative position
pub fn query_fixed(&mut self, wire: FixedWire, at: i32) -> Polynomial<F> {
let at = Rotation(at);
{
let len = self.rotations.len();
self.rotations.entry(at).or_insert(PointIndex(len));
}
// TODO: check for existing query so we don't make redundant queries
let index = self.fixed_queries.len();
self.fixed_queries.push((wire, at));
Polynomial::Fixed(index)
}
/// Query an advice wire at a relative position
pub fn query_advice(&mut self, wire: AdviceWire, at: i32) -> Polynomial<F> {
let at = Rotation(at);
{
let len = self.rotations.len();
self.rotations.entry(at).or_insert(PointIndex(len));
}
// TODO: check for existing query so we don't make redundant queries
let index = self.advice_queries.len();
self.advice_queries.push((wire, at));
Polynomial::Advice(index)
}
/// Create a new gate
pub fn create_gate(&mut self, f: impl FnOnce(&mut Self) -> Polynomial<F>) {
let poly = f(self);
self.gates.push(poly);
}
/// Allocate a new fixed wire
pub fn fixed_wire(&mut self) -> FixedWire {
let tmp = FixedWire(self.num_fixed_wires);
self.num_fixed_wires += 1;
tmp
}
/// Allocate a new advice wire
pub fn advice_wire(&mut self) -> AdviceWire {
let tmp = AdviceWire(self.num_advice_wires);
self.num_advice_wires += 1;
tmp
}
} }

View file

@ -1,5 +1,16 @@
use crate::arithmetic::{best_fft, parallelize, Field, Group}; use crate::arithmetic::{best_fft, parallelize, Field, Group};
/// Describes a relative location in the evaluation domain; applying a rotation
/// by i will rotate the vector in the evaluation domain by i.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct Rotation(pub i32);
impl Default for Rotation {
fn default() -> Rotation {
Rotation(0)
}
}
/// This structure contains precomputed constants and other details needed for /// This structure contains precomputed constants and other details needed for
/// performing operations on an evaluation domain of size $2^k$ in the context /// performing operations on an evaluation domain of size $2^k$ in the context
/// of PLONK. /// of PLONK.
@ -8,6 +19,7 @@ pub struct EvaluationDomain<G: Group> {
n: u64, n: u64,
k: u32, k: u32,
extended_k: u32, extended_k: u32,
omega: G::Scalar,
omega_inv: G::Scalar, omega_inv: G::Scalar,
extended_omega: G::Scalar, extended_omega: G::Scalar,
extended_omega_inv: G::Scalar, extended_omega_inv: G::Scalar,
@ -91,6 +103,7 @@ impl<G: Group> EvaluationDomain<G> {
n, n,
k, k,
extended_k, extended_k,
omega,
omega_inv, omega_inv,
extended_omega, extended_omega,
extended_omega_inv, extended_omega_inv,
@ -103,32 +116,46 @@ impl<G: Group> EvaluationDomain<G> {
} }
} }
/// This takes us from an n-length vector into the coset evaluation domain. /// This takes us from an n-length vector into the coefficient form.
/// Also returns the polynomial.
/// ///
/// This function will panic if the provided vector is not the correct /// This function will panic if the provided vector is not the correct
/// length. /// length.
pub fn obtain_coset(&self, mut a: Vec<G>) -> (Vec<G>, Vec<G>) { pub fn obtain_poly(&self, mut a: Vec<G>) -> Vec<G> {
assert_eq!(a.len(), 1 << self.k); assert_eq!(a.len(), 1 << self.k);
// Perform inverse FFT to obtain the polynomial in coefficient form // Perform inverse FFT to obtain the polynomial in coefficient form
Self::ifft(&mut a, self.omega_inv, self.k, self.ifft_divisor); Self::ifft(&mut a, self.omega_inv, self.k, self.ifft_divisor);
// Keep this polynomial around; we'll need to evaluate it at arbitrary a
// points later. }
let old = a.clone();
// Distributes powers so that an FFT will move us into the coset /// This takes us from an n-length coefficient vector into the coset
// evaluation domain. /// evaluation domain, rotating by `rotation` if desired.
Self::distribute_powers(&mut a, self.g_coset); ///
/// This function will panic if the provided vector is not the correct
/// length.
pub fn obtain_coset(&self, mut a: Vec<G>, rotation: Rotation) -> Vec<G> {
assert_eq!(a.len(), 1 << self.k);
// Resize to account for the quotient polynomial's size assert!(rotation.0 != i32::MIN);
a.resize(1 << self.extended_k, G::group_zero()); if rotation.0 == 0 {
// In this special case, the powers of zeta repeat so we do not need
// Move into coset evaluation domain // to compute them.
Self::distribute_powers_zeta(&mut a, self.g_coset);
} else {
let mut g = G::Scalar::ZETA;
if rotation.0 > 0 {
g *= &self.omega.pow_vartime(&[rotation.0 as u64, 0, 0, 0]);
} else {
g *= &self
.omega_inv
.pow_vartime(&[rotation.0.abs() as u64, 0, 0, 0]);
}
Self::distribute_powers(&mut a, g);
}
a.resize(self.coset_len(), G::group_zero());
best_fft(&mut a, self.extended_omega, self.extended_k); best_fft(&mut a, self.extended_omega, self.extended_k);
a
(a, old)
} }
/// This takes us from the coset evaluation domain and gets us the quotient /// This takes us from the coset evaluation domain and gets us the quotient
@ -137,7 +164,7 @@ impl<G: Group> EvaluationDomain<G> {
/// This function will panic if the provided vector is not the correct /// This function will panic if the provided vector is not the correct
/// length. /// length.
pub fn from_coset(&self, mut a: Vec<G>) -> Vec<G> { pub fn from_coset(&self, mut a: Vec<G>) -> Vec<G> {
assert_eq!(a.len(), 1 << self.extended_k); assert_eq!(a.len(), self.coset_len());
// Inverse FFT // Inverse FFT
Self::ifft( Self::ifft(
@ -162,7 +189,7 @@ impl<G: Group> EvaluationDomain<G> {
/// This divides the polynomial (in the coset domain) by the vanishing /// This divides the polynomial (in the coset domain) by the vanishing
/// polynomial. /// polynomial.
pub fn divide_by_vanishing_poly(&self, mut h_poly: Vec<G>) -> Vec<G> { pub fn divide_by_vanishing_poly(&self, mut h_poly: Vec<G>) -> Vec<G> {
assert_eq!(h_poly.len(), 1 << self.extended_k); assert_eq!(h_poly.len(), self.coset_len());
// Divide to obtain the quotient polynomial in the coset evaluation // Divide to obtain the quotient polynomial in the coset evaluation
// domain. // domain.
@ -176,7 +203,7 @@ impl<G: Group> EvaluationDomain<G> {
h_poly h_poly
} }
fn distribute_powers(mut a: &mut [G], g: G::Scalar) { fn distribute_powers_zeta(mut a: &mut [G], g: G::Scalar) {
let coset_powers = [g, g.square()]; let coset_powers = [g, g.square()];
parallelize(&mut a, |a, mut index| { parallelize(&mut a, |a, mut index| {
for a in a { for a in a {
@ -190,6 +217,16 @@ impl<G: Group> EvaluationDomain<G> {
}); });
} }
fn distribute_powers(mut a: &mut [G], g: G::Scalar) {
parallelize(&mut a, |a, index| {
let mut cur = g.pow_vartime(&[index as u64, 0, 0, 0]);
for a in a {
a.group_scale(&cur);
cur *= &g;
}
});
}
fn ifft(a: &mut [G], omega_inv: G::Scalar, log_n: u32, divisor: G::Scalar) { fn ifft(a: &mut [G], omega_inv: G::Scalar, log_n: u32, divisor: G::Scalar) {
best_fft(a, omega_inv, log_n); best_fft(a, omega_inv, log_n);
parallelize(a, |a, _| { parallelize(a, |a, _| {
@ -199,4 +236,28 @@ impl<G: Group> EvaluationDomain<G> {
} }
}); });
} }
pub fn coset_len(&self) -> usize {
1 << self.extended_k
}
pub fn get_omega(&self) -> G::Scalar {
self.omega
}
pub fn get_omega_inv(&self) -> G::Scalar {
self.omega_inv
}
pub fn rotate_omega(&self, constant: G::Scalar, rotation: Rotation) -> G::Scalar {
let mut point = constant;
if rotation.0 >= 0 {
point *= &self.get_omega().pow(&[rotation.0 as u64, 0, 0, 0]);
} else {
point *= &self
.get_omega_inv()
.pow(&[rotation.0.abs() as u64, 0, 0, 0]);
}
point
}
} }

View file

@ -1,9 +1,11 @@
use super::{ use super::{
circuit::{Circuit, ConstraintSystem, Wire}, circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit},
domain::Rotation,
hash_point, Error, Proof, SRS, hash_point, Error, Proof, SRS,
}; };
use crate::arithmetic::{ use crate::arithmetic::{
eval_polynomial, get_challenge_scalar, Challenge, Curve, CurveAffine, Field, eval_polynomial, get_challenge_scalar, kate_division, parallelize, Challenge, Curve,
CurveAffine, Field,
}; };
use crate::polycommit::Params; use crate::polycommit::Params;
use crate::transcript::Hasher; use crate::transcript::Hasher;
@ -22,121 +24,130 @@ impl<C: CurveAffine> Proof<C> {
circuit: &ConcreteCircuit, circuit: &ConcreteCircuit,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
struct WitnessCollection<F: Field> { struct WitnessCollection<F: Field> {
a: Vec<F>, advice: Vec<Vec<F>>,
b: Vec<F>,
c: Vec<F>,
d: Vec<F>,
sa: Vec<F>,
sb: Vec<F>,
sc: Vec<F>,
sd: Vec<F>,
sm: Vec<F>,
} }
impl<F: Field> ConstraintSystem<F> for WitnessCollection<F> { impl<F: Field> ConstraintSystem<F> for WitnessCollection<F> {
fn create_gate( fn assign_advice(
&mut self, &mut self,
sa: F, wire: AdviceWire,
sb: F, row: usize,
sc: F, to: impl FnOnce() -> Result<F, Error>,
sd: F, ) -> Result<(), Error> {
sm: F, *self
f: impl Fn() -> Result<(F, F, F, F), Error>, .advice
) -> Result<(Wire, Wire, Wire, Wire), Error> { .get_mut(wire.0)
let (a, b, c, d) = f()?; .and_then(|v| v.get_mut(row))
let tmp = Ok(( .ok_or(Error::BoundsFailure)? = to()?;
Wire::A(self.a.len()),
Wire::B(self.a.len()), Ok(())
Wire::C(self.a.len()), }
Wire::D(self.a.len()),
)); fn assign_fixed(
self.a.push(a); &mut self,
self.b.push(b); _: FixedWire,
self.c.push(c); _: usize,
self.d.push(d); _: impl FnOnce() -> Result<F, Error>,
self.sa.push(sa); ) -> Result<(), Error> {
self.sb.push(sb); // We only care about advice wires here
self.sc.push(sc);
self.sd.push(sd); Ok(())
self.sm.push(sm);
tmp
} }
// fn copy(&mut self, left: Wire, right: Wire) {
// unimplemented!()
// }
} }
let mut meta = MetaCircuit::default();
let config = ConcreteCircuit::configure(&mut meta);
let mut witness = WitnessCollection { let mut witness = WitnessCollection {
a: vec![], advice: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_advice_wires],
b: vec![],
c: vec![],
d: vec![],
sa: vec![],
sb: vec![],
sc: vec![],
sd: vec![],
sm: vec![],
}; };
// Synthesize the circuit to obtain the witness and other information. // Synthesize the circuit to obtain the witness and other information.
circuit.synthesize(&mut witness)?; circuit.synthesize(&mut witness, config)?;
// Create a transcript for obtaining Fiat-Shamir challenges. // Create a transcript for obtaining Fiat-Shamir challenges.
let mut transcript = HBase::init(C::Base::one()); let mut transcript = HBase::init(C::Base::one());
if witness.a.len() > params.n as usize { // Compute commitments to advice wire polynomials
// The polynomial commitment does not support a high enough degree let advice_blinds: Vec<_> = witness.advice.iter().map(|_| C::Scalar::random()).collect();
// polynomial to commit to our wires because this circuit has too let advice_commitments = witness
// many gates. .advice
return Err(Error::IncompatibleParams); .iter()
.zip(advice_blinds.iter())
.map(|(poly, blind)| params.commit_lagrange(poly, *blind).to_affine())
.collect();
for commitment in &advice_commitments {
hash_point(&mut transcript, commitment)?;
} }
witness.a.resize(params.n as usize, C::Scalar::zero());
witness.b.resize(params.n as usize, C::Scalar::zero());
witness.c.resize(params.n as usize, C::Scalar::zero());
witness.d.resize(params.n as usize, C::Scalar::zero());
witness.sa.resize(params.n as usize, C::Scalar::zero());
witness.sb.resize(params.n as usize, C::Scalar::zero());
witness.sc.resize(params.n as usize, C::Scalar::zero());
witness.sd.resize(params.n as usize, C::Scalar::zero());
witness.sm.resize(params.n as usize, C::Scalar::zero());
// Compute commitments to the various wire values
let a_blind = C::Scalar::one(); // TODO: not random
let b_blind = C::Scalar::one(); // TODO: not random
let c_blind = C::Scalar::one(); // TODO: not random
let d_blind = C::Scalar::one(); // TODO: not random
let a_commitment = params.commit_lagrange(&witness.a, a_blind).to_affine();
let b_commitment = params.commit_lagrange(&witness.b, b_blind).to_affine();
let c_commitment = params.commit_lagrange(&witness.c, c_blind).to_affine();
let d_commitment = params.commit_lagrange(&witness.d, d_blind).to_affine();
hash_point(&mut transcript, &a_commitment)?;
hash_point(&mut transcript, &b_commitment)?;
hash_point(&mut transcript, &c_commitment)?;
hash_point(&mut transcript, &d_commitment)?;
let domain = &srs.domain; let domain = &srs.domain;
let (a_coset, a_poly) = domain.obtain_coset(witness.a); let advice_polys: Vec<_> = witness
let (b_coset, b_poly) = domain.obtain_coset(witness.b); .advice
let (c_coset, c_poly) = domain.obtain_coset(witness.c); .into_iter()
let (d_coset, d_poly) = domain.obtain_coset(witness.d); .map(|poly| domain.obtain_poly(poly))
.collect();
// (a * sa) + (b * sb) + (a * sm * b) + (d * sd) - (c * sc) let advice_cosets: Vec<_> = meta
let mut h_poly = Vec::with_capacity(a_coset.len()); .advice_queries
for ((((((((a, b), c), d), sa), sb), sc), sd), sm) in a_coset
.iter() .iter()
.zip(b_coset.iter()) .map(|&(wire, at)| {
.zip(c_coset.iter()) let poly = advice_polys[wire.0].clone();
.zip(d_coset.iter()) domain.obtain_coset(poly, at)
.zip(srs.sa.0.iter()) })
.zip(srs.sb.0.iter()) .collect();
.zip(srs.sc.0.iter())
.zip(srs.sd.0.iter()) // Obtain challenge for keeping all separate gates linearly independent
.zip(srs.sm.0.iter()) let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
{
h_poly.push((*a) * sa + &((*b) * sb) + &((*a) * sm * b) + &((*d) * sd) - &((*c) * sc)); // Evaluate the circuit using the custom gates provided
let mut h_poly = vec![C::Scalar::zero(); domain.coset_len()];
for (i, poly) in meta.gates.iter().enumerate() {
if i != 0 {
for h in h_poly.iter_mut() {
*h *= &x_2;
}
}
let evaluation: Vec<C::Scalar> = poly.evaluate(
&|index| srs.fixed_cosets[index].clone(),
&|index| advice_cosets[index].clone(),
&|mut a, b| {
parallelize(&mut a, |a, start| {
for (a, b) in a.iter_mut().zip(b[start..].iter()) {
*a += b;
}
});
a
},
&|mut a, b| {
parallelize(&mut a, |a, start| {
for (a, b) in a.iter_mut().zip(b[start..].iter()) {
*a *= b;
}
});
a
},
&|mut a, scalar| {
parallelize(&mut a, |a, _| {
for a in a {
*a *= &scalar;
}
});
a
},
);
assert_eq!(h_poly.len(), evaluation.len());
if i == 0 {
h_poly = evaluation;
} else {
for (h, e) in h_poly.iter_mut().zip(evaluation.into_iter()) {
*h += &e;
}
}
} }
// Divide by t(X) = X^{params.n} - 1. // Divide by t(X) = X^{params.n} - 1.
@ -151,7 +162,7 @@ impl<C: CurveAffine> Proof<C> {
.map(|v| v.to_vec()) .map(|v| v.to_vec())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
drop(h_poly); drop(h_poly);
let h_blinds = vec![C::Scalar::one(); h_pieces.len()]; // TODO: not random let h_blinds: Vec<_> = h_pieces.iter().map(|_| C::Scalar::random()).collect();
// Compute commitments to each h(X) piece // Compute commitments to each h(X) piece
let h_commitments: Vec<_> = h_pieces let h_commitments: Vec<_> = h_pieces
@ -165,39 +176,44 @@ impl<C: CurveAffine> Proof<C> {
hash_point(&mut transcript, c)?; hash_point(&mut transcript, c)?;
} }
let x: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
// Evaluate polynomials at x // Evaluate polynomials at omega^i x_3
let a_eval_x = eval_polynomial(&a_poly, x); let advice_evals: Vec<_> = meta
let b_eval_x = eval_polynomial(&b_poly, x); .advice_queries
let c_eval_x = eval_polynomial(&c_poly, x);
let d_eval_x = eval_polynomial(&d_poly, x);
let sa_eval_x = eval_polynomial(&srs.sa.1, x);
let sb_eval_x = eval_polynomial(&srs.sb.1, x);
let sc_eval_x = eval_polynomial(&srs.sc.1, x);
let sd_eval_x = eval_polynomial(&srs.sd.1, x);
let sm_eval_x = eval_polynomial(&srs.sm.1, x);
let h_evals_x: Vec<_> = h_pieces
.iter() .iter()
.map(|poly| eval_polynomial(poly, x)) .map(|&(wire, at)| eval_polynomial(&advice_polys[wire.0], domain.rotate_omega(x_3, at)))
.collect();
let fixed_evals: Vec<_> = meta
.fixed_queries
.iter()
.map(|&(wire, at)| {
eval_polynomial(&srs.fixed_polys[wire.0], domain.rotate_omega(x_3, at))
})
.collect();
let h_evals: Vec<_> = h_pieces
.iter()
.map(|poly| eval_polynomial(poly, x_3))
.collect(); .collect();
// We set up a second transcript on the scalar field to hash in openings of // We set up a second transcript on the scalar field to hash in openings of
// our polynomial commitments. // our polynomial commitments.
let mut transcript_scalar = HScalar::init(C::Scalar::one()); let mut transcript_scalar = HScalar::init(C::Scalar::one());
transcript_scalar.absorb(a_eval_x);
transcript_scalar.absorb(b_eval_x);
transcript_scalar.absorb(c_eval_x);
transcript_scalar.absorb(d_eval_x);
transcript_scalar.absorb(sa_eval_x);
transcript_scalar.absorb(sb_eval_x);
transcript_scalar.absorb(sc_eval_x);
transcript_scalar.absorb(sd_eval_x);
transcript_scalar.absorb(sm_eval_x);
// Hash each h(x) piece // Hash each advice evaluation
for eval in h_evals_x.iter() { for eval in advice_evals.iter() {
transcript_scalar.absorb(*eval);
}
// Hash each fixed evaluation
for eval in fixed_evals.iter() {
transcript_scalar.absorb(*eval);
}
// Hash each h(x) piece evaluation
for eval in h_evals.iter() {
transcript_scalar.absorb(*eval); transcript_scalar.absorb(*eval);
} }
@ -205,61 +221,146 @@ impl<C: CurveAffine> Proof<C> {
C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap();
transcript.absorb(transcript_scalar_point); transcript.absorb(transcript_scalar_point);
let y: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
let mut q_commitment = h_commitments[0].clone().to_projective(); // Collapse openings at same points together into single openings using
let mut q_poly = h_pieces[0].clone(); // x_4 challenge.
let mut q_blind = h_blinds[0]; let mut q_polys: Vec<Option<Vec<_>>> = vec![None; meta.rotations.len()];
let mut q_blinds = vec![C::Scalar::zero(); meta.rotations.len()];
let mut q_evals: Vec<_> = vec![C::Scalar::zero(); meta.rotations.len()];
{ {
let mut accumulate = |poly: &[_], blind: &C::Scalar, commitment: C| { let mut accumulate = |point_index: usize, new_poly: &Vec<_>, blind, eval| {
for (a, q) in poly.iter().zip(q_poly.iter_mut()) { q_polys[point_index]
*q = (*q * &y) + a; .as_mut()
} .map(|poly| {
q_commitment = (q_commitment * y) + &commitment.to_projective(); parallelize(poly, |q, start| {
q_blind = (q_blind * &y) + blind; for (q, a) in q.iter_mut().zip(new_poly[start..].iter()) {
*q *= &x_4;
*q += a;
}
});
})
.or_else(|| {
q_polys[point_index] = Some(new_poly.clone());
Some(())
});
q_blinds[point_index] *= &x_4;
q_blinds[point_index] += &blind;
q_evals[point_index] *= &x_4;
q_evals[point_index] += &eval;
}; };
for ((poly, blind), commitment) in h_pieces for (query_index, &(wire, ref at)) in meta.advice_queries.iter().enumerate() {
.iter() let point_index = (*meta.rotations.get(at).unwrap()).0;
.zip(h_blinds.iter())
.zip(h_commitments.iter()) accumulate(
.skip(1) point_index,
{ &advice_polys[wire.0],
accumulate(&poly, blind, *commitment); advice_blinds[wire.0],
advice_evals[query_index],
);
} }
accumulate(&a_poly, &a_blind, a_commitment); for (query_index, &(wire, ref at)) in meta.fixed_queries.iter().enumerate() {
accumulate(&b_poly, &b_blind, b_commitment); let point_index = (*meta.rotations.get(at).unwrap()).0;
accumulate(&c_poly, &c_blind, c_commitment);
accumulate(&d_poly, &d_blind, d_commitment); accumulate(
accumulate(&srs.sa.1, &Field::one(), srs.sa_commitment); point_index,
accumulate(&srs.sb.1, &Field::one(), srs.sb_commitment); &srs.fixed_polys[wire.0],
accumulate(&srs.sc.1, &Field::one(), srs.sc_commitment); C::Scalar::one(),
accumulate(&srs.sd.1, &Field::one(), srs.sd_commitment); fixed_evals[query_index],
accumulate(&srs.sm.1, &Field::one(), srs.sm_commitment); );
}
// We query the h(X) polynomial at x_3
let current_index = (*meta.rotations.get(&Rotation::default()).unwrap()).0;
for ((h_poly, h_blind), h_eval) in h_pieces
.into_iter()
.zip(h_blinds.iter())
.zip(h_evals.iter())
{
accumulate(current_index, &h_poly, *h_blind, *h_eval);
}
}
let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
let mut f_poly: Option<Vec<C::Scalar>> = None;
for (&row, &point_index) in meta.rotations.iter() {
let mut poly = q_polys[point_index.0].as_ref().unwrap().clone();
let point = domain.rotate_omega(x_3, row);
poly[0] -= &q_evals[point_index.0];
let mut poly = kate_division(&poly, point);
poly.push(C::Scalar::zero());
f_poly = f_poly
.map(|mut f_poly| {
parallelize(&mut f_poly, |q, start| {
for (q, a) in q.iter_mut().zip(poly[start..].iter()) {
*q *= &x_5;
*q += a;
}
});
f_poly
})
.or_else(|| Some(poly));
}
let mut f_poly = f_poly.unwrap();
let mut f_blind = C::Scalar::random();
let f_commitment = params.commit(&f_poly, f_blind).to_affine();
hash_point(&mut transcript, &f_commitment)?;
let x_6: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
let mut q_evals = vec![];
for (_, &point_index) in meta.rotations.iter() {
q_evals.push(eval_polynomial(
&q_polys[point_index.0].as_ref().unwrap(),
x_6,
));
}
for eval in q_evals.iter() {
transcript_scalar.absorb(*eval);
}
let transcript_scalar_point =
C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap();
transcript.absorb(transcript_scalar_point);
let x_7: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
for (_, &point_index) in meta.rotations.iter() {
f_blind *= &x_7;
f_blind += &q_blinds[point_index.0];
parallelize(&mut f_poly, |f, start| {
for (f, a) in f
.iter_mut()
.zip(q_polys[point_index.0].as_ref().unwrap()[start..].iter())
{
*f *= &x_7;
*f += a;
}
});
} }
// Let's prove that the q_commitment opens at x to the expected value. // Let's prove that the q_commitment opens at x to the expected value.
let opening = params let opening = params
.create_proof(&mut transcript, &q_poly, q_blind, x) .create_proof(&mut transcript, &f_poly, f_blind, x_6)
.map_err(|_| Error::ConstraintSystemFailure)?; .map_err(|_| Error::ConstraintSystemFailure)?;
Ok(Proof { Ok(Proof {
a_commitment, advice_commitments,
b_commitment,
c_commitment,
d_commitment,
h_commitments, h_commitments,
a_eval_x, advice_evals,
b_eval_x, fixed_evals,
c_eval_x, h_evals,
d_eval_x, f_commitment,
sa_eval_x, q_evals,
sb_eval_x,
sc_eval_x,
sd_eval_x,
sm_eval_x,
h_evals_x,
opening, opening,
}) })
} }

View file

@ -1,7 +1,7 @@
use super::{ use super::{
circuit::{Circuit, ConstraintSystem, Wire}, circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit},
domain::EvaluationDomain, domain::EvaluationDomain,
Error, GATE_DEGREE, SRS, Error, SRS,
}; };
use crate::arithmetic::{Curve, CurveAffine, Field}; use crate::arithmetic::{Curve, CurveAffine, Field};
use crate::polycommit::Params; use crate::polycommit::Params;
@ -14,92 +14,80 @@ impl<C: CurveAffine> SRS<C> {
circuit: &ConcreteCircuit, circuit: &ConcreteCircuit,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
struct Assembly<F: Field> { struct Assembly<F: Field> {
sa: Vec<F>, fixed: Vec<Vec<F>>,
sb: Vec<F>,
sc: Vec<F>,
sd: Vec<F>,
sm: Vec<F>,
} }
impl<F: Field> ConstraintSystem<F> for Assembly<F> { impl<F: Field> ConstraintSystem<F> for Assembly<F> {
fn create_gate( fn assign_advice(
&mut self, &mut self,
sa: F, _: AdviceWire,
sb: F, _: usize,
sc: F, _: impl FnOnce() -> Result<F, Error>,
sd: F, ) -> Result<(), Error> {
sm: F, // We only care about fixed wires here
_: impl Fn() -> Result<(F, F, F, F), Error>, Ok(())
) -> Result<(Wire, Wire, Wire, Wire), Error> { }
let tmp = Ok((
Wire::A(self.sa.len()), fn assign_fixed(
Wire::B(self.sa.len()), &mut self,
Wire::C(self.sa.len()), wire: FixedWire,
Wire::D(self.sa.len()), row: usize,
)); to: impl FnOnce() -> Result<F, Error>,
self.sa.push(sa); ) -> Result<(), Error> {
self.sb.push(sb); *self
self.sc.push(sc); .fixed
self.sd.push(sd); .get_mut(wire.0)
self.sm.push(sm); .and_then(|v| v.get_mut(row))
tmp .ok_or(Error::BoundsFailure)? = to()?;
Ok(())
} }
} }
let mut meta = MetaCircuit::default();
let config = ConcreteCircuit::configure(&mut meta);
let mut assembly: Assembly<C::Scalar> = Assembly { let mut assembly: Assembly<C::Scalar> = Assembly {
sa: vec![], fixed: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_fixed_wires],
sb: vec![],
sc: vec![],
sd: vec![],
sm: vec![],
}; };
// Synthesize the circuit to obtain SRS // Synthesize the circuit to obtain SRS
circuit.synthesize(&mut assembly)?; circuit.synthesize(&mut assembly, config)?;
assembly.sa.resize(params.n as usize, C::Scalar::zero()); let fixed_commitments = assembly
assembly.sb.resize(params.n as usize, C::Scalar::zero()); .fixed
assembly.sc.resize(params.n as usize, C::Scalar::zero()); .iter()
assembly.sd.resize(params.n as usize, C::Scalar::zero()); .map(|poly| params.commit_lagrange(poly, C::Scalar::one()).to_affine())
assembly.sm.resize(params.n as usize, C::Scalar::zero()); .collect();
// Compute commitments to the fixed wire values let mut degree = 1;
let sa_commitment = params for poly in meta.gates.iter() {
.commit_lagrange(&assembly.sa, C::Scalar::one()) degree = std::cmp::max(degree, poly.degree());
.to_affine(); }
let sb_commitment = params
.commit_lagrange(&assembly.sb, C::Scalar::one())
.to_affine();
let sc_commitment = params
.commit_lagrange(&assembly.sc, C::Scalar::one())
.to_affine();
let sd_commitment = params
.commit_lagrange(&assembly.sd, C::Scalar::one())
.to_affine();
let sm_commitment = params
.commit_lagrange(&assembly.sm, C::Scalar::one())
.to_affine();
let domain = EvaluationDomain::new(GATE_DEGREE, params.k); let domain = EvaluationDomain::new(degree as u32, params.k);
let sa = domain.obtain_coset(assembly.sa); let fixed_polys: Vec<_> = assembly
let sb = domain.obtain_coset(assembly.sb); .fixed
let sc = domain.obtain_coset(assembly.sc); .into_iter()
let sd = domain.obtain_coset(assembly.sd); .map(|poly| domain.obtain_poly(poly))
let sm = domain.obtain_coset(assembly.sm); .collect();
let fixed_cosets = meta
.fixed_queries
.iter()
.map(|&(wire, at)| {
let poly = fixed_polys[wire.0].clone();
domain.obtain_coset(poly, at)
})
.collect();
Ok(SRS { Ok(SRS {
sa,
sb,
sc,
sd,
sm,
sa_commitment,
sb_commitment,
sc_commitment,
sd_commitment,
sm_commitment,
domain, domain,
fixed_commitments,
fixed_polys,
fixed_cosets,
meta,
}) })
} }
} }

View file

@ -1,4 +1,4 @@
use super::{hash_point, Proof, SRS}; use super::{domain::Rotation, hash_point, Proof, SRS};
use crate::arithmetic::{get_challenge_scalar, Challenge, Curve, CurveAffine, Field}; use crate::arithmetic::{get_challenge_scalar, Challenge, Curve, CurveAffine, Field};
use crate::polycommit::Params; use crate::polycommit::Params;
use crate::transcript::Hasher; use crate::transcript::Hasher;
@ -13,35 +13,34 @@ impl<C: CurveAffine> Proof<C> {
// Create a transcript for obtaining Fiat-Shamir challenges. // Create a transcript for obtaining Fiat-Shamir challenges.
let mut transcript = HBase::init(C::Base::one()); let mut transcript = HBase::init(C::Base::one());
hash_point(&mut transcript, &self.a_commitment) // Hash the prover's advice commitments into the transcript
.expect("proof cannot contain points at infinity"); for commitment in &self.advice_commitments {
hash_point(&mut transcript, &self.b_commitment) hash_point(&mut transcript, commitment)
.expect("proof cannot contain points at infinity"); .expect("proof cannot contain points at infinity");
hash_point(&mut transcript, &self.c_commitment) }
.expect("proof cannot contain points at infinity");
hash_point(&mut transcript, &self.d_commitment)
.expect("proof cannot contain points at infinity");
// Sample x_2 challenge, which keeps the gates linearly independent.
let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
// Obtain a commitment to h(X) in the form of multiple pieces of degree n - 1
for c in &self.h_commitments { for c in &self.h_commitments {
hash_point(&mut transcript, c).expect("proof cannot contain points at infinity"); hash_point(&mut transcript, c).expect("proof cannot contain points at infinity");
} }
let x: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); // Sample x_3 challenge, which is used to ensure the circuit is
// satisfied with high probability.
let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
// We set up a second transcript on the scalar field to hash in openings of // Hash together all the openings provided by the prover into a new
// our polynomial commitments. // transcript on the scalar field.
let mut transcript_scalar = HScalar::init(C::Scalar::one()); let mut transcript_scalar = HScalar::init(C::Scalar::one());
transcript_scalar.absorb(self.a_eval_x);
transcript_scalar.absorb(self.b_eval_x);
transcript_scalar.absorb(self.c_eval_x);
transcript_scalar.absorb(self.d_eval_x);
transcript_scalar.absorb(self.sa_eval_x);
transcript_scalar.absorb(self.sb_eval_x);
transcript_scalar.absorb(self.sc_eval_x);
transcript_scalar.absorb(self.sd_eval_x);
transcript_scalar.absorb(self.sm_eval_x);
for eval in &self.h_evals_x { for eval in self
.advice_evals
.iter()
.chain(self.fixed_evals.iter())
.chain(self.h_evals.iter())
{
transcript_scalar.absorb(*eval); transcript_scalar.absorb(*eval);
} }
@ -49,60 +48,136 @@ impl<C: CurveAffine> Proof<C> {
C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap();
transcript.absorb(transcript_scalar_point); transcript.absorb(transcript_scalar_point);
let y: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); // Evaluate the circuit using the custom gates provided
let mut h_eval = C::Scalar::zero();
for poly in srs.meta.gates.iter() {
h_eval *= &x_2;
let mut q_commitment = self.h_commitments[0].clone().to_projective(); let evaluation: C::Scalar = poly.evaluate(
let mut expected_opening = self.h_evals_x[0]; &|index| self.fixed_evals[index],
{ &|index| self.advice_evals[index],
let mut accumulate = |commitment: C, opening: C::Scalar| { &|a, b| a + &b,
q_commitment = commitment.to_projective() + &(q_commitment * y); &|a, b| a * &b,
expected_opening = opening + &(expected_opening * &y); &|a, scalar| a * &scalar,
}; );
for (commitment, eval) in self.h_commitments.iter().zip(self.h_evals_x.iter()).skip(1) { h_eval += &evaluation;
accumulate(*commitment, *eval);
}
accumulate(self.a_commitment, self.a_eval_x);
accumulate(self.b_commitment, self.b_eval_x);
accumulate(self.c_commitment, self.c_eval_x);
accumulate(self.d_commitment, self.d_eval_x);
accumulate(srs.sa_commitment, self.sa_eval_x);
accumulate(srs.sb_commitment, self.sb_eval_x);
accumulate(srs.sc_commitment, self.sc_eval_x);
accumulate(srs.sd_commitment, self.sd_eval_x);
accumulate(srs.sm_commitment, self.sm_eval_x);
} }
let q_commitment = q_commitment.to_affine(); let xn = x_3.pow(&[params.n as u64, 0, 0, 0]);
let xn = x.pow(&[params.n as u64, 0, 0, 0]);
// Compute the expected h(x) value // Compute the expected h(x) value
let mut h_eval_x = C::Scalar::zero(); let mut expected_h_eval = C::Scalar::zero();
let mut cur = C::Scalar::one(); let mut cur = C::Scalar::one();
for eval in &self.h_evals_x { for eval in &self.h_evals {
h_eval_x += &(cur * eval); expected_h_eval += &(cur * eval);
cur *= &xn; cur *= &xn;
} }
// Check that the circuit is satisfied. if h_eval != (expected_h_eval * &(xn - &C::Scalar::one())) {
// (a * sa) + (b * sb) + (a * sm * b) + (d * sd) - (c * sc)
if self.a_eval_x * &self.sa_eval_x
+ &(self.b_eval_x * &self.sb_eval_x)
+ &(self.a_eval_x * &self.sm_eval_x * &self.b_eval_x)
+ &(self.d_eval_x * &self.sd_eval_x)
- &(self.c_eval_x * &self.sc_eval_x)
!= h_eval_x * &(xn - &C::Scalar::one())
{
return false; return false;
} }
// We are now convinced the circuit is satisfied so long as the
// polynomial commitments open to the correct values.
// Sample x_4 for compressing openings at the same points together
let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
// Compress the commitments and expected evaluations at x_3 together
// using the challenge x_4
let mut q_commitments: Vec<Option<C::Projective>> = vec![None; srs.meta.rotations.len()];
let mut q_evals: Vec<_> = vec![C::Scalar::zero(); srs.meta.rotations.len()];
{
let mut accumulate = |point_index: usize, new_commitment, eval| {
q_commitments[point_index] = q_commitments[point_index]
.map(|mut commitment| {
commitment *= x_4;
commitment += new_commitment;
commitment
})
.or_else(|| Some(new_commitment.to_projective()));
q_evals[point_index] *= &x_4;
q_evals[point_index] += &eval;
};
for (query_index, &(wire, ref at)) in srs.meta.advice_queries.iter().enumerate() {
let point_index = (*srs.meta.rotations.get(at).unwrap()).0;
accumulate(
point_index,
self.advice_commitments[wire.0],
self.advice_evals[query_index],
);
}
for (query_index, &(wire, ref at)) in srs.meta.fixed_queries.iter().enumerate() {
let point_index = (*srs.meta.rotations.get(at).unwrap()).0;
accumulate(
point_index,
srs.fixed_commitments[wire.0],
self.fixed_evals[query_index],
);
}
let current_index = (*srs.meta.rotations.get(&Rotation::default()).unwrap()).0;
for (h_commitment, h_eval) in self.h_commitments.iter().zip(self.h_evals.iter()) {
accumulate(current_index, *h_commitment, *h_eval);
}
}
// Sample a challenge x_5 for keeping the multi-point quotient
// polynomial terms linearly independent.
let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
// Obtain the commitment to the multi-point quotient polynomial f(X).
hash_point(&mut transcript, &self.f_commitment)
.expect("proof cannot contain points at infinity");
// Sample a challenge x_6 for checking that f(X) was committed to
// correctly.
let x_6: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
for eval in self.q_evals.iter() {
transcript_scalar.absorb(*eval);
}
let transcript_scalar_point =
C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap();
transcript.absorb(transcript_scalar_point);
// We can compute the expected f_eval at x_6 using the q_evals provided
// by the prover and from x_5
let mut f_eval = C::Scalar::zero();
for (&row, &point_index) in srs.meta.rotations.iter() {
let mut eval = self.q_evals[point_index.0];
let point = srs.domain.rotate_omega(x_3, row);
eval = eval - &q_evals[point_index.0];
eval = eval * &(x_6 - &point).invert().unwrap();
f_eval *= &x_5;
f_eval += &eval;
}
// Sample a challenge x_7 that we will use to collapse the openings of
// the various remaining polynomials at x_6 together.
let x_7: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
// Compute the final commitment that has to be opened
let mut f_commitment: C::Projective = self.f_commitment.to_projective();
for (_, &point_index) in srs.meta.rotations.iter() {
f_commitment *= x_7;
f_commitment = f_commitment + &q_commitments[point_index.0].as_ref().unwrap();
f_eval *= &x_7;
f_eval += &self.q_evals[point_index.0];
}
// Verify the opening proof
params.verify_proof( params.verify_proof(
&self.opening, &self.opening,
&mut transcript, &mut transcript,
x, x_6,
&q_commitment, &f_commitment.to_affine(),
expected_opening, f_eval,
) )
} }
} }