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
}
/// 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
/// performed over a mutable slice.
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;
// TODO: remove this
const GATE_DEGREE: u32 = 3;
/// This is a structured reference string (SRS) that is (deterministically)
/// computed from a specific circuit and parameters for the polynomial
/// commitment scheme.
#[derive(Debug)]
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>,
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 structure must never allow points at infinity.
#[derive(Debug, Clone)]
pub struct Proof<C: CurveAffine> {
a_commitment: C,
b_commitment: C,
c_commitment: C,
d_commitment: C,
advice_commitments: Vec<C>,
h_commitments: Vec<C>,
a_eval_x: C::Scalar,
b_eval_x: C::Scalar,
c_eval_x: C::Scalar,
d_eval_x: C::Scalar,
sa_eval_x: 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>,
advice_evals: Vec<C::Scalar>,
fixed_evals: Vec<C::Scalar>,
h_evals: Vec<C::Scalar>,
f_commitment: C,
q_evals: Vec<C::Scalar>,
opening: OpeningProof<C>,
}
@ -77,6 +61,8 @@ pub enum Error {
IncompatibleParams,
/// The constraint system is not satisfied.
ConstraintSystemFailure,
/// Out of bounds index passed to a backend
BoundsFailure,
}
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::polycommit::Params;
use crate::transcript::DummyHash;
use std::marker::PhantomData;
const K: u32 = 5;
// Initialize the polynomial commitment parameters
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> {
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> {
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 {
let (_, _, _, _) = cs.multiply(|| {
let a = self.a.ok_or(Error::SynthesisError)?;
let a2 = a.square();
Ok((a, a, a2))
let mut a_squared = None;
let (_, _, _) = cs.raw_multiply(|| {
a_squared = self.a.map(|a| a.square());
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.add(|| {
let a = self.a.ok_or(Error::SynthesisError)?;
let a2 = a.square();
let a3 = a + a2;
Ok((a, a2, a3))
let (_, _, _) = cs.raw_add(|| {
let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2));
Ok((
self.a.ok_or(Error::SynthesisError)?,
a_squared.ok_or(Error::SynthesisError)?,
fin.ok_or(Error::SynthesisError)?,
))
})?;
//cs.copy(a, d);
//cs.copy(c, e);
}
Ok(())
@ -134,8 +274,10 @@ fn test_proving() {
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
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
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;
/// This represents a PLONK wire, which could be a fixed (selector) wire or an
/// advice wire.
#[derive(Debug)]
pub enum Wire {
/// A wires
A(usize),
/// B wires
B(usize),
/// C wires
C(usize),
/// D wires
D(usize),
}
use super::domain::Rotation;
/// This represents a wire which has a fixed (permanent) value
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct FixedWire(pub usize);
/// This represents a wire which has a witness-specific value
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct AdviceWire(pub usize);
/// This trait allows a [`Circuit`] to direct some backend to assign a witness
/// for a constraint system.
pub trait ConstraintSystem<F: Field> {
/// Creates a gate.
fn create_gate(
/// Assign an advice wire value (witness)
fn assign_advice(
&mut self,
sa: F,
sb: F,
sc: F,
sd: F,
sm: F,
f: impl Fn() -> Result<(F, F, F, F), Error>,
) -> Result<(Wire, Wire, Wire, Wire), Error>;
wire: AdviceWire,
row: usize,
to: impl FnOnce() -> Result<F, Error>,
) -> Result<(), Error>;
/// a * b - c = 0
fn multiply(
/// Assign a fixed value
fn assign_fixed(
&mut self,
f: impl Fn() -> Result<(F, F, F), Error>,
) -> Result<(Wire, Wire, Wire, Wire), Error> {
self.create_gate(F::zero(), F::zero(), F::one(), F::zero(), F::one(), || {
let (a, b, c) = f()?;
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()))
})
}
wire: FixedWire,
row: usize,
to: impl FnOnce() -> Result<F, Error>,
) -> Result<(), Error>;
// 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
/// [`ConstraintSystem`] implementation.
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
/// the caller will be different depending on the context, and they may or
/// 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};
/// 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
/// performing operations on an evaluation domain of size $2^k$ in the context
/// of PLONK.
@ -8,6 +19,7 @@ pub struct EvaluationDomain<G: Group> {
n: u64,
k: u32,
extended_k: u32,
omega: G::Scalar,
omega_inv: G::Scalar,
extended_omega: G::Scalar,
extended_omega_inv: G::Scalar,
@ -91,6 +103,7 @@ impl<G: Group> EvaluationDomain<G> {
n,
k,
extended_k,
omega,
omega_inv,
extended_omega,
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.
/// Also returns the polynomial.
/// This takes us from an n-length vector into the coefficient form.
///
/// This function will panic if the provided vector is not the correct
/// 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);
// Perform inverse FFT to obtain the polynomial in coefficient form
Self::ifft(&mut a, self.omega_inv, self.k, self.ifft_divisor);
// Keep this polynomial around; we'll need to evaluate it at arbitrary
// points later.
let old = a.clone();
a
}
// Distributes powers so that an FFT will move us into the coset
// evaluation domain.
Self::distribute_powers(&mut a, self.g_coset);
/// This takes us from an n-length coefficient vector into the coset
/// evaluation domain, rotating by `rotation` if desired.
///
/// 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
a.resize(1 << self.extended_k, G::group_zero());
// Move into coset evaluation domain
assert!(rotation.0 != i32::MIN);
if rotation.0 == 0 {
// In this special case, the powers of zeta repeat so we do not need
// 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);
(a, old)
a
}
/// 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
/// length.
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
Self::ifft(
@ -162,7 +189,7 @@ impl<G: Group> EvaluationDomain<G> {
/// This divides the polynomial (in the coset domain) by the vanishing
/// polynomial.
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
// domain.
@ -176,7 +203,7 @@ impl<G: Group> EvaluationDomain<G> {
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()];
parallelize(&mut a, |a, mut index| {
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) {
best_fft(a, omega_inv, log_n);
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::{
circuit::{Circuit, ConstraintSystem, Wire},
circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit},
domain::Rotation,
hash_point, Error, Proof, SRS,
};
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::transcript::Hasher;
@ -22,121 +24,130 @@ impl<C: CurveAffine> Proof<C> {
circuit: &ConcreteCircuit,
) -> Result<Self, Error> {
struct WitnessCollection<F: Field> {
a: 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>,
advice: Vec<Vec<F>>,
}
impl<F: Field> ConstraintSystem<F> for WitnessCollection<F> {
fn create_gate(
fn assign_advice(
&mut self,
sa: F,
sb: F,
sc: F,
sd: F,
sm: F,
f: impl Fn() -> Result<(F, F, F, F), Error>,
) -> Result<(Wire, Wire, Wire, Wire), Error> {
let (a, b, c, d) = f()?;
let tmp = Ok((
Wire::A(self.a.len()),
Wire::B(self.a.len()),
Wire::C(self.a.len()),
Wire::D(self.a.len()),
));
self.a.push(a);
self.b.push(b);
self.c.push(c);
self.d.push(d);
self.sa.push(sa);
self.sb.push(sb);
self.sc.push(sc);
self.sd.push(sd);
self.sm.push(sm);
tmp
wire: AdviceWire,
row: usize,
to: impl FnOnce() -> Result<F, Error>,
) -> Result<(), Error> {
*self
.advice
.get_mut(wire.0)
.and_then(|v| v.get_mut(row))
.ok_or(Error::BoundsFailure)? = to()?;
Ok(())
}
fn assign_fixed(
&mut self,
_: FixedWire,
_: usize,
_: impl FnOnce() -> Result<F, Error>,
) -> Result<(), Error> {
// We only care about advice wires here
Ok(())
}
// fn copy(&mut self, left: Wire, right: Wire) {
// unimplemented!()
// }
}
let mut meta = MetaCircuit::default();
let config = ConcreteCircuit::configure(&mut meta);
let mut witness = WitnessCollection {
a: vec![],
b: vec![],
c: vec![],
d: vec![],
sa: vec![],
sb: vec![],
sc: vec![],
sd: vec![],
sm: vec![],
advice: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_advice_wires],
};
// 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.
let mut transcript = HBase::init(C::Base::one());
if witness.a.len() > params.n as usize {
// The polynomial commitment does not support a high enough degree
// polynomial to commit to our wires because this circuit has too
// many gates.
return Err(Error::IncompatibleParams);
// Compute commitments to advice wire polynomials
let advice_blinds: Vec<_> = witness.advice.iter().map(|_| C::Scalar::random()).collect();
let advice_commitments = witness
.advice
.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 (a_coset, a_poly) = domain.obtain_coset(witness.a);
let (b_coset, b_poly) = domain.obtain_coset(witness.b);
let (c_coset, c_poly) = domain.obtain_coset(witness.c);
let (d_coset, d_poly) = domain.obtain_coset(witness.d);
let advice_polys: Vec<_> = witness
.advice
.into_iter()
.map(|poly| domain.obtain_poly(poly))
.collect();
// (a * sa) + (b * sb) + (a * sm * b) + (d * sd) - (c * sc)
let mut h_poly = Vec::with_capacity(a_coset.len());
for ((((((((a, b), c), d), sa), sb), sc), sd), sm) in a_coset
let advice_cosets: Vec<_> = meta
.advice_queries
.iter()
.zip(b_coset.iter())
.zip(c_coset.iter())
.zip(d_coset.iter())
.zip(srs.sa.0.iter())
.zip(srs.sb.0.iter())
.zip(srs.sc.0.iter())
.zip(srs.sd.0.iter())
.zip(srs.sm.0.iter())
{
h_poly.push((*a) * sa + &((*b) * sb) + &((*a) * sm * b) + &((*d) * sd) - &((*c) * sc));
.map(|&(wire, at)| {
let poly = advice_polys[wire.0].clone();
domain.obtain_coset(poly, at)
})
.collect();
// Obtain challenge for keeping all separate gates linearly independent
let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
// 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.
@ -151,7 +162,7 @@ impl<C: CurveAffine> Proof<C> {
.map(|v| v.to_vec())
.collect::<Vec<_>>();
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
let h_commitments: Vec<_> = h_pieces
@ -165,39 +176,44 @@ impl<C: CurveAffine> Proof<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
let a_eval_x = eval_polynomial(&a_poly, x);
let b_eval_x = eval_polynomial(&b_poly, x);
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
// Evaluate polynomials at omega^i x_3
let advice_evals: Vec<_> = meta
.advice_queries
.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();
// We set up a second transcript on the scalar field to hash in openings of
// our polynomial commitments.
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
for eval in h_evals_x.iter() {
// Hash each advice evaluation
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);
}
@ -205,61 +221,146 @@ impl<C: CurveAffine> Proof<C> {
C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap();
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();
let mut q_poly = h_pieces[0].clone();
let mut q_blind = h_blinds[0];
// Collapse openings at same points together into single openings using
// x_4 challenge.
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| {
for (a, q) in poly.iter().zip(q_poly.iter_mut()) {
*q = (*q * &y) + a;
}
q_commitment = (q_commitment * y) + &commitment.to_projective();
q_blind = (q_blind * &y) + blind;
let mut accumulate = |point_index: usize, new_poly: &Vec<_>, blind, eval| {
q_polys[point_index]
.as_mut()
.map(|poly| {
parallelize(poly, |q, start| {
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
.iter()
.zip(h_blinds.iter())
.zip(h_commitments.iter())
.skip(1)
{
accumulate(&poly, blind, *commitment);
for (query_index, &(wire, ref at)) in meta.advice_queries.iter().enumerate() {
let point_index = (*meta.rotations.get(at).unwrap()).0;
accumulate(
point_index,
&advice_polys[wire.0],
advice_blinds[wire.0],
advice_evals[query_index],
);
}
accumulate(&a_poly, &a_blind, a_commitment);
accumulate(&b_poly, &b_blind, b_commitment);
accumulate(&c_poly, &c_blind, c_commitment);
accumulate(&d_poly, &d_blind, d_commitment);
accumulate(&srs.sa.1, &Field::one(), srs.sa_commitment);
accumulate(&srs.sb.1, &Field::one(), srs.sb_commitment);
accumulate(&srs.sc.1, &Field::one(), srs.sc_commitment);
accumulate(&srs.sd.1, &Field::one(), srs.sd_commitment);
accumulate(&srs.sm.1, &Field::one(), srs.sm_commitment);
for (query_index, &(wire, ref at)) in meta.fixed_queries.iter().enumerate() {
let point_index = (*meta.rotations.get(at).unwrap()).0;
accumulate(
point_index,
&srs.fixed_polys[wire.0],
C::Scalar::one(),
fixed_evals[query_index],
);
}
// 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 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)?;
Ok(Proof {
a_commitment,
b_commitment,
c_commitment,
d_commitment,
advice_commitments,
h_commitments,
a_eval_x,
b_eval_x,
c_eval_x,
d_eval_x,
sa_eval_x,
sb_eval_x,
sc_eval_x,
sd_eval_x,
sm_eval_x,
h_evals_x,
advice_evals,
fixed_evals,
h_evals,
f_commitment,
q_evals,
opening,
})
}

View file

@ -1,7 +1,7 @@
use super::{
circuit::{Circuit, ConstraintSystem, Wire},
circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit},
domain::EvaluationDomain,
Error, GATE_DEGREE, SRS,
Error, SRS,
};
use crate::arithmetic::{Curve, CurveAffine, Field};
use crate::polycommit::Params;
@ -14,92 +14,80 @@ impl<C: CurveAffine> SRS<C> {
circuit: &ConcreteCircuit,
) -> Result<Self, Error> {
struct Assembly<F: Field> {
sa: Vec<F>,
sb: Vec<F>,
sc: Vec<F>,
sd: Vec<F>,
sm: Vec<F>,
fixed: Vec<Vec<F>>,
}
impl<F: Field> ConstraintSystem<F> for Assembly<F> {
fn create_gate(
fn assign_advice(
&mut self,
sa: F,
sb: F,
sc: F,
sd: F,
sm: F,
_: impl Fn() -> Result<(F, F, F, F), Error>,
) -> Result<(Wire, Wire, Wire, Wire), Error> {
let tmp = Ok((
Wire::A(self.sa.len()),
Wire::B(self.sa.len()),
Wire::C(self.sa.len()),
Wire::D(self.sa.len()),
));
self.sa.push(sa);
self.sb.push(sb);
self.sc.push(sc);
self.sd.push(sd);
self.sm.push(sm);
tmp
_: AdviceWire,
_: usize,
_: impl FnOnce() -> Result<F, Error>,
) -> Result<(), Error> {
// We only care about fixed wires here
Ok(())
}
fn assign_fixed(
&mut self,
wire: FixedWire,
row: usize,
to: impl FnOnce() -> Result<F, Error>,
) -> Result<(), Error> {
*self
.fixed
.get_mut(wire.0)
.and_then(|v| v.get_mut(row))
.ok_or(Error::BoundsFailure)? = to()?;
Ok(())
}
}
let mut meta = MetaCircuit::default();
let config = ConcreteCircuit::configure(&mut meta);
let mut assembly: Assembly<C::Scalar> = Assembly {
sa: vec![],
sb: vec![],
sc: vec![],
sd: vec![],
sm: vec![],
fixed: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_fixed_wires],
};
// 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());
assembly.sb.resize(params.n as usize, C::Scalar::zero());
assembly.sc.resize(params.n as usize, C::Scalar::zero());
assembly.sd.resize(params.n as usize, C::Scalar::zero());
assembly.sm.resize(params.n as usize, C::Scalar::zero());
let fixed_commitments = assembly
.fixed
.iter()
.map(|poly| params.commit_lagrange(poly, C::Scalar::one()).to_affine())
.collect();
// Compute commitments to the fixed wire values
let sa_commitment = params
.commit_lagrange(&assembly.sa, C::Scalar::one())
.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 mut degree = 1;
for poly in meta.gates.iter() {
degree = std::cmp::max(degree, poly.degree());
}
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 sb = domain.obtain_coset(assembly.sb);
let sc = domain.obtain_coset(assembly.sc);
let sd = domain.obtain_coset(assembly.sd);
let sm = domain.obtain_coset(assembly.sm);
let fixed_polys: Vec<_> = assembly
.fixed
.into_iter()
.map(|poly| domain.obtain_poly(poly))
.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 {
sa,
sb,
sc,
sd,
sm,
sa_commitment,
sb_commitment,
sc_commitment,
sd_commitment,
sm_commitment,
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::polycommit::Params;
use crate::transcript::Hasher;
@ -13,35 +13,34 @@ impl<C: CurveAffine> Proof<C> {
// Create a transcript for obtaining Fiat-Shamir challenges.
let mut transcript = HBase::init(C::Base::one());
hash_point(&mut transcript, &self.a_commitment)
.expect("proof cannot contain points at infinity");
hash_point(&mut transcript, &self.b_commitment)
.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");
// Hash the prover's advice commitments into the transcript
for commitment in &self.advice_commitments {
hash_point(&mut transcript, 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 {
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
// our polynomial commitments.
// Hash together all the openings provided by the prover into a new
// transcript on the scalar field.
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);
}
@ -49,60 +48,136 @@ impl<C: CurveAffine> Proof<C> {
C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap();
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 mut expected_opening = self.h_evals_x[0];
{
let mut accumulate = |commitment: C, opening: C::Scalar| {
q_commitment = commitment.to_projective() + &(q_commitment * y);
expected_opening = opening + &(expected_opening * &y);
};
let evaluation: C::Scalar = poly.evaluate(
&|index| self.fixed_evals[index],
&|index| self.advice_evals[index],
&|a, b| a + &b,
&|a, b| a * &b,
&|a, scalar| a * &scalar,
);
for (commitment, eval) in self.h_commitments.iter().zip(self.h_evals_x.iter()).skip(1) {
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);
h_eval += &evaluation;
}
let q_commitment = q_commitment.to_affine();
let xn = x.pow(&[params.n as u64, 0, 0, 0]);
let xn = x_3.pow(&[params.n as u64, 0, 0, 0]);
// 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();
for eval in &self.h_evals_x {
h_eval_x += &(cur * eval);
for eval in &self.h_evals {
expected_h_eval += &(cur * eval);
cur *= &xn;
}
// Check that the circuit is satisfied.
// (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())
{
if h_eval != (expected_h_eval * &(xn - &C::Scalar::one())) {
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(
&self.opening,
&mut transcript,
x,
&q_commitment,
expected_opening,
x_6,
&f_commitment.to_affine(),
f_eval,
)
}
}