use std::borrow::Cow;
use std::iter::{successors, zip};
use std::ops::Deref;
use educe::Educe;
use itertools::Itertools;
use num_traits::{One, Zero};
use thiserror::Error;
use super::gkr_verifier::{GkrArtifact, GkrBatchProof, GkrMask};
use super::mle::{Mle, MleOps};
use super::sumcheck::MultivariatePolyOracle;
use super::utils::{eq, random_linear_combination, UnivariatePoly};
use crate::core::channel::Channel;
use crate::core::fields::m31::BaseField;
use crate::core::fields::qm31::SecureField;
use crate::core::fields::{Field, FieldExpOps};
use crate::prover::backend::{Col, Column, ColumnOps, CpuBackend};
use crate::prover::lookups::sumcheck;
pub trait GkrOps: MleOps<BaseField> + MleOps<SecureField> {
fn gen_eq_evals(y: &[SecureField], v: SecureField) -> Mle<Self, SecureField>;
fn next_layer(layer: &Layer<Self>) -> Layer<Self>;
fn sum_as_poly_in_first_variable(
h: &GkrMultivariatePolyOracle<'_, Self>,
claim: SecureField,
) -> UnivariatePoly<SecureField>;
}
#[derive(Educe)]
#[educe(Debug, Clone)]
pub struct EqEvals<B: ColumnOps<SecureField>> {
y: Vec<SecureField>,
evals: Mle<B, SecureField>,
}
impl<B: GkrOps> EqEvals<B> {
pub fn generate(y: &[SecureField]) -> Self {
let y = y.to_vec();
if y.is_empty() {
let evals = Mle::new([SecureField::one()].into_iter().collect());
return Self { evals, y };
}
let evals = B::gen_eq_evals(&y[1..], eq(&[SecureField::zero()], &[y[0]]));
assert_eq!(evals.len(), 1 << (y.len() - 1));
Self { evals, y }
}
pub fn y(&self) -> &[SecureField] {
&self.y
}
}
impl<B: ColumnOps<SecureField>> Deref for EqEvals<B> {
type Target = Col<B, SecureField>;
fn deref(&self) -> &Col<B, SecureField> {
&self.evals
}
}
#[derive(Educe)]
#[educe(Debug, Clone)]
pub enum Layer<B: GkrOps> {
GrandProduct(Mle<B, SecureField>),
LogUpGeneric {
numerators: Mle<B, SecureField>,
denominators: Mle<B, SecureField>,
},
LogUpMultiplicities {
numerators: Mle<B, BaseField>,
denominators: Mle<B, SecureField>,
},
LogUpSingles {
denominators: Mle<B, SecureField>,
},
}
impl<B: GkrOps> Layer<B> {
pub fn n_variables(&self) -> usize {
match self {
Self::GrandProduct(mle)
| Self::LogUpSingles { denominators: mle }
| Self::LogUpMultiplicities {
denominators: mle, ..
}
| Self::LogUpGeneric {
denominators: mle, ..
} => mle.n_variables(),
}
}
fn is_output_layer(&self) -> bool {
self.n_variables() == 0
}
pub fn next_layer(&self) -> Option<Self> {
if self.is_output_layer() {
return None;
}
Some(B::next_layer(self))
}
fn try_into_output_layer_values(self) -> Result<Vec<SecureField>, NotOutputLayerError> {
if !self.is_output_layer() {
return Err(NotOutputLayerError);
}
Ok(match self {
Layer::LogUpSingles { denominators } => {
let numerator = SecureField::one();
let denominator = denominators.at(0);
vec![numerator, denominator]
}
Layer::LogUpMultiplicities {
numerators,
denominators,
} => {
let numerator = numerators.at(0).into();
let denominator = denominators.at(0);
vec![numerator, denominator]
}
Layer::LogUpGeneric {
numerators,
denominators,
} => {
let numerator = numerators.at(0);
let denominator = denominators.at(0);
vec![numerator, denominator]
}
Layer::GrandProduct(col) => {
vec![col.at(0)]
}
})
}
fn fix_first_variable(self, x0: SecureField) -> Self {
if self.n_variables() == 0 {
return self;
}
match self {
Self::GrandProduct(mle) => Self::GrandProduct(mle.fix_first_variable(x0)),
Self::LogUpGeneric {
numerators,
denominators,
} => Self::LogUpGeneric {
numerators: numerators.fix_first_variable(x0),
denominators: denominators.fix_first_variable(x0),
},
Self::LogUpMultiplicities {
numerators,
denominators,
} => Self::LogUpGeneric {
numerators: numerators.fix_first_variable(x0),
denominators: denominators.fix_first_variable(x0),
},
Self::LogUpSingles { denominators } => Self::LogUpSingles {
denominators: denominators.fix_first_variable(x0),
},
}
}
fn into_multivariate_poly(
self,
lambda: SecureField,
eq_evals: &EqEvals<B>,
) -> GkrMultivariatePolyOracle<'_, B> {
GkrMultivariatePolyOracle {
eq_evals: Cow::Borrowed(eq_evals),
input_layer: self,
eq_fixed_var_correction: SecureField::one(),
lambda,
}
}
pub fn to_cpu(&self) -> Layer<CpuBackend> {
match self {
Layer::GrandProduct(mle) => Layer::GrandProduct(Mle::new(mle.to_cpu())),
Layer::LogUpGeneric {
numerators,
denominators,
} => Layer::LogUpGeneric {
numerators: Mle::new(numerators.to_cpu()),
denominators: Mle::new(denominators.to_cpu()),
},
Layer::LogUpMultiplicities {
numerators,
denominators,
} => Layer::LogUpMultiplicities {
numerators: Mle::new(numerators.to_cpu()),
denominators: Mle::new(denominators.to_cpu()),
},
Layer::LogUpSingles { denominators } => Layer::LogUpSingles {
denominators: Mle::new(denominators.to_cpu()),
},
}
}
}
#[derive(Debug)]
struct NotOutputLayerError;
pub struct GkrMultivariatePolyOracle<'a, B: GkrOps> {
pub eq_evals: Cow<'a, EqEvals<B>>,
pub input_layer: Layer<B>,
pub eq_fixed_var_correction: SecureField,
pub lambda: SecureField,
}
impl<B: GkrOps> MultivariatePolyOracle for GkrMultivariatePolyOracle<'_, B> {
fn n_variables(&self) -> usize {
self.input_layer.n_variables() - 1
}
fn sum_as_poly_in_first_variable(&self, claim: SecureField) -> UnivariatePoly<SecureField> {
B::sum_as_poly_in_first_variable(self, claim)
}
fn fix_first_variable(self, challenge: SecureField) -> Self {
if self.is_constant() {
return self;
}
let z0 = self.eq_evals.y()[self.eq_evals.y().len() - self.n_variables()];
let eq_fixed_var_correction = self.eq_fixed_var_correction * eq(&[challenge], &[z0]);
Self {
eq_evals: self.eq_evals,
eq_fixed_var_correction,
input_layer: self.input_layer.fix_first_variable(challenge),
lambda: self.lambda,
}
}
}
impl<'a, B: GkrOps> GkrMultivariatePolyOracle<'a, B> {
fn is_constant(&self) -> bool {
self.n_variables() == 0
}
fn try_into_mask(self) -> Result<GkrMask, NotConstantPolyError> {
if !self.is_constant() {
return Err(NotConstantPolyError);
}
let columns = match self.input_layer {
Layer::GrandProduct(mle) => vec![mle.to_cpu().try_into().unwrap()],
Layer::LogUpGeneric {
numerators,
denominators,
} => {
let numerators = numerators.to_cpu().try_into().unwrap();
let denominators = denominators.to_cpu().try_into().unwrap();
vec![numerators, denominators]
}
Layer::LogUpMultiplicities { .. } => unimplemented!(),
Layer::LogUpSingles { denominators } => {
let numerators = [SecureField::one(); 2];
let denominators = denominators.to_cpu().try_into().unwrap();
vec![numerators, denominators]
}
};
Ok(GkrMask::new(columns))
}
pub fn to_cpu(&self) -> GkrMultivariatePolyOracle<'a, CpuBackend> {
let n_eq_evals = 1 << (self.n_variables() - 1);
let eq_evals = Cow::Owned(EqEvals {
evals: Mle::new((0..n_eq_evals).map(|i| self.eq_evals.at(i)).collect()),
y: self.eq_evals.y.to_vec(),
});
GkrMultivariatePolyOracle {
eq_evals,
eq_fixed_var_correction: self.eq_fixed_var_correction,
input_layer: self.input_layer.to_cpu(),
lambda: self.lambda,
}
}
}
#[derive(Debug, Error)]
#[error("polynomial is not constant")]
pub struct NotConstantPolyError;
pub fn prove_batch<B: GkrOps>(
channel: &mut impl Channel,
input_layer_by_instance: Vec<Layer<B>>,
) -> (GkrBatchProof, GkrArtifact) {
let n_instances = input_layer_by_instance.len();
let n_layers_by_instance = input_layer_by_instance
.iter()
.map(|l| l.n_variables())
.collect_vec();
let n_layers = *n_layers_by_instance.iter().max().unwrap();
let mut layers_by_instance = input_layer_by_instance
.into_iter()
.map(|input_layer| gen_layers(input_layer).into_iter().rev())
.collect_vec();
let mut output_claims_by_instance = vec![None; n_instances];
let mut layer_masks_by_instance = (0..n_instances).map(|_| Vec::new()).collect_vec();
let mut sumcheck_proofs = Vec::new();
let mut ood_point = Vec::new();
let mut claims_to_verify_by_instance = vec![None; n_instances];
for layer in 0..n_layers {
let n_remaining_layers = n_layers - layer;
for (instance, layers) in layers_by_instance.iter_mut().enumerate() {
if n_layers_by_instance[instance] == n_remaining_layers {
let output_layer = layers.next().unwrap();
let output_layer_values = output_layer.try_into_output_layer_values().unwrap();
claims_to_verify_by_instance[instance] = Some(output_layer_values.clone());
output_claims_by_instance[instance] = Some(output_layer_values);
}
}
for claims_to_verify in claims_to_verify_by_instance.iter().flatten() {
channel.mix_felts(claims_to_verify);
}
let eq_evals = EqEvals::generate(&ood_point);
let sumcheck_alpha = channel.draw_secure_felt();
let instance_lambda = channel.draw_secure_felt();
let mut sumcheck_oracles = Vec::new();
let mut sumcheck_claims = Vec::new();
let mut sumcheck_instances = Vec::new();
for (instance, claims_to_verify) in claims_to_verify_by_instance.iter().enumerate() {
if let Some(claims_to_verify) = claims_to_verify {
let layer = layers_by_instance[instance].next().unwrap();
sumcheck_oracles.push(layer.into_multivariate_poly(instance_lambda, &eq_evals));
sumcheck_claims.push(random_linear_combination(claims_to_verify, instance_lambda));
sumcheck_instances.push(instance);
}
}
let (sumcheck_proof, sumcheck_ood_point, constant_poly_oracles, _) =
sumcheck::prove_batch(sumcheck_claims, sumcheck_oracles, sumcheck_alpha, channel);
sumcheck_proofs.push(sumcheck_proof);
let masks = constant_poly_oracles
.into_iter()
.map(|oracle| oracle.try_into_mask().unwrap())
.collect_vec();
for (&instance, mask) in zip(&sumcheck_instances, &masks) {
channel.mix_felts(mask.columns().as_flattened());
layer_masks_by_instance[instance].push(mask.clone());
}
let challenge = channel.draw_secure_felt();
ood_point = sumcheck_ood_point;
ood_point.push(challenge);
for (instance, mask) in zip(sumcheck_instances, masks) {
claims_to_verify_by_instance[instance] = Some(mask.reduce_at_point(challenge));
}
}
let output_claims_by_instance = output_claims_by_instance
.into_iter()
.map(Option::unwrap)
.collect();
let claims_to_verify_by_instance = claims_to_verify_by_instance
.into_iter()
.map(Option::unwrap)
.collect();
let proof = GkrBatchProof {
sumcheck_proofs,
layer_masks_by_instance,
output_claims_by_instance,
};
let artifact = GkrArtifact {
ood_point,
claims_to_verify_by_instance,
n_variables_by_instance: n_layers_by_instance,
};
(proof, artifact)
}
fn gen_layers<B: GkrOps>(input_layer: Layer<B>) -> Vec<Layer<B>> {
let n_variables = input_layer.n_variables();
let layers = successors(Some(input_layer), |layer| layer.next_layer()).collect_vec();
assert_eq!(layers.len(), n_variables + 1);
layers
}
pub fn correct_sum_as_poly_in_first_variable(
f_at_0: SecureField,
f_at_2: SecureField,
claim: SecureField,
y: &[SecureField],
k: usize,
) -> UnivariatePoly<SecureField> {
assert_ne!(k, 0);
let n = y.len();
assert!(k <= n);
let a_const = eq(&vec![SecureField::zero(); n - k + 1], &y[..n - k + 1]).inverse();
let b_const = (SecureField::one() - y[n - k]) / (SecureField::one() - y[n - k].double());
let r_at_0 = f_at_0 * eq(&[SecureField::zero()], &[y[n - k]]) * a_const;
let r_at_1 = claim - r_at_0;
let r_at_2 = f_at_2 * eq(&[BaseField::from(2).into()], &[y[n - k]]) * a_const;
let r_at_b = SecureField::zero();
UnivariatePoly::interpolate_lagrange(
&[
SecureField::zero(),
SecureField::one(),
SecureField::from(BaseField::from(2)),
b_const,
],
&[r_at_0, r_at_1, r_at_2, r_at_b],
)
}