Browse Source

23 permutation checks (#32)

main
zhenfei 2 years ago
committed by GitHub
parent
commit
1d12a3e582
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 891 additions and 235 deletions
  1. +93
    -5
      poly-iop/benches/bench.rs
  2. +2
    -1
      poly-iop/readme.md
  3. +5
    -1
      poly-iop/src/lib.rs
  4. +587
    -172
      poly-iop/src/perm_check/mod.rs
  5. +143
    -0
      poly-iop/src/perm_check/util.rs
  6. +0
    -10
      poly-iop/src/structs.rs
  7. +23
    -8
      poly-iop/src/sum_check/mod.rs
  8. +11
    -11
      poly-iop/src/sum_check/verifier.rs
  9. +2
    -2
      poly-iop/src/virtual_poly.rs
  10. +25
    -25
      poly-iop/src/zero_check/mod.rs

+ 93
- 5
poly-iop/benches/bench.rs

@ -1,9 +1,15 @@
use ark_bls12_381::Fr;
use ark_std::test_rng;
use poly_iop::{PolyIOP, PolyIOPErrors, SumCheck, VirtualPolynomial, ZeroCheck};
use std::time::Instant;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::{test_rng, UniformRand};
use poly_iop::{
identity_permutation_mle, PermutationCheck, PolyIOP, PolyIOPErrors, SumCheck, VPAuxInfo,
VirtualPolynomial, ZeroCheck,
};
use std::{marker::PhantomData, time::Instant};
fn main() -> Result<(), PolyIOPErrors> {
bench_permutation_check()?;
println!("\n\n");
bench_sum_check()?;
println!("\n\n");
bench_zero_check()
@ -105,13 +111,14 @@ fn bench_zero_check() -> Result<(), PolyIOPErrors> {
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let subclaim =
<PolyIOP<Fr> as ZeroCheck<Fr>>::verify(&proof, &poly_info, &mut transcript)?.0;
<PolyIOP<Fr> as ZeroCheck<Fr>>::verify(&proof, &poly_info, &mut transcript)?
.sum_check_sub_claim;
assert!(
poly.evaluate(&subclaim.point)? == subclaim.expected_evaluation,
"wrong subclaim"
);
println!(
"zero check verification time for {} variables and {} degree:: {} ns",
"zero check verification time for {} variables and {} degree: {} ns",
nv,
degree,
start.elapsed().as_nanos() / repetition as u128
@ -123,3 +130,84 @@ fn bench_zero_check() -> Result<(), PolyIOPErrors> {
}
Ok(())
}
fn bench_permutation_check() -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
for nv in 4..20 {
let repetition = if nv < 10 {
100
} else if nv < 20 {
50
} else {
10
};
let w = DenseMultilinearExtension::rand(nv, &mut rng);
// s_perm is the identity map
let s_perm = identity_permutation_mle(nv);
let proof = {
let start = Instant::now();
let mut transcript = <PolyIOP<Fr> as PermutationCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let mut challenge =
<PolyIOP<Fr> as PermutationCheck<Fr>>::generate_challenge(&mut transcript)?;
let prod_x_and_aux = <PolyIOP<Fr> as PermutationCheck<Fr>>::compute_products(
&challenge, &w, &w, &s_perm,
)?;
let prod_x_binding = mock_commit(&prod_x_and_aux[0]);
<PolyIOP<Fr> as PermutationCheck<Fr>>::update_challenge(
&mut challenge,
&mut transcript,
&prod_x_binding,
)?;
let proof = <PolyIOP<Fr> as PermutationCheck<Fr>>::prove(
&prod_x_and_aux,
&challenge,
&mut transcript,
)?;
println!(
"permutation check proving time for {} variables: {} ns",
nv,
start.elapsed().as_nanos() / repetition as u128
);
proof
};
{
let poly_info = VPAuxInfo {
max_degree: 2,
num_variables: nv,
phantom: PhantomData::default(),
};
let start = Instant::now();
let mut transcript = <PolyIOP<Fr> as PermutationCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let _subclaim =
<PolyIOP<Fr> as PermutationCheck<Fr>>::verify(&proof, &poly_info, &mut transcript)?;
println!(
"permutation check verification time for {} variables: {} ns",
nv,
start.elapsed().as_nanos() / repetition as u128
);
}
println!("====================================");
}
Ok(())
}
fn mock_commit(_f: &DenseMultilinearExtension<Fr>) -> Fr {
let mut rng = test_rng();
Fr::rand(&mut rng)
}

+ 2
- 1
poly-iop/readme.md

@ -4,4 +4,5 @@ Poly IOP
Implements the following protocols
- [x] sum checks
- [x] zero checks
- [x] zero checks
- [x] permutation checks

+ 5
- 1
poly-iop/src/lib.rs

@ -11,8 +11,12 @@ mod virtual_poly;
mod zero_check;
pub use errors::PolyIOPErrors;
pub use perm_check::{
util::{identity_permutation_mle, random_permutation_mle},
PermutationCheck,
};
pub use sum_check::SumCheck;
pub use virtual_poly::VirtualPolynomial;
pub use virtual_poly::{VPAuxInfo, VirtualPolynomial};
pub use zero_check::ZeroCheck;
#[derive(Clone, Debug, Default, Copy)]

+ 587
- 172
poly-iop/src/perm_check/mod.rs

@ -1,205 +1,618 @@
//! This module implements the permutation check protocol.
//! Main module for the Permutation Check protocol
use crate::{utils::get_index, PolyIOPErrors};
use crate::{
errors::PolyIOPErrors, perm_check::util::compute_prod_0, structs::IOPProof,
transcript::IOPTranscript, utils::get_index, PolyIOP, VirtualPolynomial, ZeroCheck,
};
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, start_timer};
use std::rc::Rc;
/// Compute `prod(0,x) := prod(0, x1, …, xn)` which is the MLE over the
/// evaluations of the following polynomial on the boolean hypercube {0,1}^n:
pub mod util;
/// A PermutationCheck is derived from ZeroCheck.
///
/// (w(x) + \beta s_id(x) + \gamma)/(w(x) + \beta s_perm(x) + \gamma)
/// A Permutation Check IOP takes the following steps:
///
/// where
/// - beta and gamma are challenges
/// - w(x), s_id(x), s_perm(x) are mle-s
/// Inputs:
/// - f(x)
/// - g(x)
/// - permutation s_perm(x)
///
/// The caller needs to check num_vars matches in w/s_id/s_perm
/// Cost: linear in N.
#[allow(dead_code)]
// TODO: remove
fn compute_prod_0<F: PrimeField>(
beta: &F,
gamma: &F,
w: &DenseMultilinearExtension<F>,
s_id: &DenseMultilinearExtension<F>,
s_perm: &DenseMultilinearExtension<F>,
) -> Result<DenseMultilinearExtension<F>, PolyIOPErrors> {
let start = start_timer!(|| "compute prod(1,x)");
let num_vars = w.num_vars;
let eval: Vec<F> = w
.iter()
.zip(s_id.iter().zip(s_perm.iter()))
.map(|(wi, (s_id_i, s_perm_i))| {
let tmp = *wi + *gamma;
(tmp + *beta * *s_id_i) / (tmp + *beta * *s_perm_i)
})
.collect();
/// Steps:
/// 1. `generate_challenge` from current transcript (generate beta, gamma)
/// 2. `compute_product` to build `prod(x)` etc. from f, g and s_perm
/// 3. push a commitment of `prod(x)` to the transcript (done by the snark
/// caller)
/// 4. `update_challenge` with the updated transcript (generate alpha)
/// 5. `prove` to generate the proof
pub trait PermutationCheck<F: PrimeField>: ZeroCheck<F> {
type PermutationCheckSubClaim;
type PermutationChallenge;
/// Initialize the system with a transcript
///
/// This function is optional -- in the case where a PermutationCheck is
/// an building block for a more complex protocol, the transcript
/// may be initialized by this complex protocol, and passed to the
/// PermutationCheck prover/verifier.
fn init_transcript() -> Self::Transcript;
/// Step 1 of the IOP.
/// Generate challenge beta and gamma from a transcript.
fn generate_challenge(
transcript: &mut Self::Transcript,
) -> Result<Self::PermutationChallenge, PolyIOPErrors>;
/// Step 4 of the IOP.
/// Update the challenge with alpha; returns an error if
/// alpha already exists.
fn update_challenge(
challenge: &mut Self::PermutationChallenge,
transcript: &mut Self::Transcript,
prod_x_binding: &F,
) -> Result<(), PolyIOPErrors>;
/// Step 2 of the IOP.
/// Compute the following 7 polynomials
/// - prod(x)
/// - prod(0, x)
/// - prod(1, x)
/// - prod(x, 0)
/// - prod(x, 1)
/// - numerator
/// - denominator
///
/// where
/// - `prod(0,x) := prod(0, x0, x1, …, xn)` which is the MLE over the
/// evaluations of the following polynomial on the boolean hypercube
/// {0,1}^n:
///
/// (f(x) + \beta s_id(x) + \gamma)/(g(x) + \beta s_perm(x) + \gamma)
///
/// where
/// - beta and gamma are challenges
/// - f(x), g(x), s_id(x), s_perm(x) are mle-s
///
/// - `prod(1,x) := prod(x, 0) * prod(x, 1)`
/// - numerator is the MLE for `f(x) + \beta s_id(x) + \gamma`
/// - denominator is the MLE for `g(x) + \beta s_perm(x) + \gamma`
///
/// The caller needs to check num_vars matches in f/g/s_id/s_perm
/// Cost: linear in N.
fn compute_products(
challenge: &Self::PermutationChallenge,
fx: &DenseMultilinearExtension<F>,
gx: &DenseMultilinearExtension<F>,
s_perm: &DenseMultilinearExtension<F>,
) -> Result<[DenseMultilinearExtension<F>; 7], PolyIOPErrors>;
/// Step 5 of the IOP.
///
/// Initialize the prover to argue that an MLE g(x) is a permutation of
/// MLE f(x) over a permutation given by s_perm.
/// Inputs:
/// - 7 MLEs from `Self::compute_products`
/// - challenge: `Self::Challenge` that has been updated
/// - transcript: a transcript that is used to generate the challenges beta
/// and gamma
/// Cost: O(N)
fn prove(
prod_x_and_aux_info: &[DenseMultilinearExtension<F>; 7],
challenge: &Self::PermutationChallenge,
transcript: &mut IOPTranscript<F>,
) -> Result<Self::Proof, PolyIOPErrors>;
/// Verify that an MLE g(x) is a permutation of
/// MLE f(x) over a permutation given by s_perm.
fn verify(
proof: &Self::Proof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::PermutationCheckSubClaim, PolyIOPErrors>;
}
let res = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval);
end_timer!(start);
Ok(res)
/// A permutation subclaim consists of
/// - A zero check IOP subclaim for Q(x) is 0, consists of the following:
/// (See `build_qx` for definition of Q(x).)
/// - the SubClaim from the SumCheck
/// - the initial challenge r which is used to build eq(x, r) in ZeroCheck
/// - A final query for `prod(1, ..., 1, 0) = 1`.
// Note that this final query is in fact a constant that
// is independent from the proof. So we should avoid
// (de)serialize it.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PermutationCheckSubClaim<F: PrimeField, ZC: ZeroCheck<F>> {
// the SubClaim from the ZeroCheck
zero_check_sub_claim: ZC::ZeroCheckSubClaim,
// final query which consists of
// - the vector `(1, ..., 1, 0)`
// - the evaluation `1`
final_query: (Vec<F>, F),
}
/// Compute the following 5 polynomials
/// - prod(x)
/// - prod(0, x)
/// - prod(1, x)
/// - prod(x, 0)
/// - prod(x, 1)
///
/// where
/// - `prod(0,x) := prod(0, x0, x1, …, xn)` which is the MLE over the
/// evaluations of the following polynomial on the boolean hypercube {0,1}^n:
///
/// (w(x) + \beta s_id(x) + \gamma)/(w(x) + \beta s_perm(x) + \gamma)
pub struct PermutationChallenge<F: PrimeField> {
alpha: Option<F>,
beta: F,
gamma: F,
}
/// A PermutationCheck is derived from ZeroCheck.
///
/// where
/// - beta and gamma are challenges
/// - w(x), s_id(x), s_perm(x) are mle-s
/// A Permutation Check IOP takes the following steps:
///
/// - `prod(1,x) := prod(x, 0) * prod(x, 1)`
/// Inputs:
/// - f(x)
/// - g(x)
/// - permutation s_perm(x)
///
/// Returns an error when the num_vars in w/s_id/s_perm does not match
/// Cost: linear in N.
#[allow(dead_code)]
// TODO: remove
fn compute_products<F: PrimeField>(
beta: &F,
gamma: &F,
w: &DenseMultilinearExtension<F>,
s_id: &DenseMultilinearExtension<F>,
s_perm: &DenseMultilinearExtension<F>,
) -> Result<[DenseMultilinearExtension<F>; 5], PolyIOPErrors> {
let start = start_timer!(|| "compute all prod polynomial");
let num_vars = w.num_vars;
if num_vars != s_id.num_vars || num_vars != s_perm.num_vars {
return Err(PolyIOPErrors::InvalidParameters(
"num of variables do not match".to_string(),
));
/// Steps:
/// 1. `generate_challenge` from current transcript (generate beta, gamma)
/// 2. `compute_product` to build `prod(x)` etc. from f, g and s_perm
/// 3. push a commitment of `prod(x)` to the transcript (done by the snark
/// caller)
/// 4. `update_challenge` with the updated transcript (generate alpha)
/// 5. `prove` to generate the proof
impl<F: PrimeField> PermutationCheck<F> for PolyIOP<F> {
/// A Permutation SubClaim is indeed a ZeroCheck SubClaim that consists of
/// - the SubClaim from the SumCheck
/// - the initial challenge r which is used to build eq(x, r)
type PermutationCheckSubClaim = PermutationCheckSubClaim<F, Self>;
type PermutationChallenge = PermutationChallenge<F>;
/// Initialize the system with a transcript
///
/// This function is optional -- in the case where a PermutationCheck is
/// an building block for a more complex protocol, the transcript
/// may be initialized by this complex protocol, and passed to the
/// PermutationCheck prover/verifier.
fn init_transcript() -> Self::Transcript {
IOPTranscript::<F>::new(b"Initializing PermutationCheck transcript")
}
// ===================================
// prod(0, x)
// ===================================
let prod_0x = compute_prod_0(beta, gamma, w, s_id, s_perm)?;
// ===================================
// prod(1, x)
// ===================================
//
// `prod(1, x)` can be computed via recursing the following formula for 2^n-1
// times
//
// `prod(1, x_1, ..., x_n) :=
// prod(x_1, x_2, ..., x_n, 0) * prod(x_1, x_2, ..., x_n, 1)`
//
// At any given step, the right hand side of the equation
// is available via either eval_0x or the current view of eval_1x
let eval_0x = &prod_0x.evaluations;
let mut eval_1x = vec![];
for x in 0..(1 << num_vars) - 1 {
// sign will decide if the evaluation should be looked up from eval_0x or
// eval_1x; x_zero_index is the index for the evaluation (x_2, ..., x_n,
// 0); x_one_index is the index for the evaluation (x_2, ..., x_n, 1);
let (x_zero_index, x_one_index, sign) = get_index(x, num_vars);
if !sign {
eval_1x.push(eval_0x[x_zero_index] * eval_0x[x_one_index]);
} else {
// sanity check: if we are trying to look up from the eval_1x table,
// then the target index must already exist
if x_zero_index >= eval_1x.len() || x_one_index >= eval_1x.len() {
return Err(PolyIOPErrors::ShouldNotArrive);
}
eval_1x.push(eval_1x[x_zero_index] * eval_1x[x_one_index]);
/// Step 1 of the IOP.
/// Generate challenge beta and gamma from a transcript.
fn generate_challenge(
transcript: &mut Self::Transcript,
) -> Result<Self::PermutationChallenge, PolyIOPErrors> {
Ok(Self::PermutationChallenge {
beta: transcript.get_and_append_challenge(b"beta")?,
gamma: transcript.get_and_append_challenge(b"gamma")?,
alpha: None,
})
}
/// Step 4 of the IOP.
/// Update the challenge with alpha; returns an error if
/// alpha already exists.
fn update_challenge(
challenge: &mut Self::PermutationChallenge,
transcript: &mut Self::Transcript,
prod_x_binding: &F,
) -> Result<(), PolyIOPErrors> {
if challenge.alpha.is_some() {
return Err(PolyIOPErrors::InvalidTranscript(
"alpha should not be sampled at the current stage".to_string(),
));
}
transcript.append_field_element(b"prod(x)", prod_x_binding)?;
challenge.alpha = Some(transcript.get_and_append_challenge(b"alpha")?);
Ok(())
}
// prod(1, 1, ..., 1) := 0
eval_1x.push(F::zero());
// ===================================
// prod(x)
// ===================================
// prod(x)'s evaluation is indeed `e := [eval_0x[..], eval_1x[..]].concat()`
let eval = [eval_0x.as_slice(), eval_1x.as_slice()].concat();
// ===================================
// prod(x, 0) and prod(x, 1)
// ===================================
//
// now we compute eval_x0 and eval_x1
// eval_0x will be the odd coefficients of eval
// and eval_1x will be the even coefficients of eval
let mut eval_x0 = vec![];
let mut eval_x1 = vec![];
for (x, &prod_x) in eval.iter().enumerate() {
if x & 1 == 0 {
eval_x0.push(prod_x);
} else {
eval_x1.push(prod_x);
/// Step 2 of the IOP.
/// Compute the following 7 polynomials
/// - prod(x)
/// - prod(0, x)
/// - prod(1, x)
/// - prod(x, 0)
/// - prod(x, 1)
/// - numerator
/// - denominator
///
/// where
/// - `prod(0,x) := prod(0, x0, x1, …, xn)` which is the MLE over the
/// evaluations of the following polynomial on the boolean hypercube
/// {0,1}^n:
///
/// (f(x) + \beta s_id(x) + \gamma)/(g(x) + \beta s_perm(x) + \gamma)
///
/// where
/// - beta and gamma are challenges
/// - f(x), g(x), s_id(x), s_perm(x) are mle-s
///
/// - `prod(1,x) := prod(x, 0) * prod(x, 1)`
/// - numerator is the MLE for `f(x) + \beta s_id(x) + \gamma`
/// - denominator is the MLE for `g(x) + \beta s_perm(x) + \gamma`
///
/// The caller needs to check num_vars matches in f/g/s_id/s_perm
/// Cost: linear in N.
fn compute_products(
challenge: &Self::PermutationChallenge,
fx: &DenseMultilinearExtension<F>,
gx: &DenseMultilinearExtension<F>,
s_perm: &DenseMultilinearExtension<F>,
) -> Result<[DenseMultilinearExtension<F>; 7], PolyIOPErrors> {
let start = start_timer!(|| "compute all prod polynomial");
if challenge.alpha.is_some() {
return Err(PolyIOPErrors::InvalidTranscript(
"alpha is already sampled".to_string(),
));
}
let num_vars = fx.num_vars;
// ===================================
// prod(0, x)
// ===================================
let (prod_0x, numerator, denominator) =
compute_prod_0(&challenge.beta, &challenge.gamma, fx, gx, s_perm)?;
// ===================================
// prod(1, x)
// ===================================
//
// `prod(1, x)` can be computed via recursing the following formula for 2^n-1
// times
//
// `prod(1, x_1, ..., x_n) :=
// prod(x_1, x_2, ..., x_n, 0) * prod(x_1, x_2, ..., x_n, 1)`
//
// At any given step, the right hand side of the equation
// is available via either eval_0x or the current view of eval_1x
let eval_0x = &prod_0x.evaluations;
let mut eval_1x = vec![];
for x in 0..(1 << num_vars) - 1 {
// sign will decide if the evaluation should be looked up from eval_0x or
// eval_1x; x_zero_index is the index for the evaluation (x_2, ..., x_n,
// 0); x_one_index is the index for the evaluation (x_2, ..., x_n, 1);
let (x_zero_index, x_one_index, sign) = get_index(x, num_vars);
if !sign {
eval_1x.push(eval_0x[x_zero_index] * eval_0x[x_one_index]);
} else {
// sanity check: if we are trying to look up from the eval_1x table,
// then the target index must already exist
if x_zero_index >= eval_1x.len() || x_one_index >= eval_1x.len() {
return Err(PolyIOPErrors::ShouldNotArrive);
}
eval_1x.push(eval_1x[x_zero_index] * eval_1x[x_one_index]);
}
}
// prod(1, 1, ..., 1) := 0
eval_1x.push(F::zero());
// ===================================
// prod(x)
// ===================================
// prod(x)'s evaluation is indeed `e := [eval_0x[..], eval_1x[..]].concat()`
let eval = [eval_0x.as_slice(), eval_1x.as_slice()].concat();
// ===================================
// prod(x, 0) and prod(x, 1)
// ===================================
//
// now we compute eval_x0 and eval_x1
// eval_0x will be the odd coefficients of eval
// and eval_1x will be the even coefficients of eval
let mut eval_x0 = vec![];
let mut eval_x1 = vec![];
for (x, &prod_x) in eval.iter().enumerate() {
if x & 1 == 0 {
eval_x0.push(prod_x);
} else {
eval_x1.push(prod_x);
}
}
let prod_1x = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_1x);
let prod_x0 = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_x0);
let prod_x1 = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_x1);
let prod = DenseMultilinearExtension::from_evaluations_vec(num_vars + 1, eval);
end_timer!(start);
Ok([
prod,
prod_0x,
prod_1x,
prod_x0,
prod_x1,
numerator,
denominator,
])
}
let prod_1x = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_1x);
let prod_x0 = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_x0);
let prod_x1 = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_x1);
let prod = DenseMultilinearExtension::from_evaluations_vec(num_vars + 1, eval);
/// Step 5 of the IOP.
///
/// Generate a proof to argue that an MLE g(x) is a permutation of
/// MLE f(x) over a permutation given by s_perm.
/// Inputs:
/// - 7 MLEs from `Self::compute_products(*, f, g, s_perm)`
/// - challenge: `Self::Challenge` that has been updated
/// - transcript: a transcript that is used to generate the challenges beta
/// and gamma
/// Cost: O(N)
fn prove(
prod_x_and_aux_info: &[DenseMultilinearExtension<F>; 7],
challenge: &Self::PermutationChallenge,
transcript: &mut IOPTranscript<F>,
) -> Result<Self::Proof, PolyIOPErrors> {
let alpha = match challenge.alpha {
Some(p) => p,
None => {
return Err(PolyIOPErrors::InvalidTranscript(
"alpha is not sampled yet".to_string(),
))
},
};
let (proof, _q_x) = prove_internal(prod_x_and_aux_info, &alpha, transcript)?;
Ok(proof)
}
/// Verify that an MLE g(x) is a permutation of an
/// MLE f(x) over a permutation given by s_perm.
fn verify(
proof: &Self::Proof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::PermutationCheckSubClaim, PolyIOPErrors> {
let start = start_timer!(|| "Permutation check verify");
// invoke the zero check on the iop_proof
let zero_check_sub_claim = <Self as ZeroCheck<F>>::verify(proof, aux_info, transcript)?;
let mut final_query = vec![F::one(); aux_info.num_variables];
final_query[aux_info.num_variables - 1] = F::zero();
let final_eval = F::one();
end_timer!(start);
Ok(PermutationCheckSubClaim {
zero_check_sub_claim,
final_query: (final_query, final_eval),
})
}
}
/// Step 5 of the IOP.
///
/// Generate a proof to argue that an MLE g(x) is a permutation of
/// MLE f(x) over a permutation given by s_perm.
/// Inputs:
/// - 7 MLEs from `Self::compute_products(*, f, g, s_perm)`
/// - challenge: `Self::Challenge` that has been updated
/// - transcript: a transcript that is used to generate the challenges beta and
/// gamma
///
/// Returns proof and Q(x) for testing purpose.
///
/// Cost: O(N)
fn prove_internal<F: PrimeField>(
prod_x_and_aux_info: &[DenseMultilinearExtension<F>; 7],
alpha: &F,
transcript: &mut IOPTranscript<F>,
) -> Result<(IOPProof<F>, VirtualPolynomial<F>), PolyIOPErrors> {
let start = start_timer!(|| "Permutation check prove");
// prods consists of the following:
// - prod(x)
// - prod(0, x)
// - prod(1, x)
// - prod(x, 0)
// - prod(x, 1)
// - numerator
// - denominator
let prod_0x = Rc::new(prod_x_and_aux_info[1].clone());
let prod_1x = Rc::new(prod_x_and_aux_info[2].clone());
let prod_x1 = Rc::new(prod_x_and_aux_info[3].clone());
let prod_x0 = Rc::new(prod_x_and_aux_info[4].clone());
let numerator = Rc::new(prod_x_and_aux_info[5].clone());
let denominator = Rc::new(prod_x_and_aux_info[6].clone());
// compute (g(x) + beta * s_perm(x) + gamma) * prod(0, x) * alpha
// which is prods[6] * prod[1] * alpha
let mut q_x = VirtualPolynomial::new_from_mle(denominator, F::one());
q_x.mul_by_mle(prod_0x, *alpha)?;
// (g(x) + beta * s_perm(x) + gamma) * prod(0, x) * alpha
// - (f(x) + beta * s_id(x) + gamma) * alpha
q_x.add_mle_list([numerator], -*alpha)?;
// Q(x) := prod(1,x) - prod(x, 0) * prod(x, 1)
// + alpha * (
// (g(x) + beta * s_perm(x) + gamma) * prod(0, x)
// - (f(x) + beta * s_id(x) + gamma))
q_x.add_mle_list([prod_x0, prod_x1], -F::one())?;
q_x.add_mle_list([prod_1x], F::one())?;
let iop_proof = <PolyIOP<F> as ZeroCheck<F>>::prove(&q_x, transcript)?;
end_timer!(start);
Ok([prod, prod_0x, prod_1x, prod_x0, prod_x1])
Ok((iop_proof, q_x))
}
#[cfg(test)]
mod test {
use super::*;
use crate::utils::bit_decompose;
use super::PermutationCheck;
use crate::{
errors::PolyIOPErrors,
perm_check::{prove_internal, util::identity_permutation_mle},
random_permutation_mle,
structs::IOPProof,
utils::bit_decompose,
virtual_poly::VPAuxInfo,
PolyIOP, VirtualPolynomial,
};
use ark_bls12_381::Fr;
use ark_ff::UniformRand;
use ark_poly::MultilinearExtension;
use ark_ff::{PrimeField, Zero};
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::test_rng;
use std::marker::PhantomData;
/// An MLE that represent an identity permutation: `f(index) \mapto index`
fn identity_permutation_mle<F: PrimeField>(num_vars: usize) -> DenseMultilinearExtension<F> {
let s_id_vec = (0..1u64 << num_vars).map(|index| F::from(index)).collect();
DenseMultilinearExtension::from_evaluations_vec(num_vars, s_id_vec)
fn mock_commit<F: PrimeField>(_f: &DenseMultilinearExtension<F>) -> F {
let mut rng = test_rng();
F::rand(&mut rng)
}
#[test]
fn test_compute_prod_0() -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
fn test_permutation_check_helper(
f: &DenseMultilinearExtension<Fr>,
g: &DenseMultilinearExtension<Fr>,
s_perm: &DenseMultilinearExtension<Fr>,
) -> Result<(IOPProof<Fr>, VirtualPolynomial<Fr>), PolyIOPErrors> {
let mut transcript = <PolyIOP<Fr> as PermutationCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
for num_vars in 2..6 {
let w_vec: Vec<Fr> = (0..(1 << num_vars)).map(|_| Fr::rand(&mut rng)).collect();
let w = DenseMultilinearExtension::from_evaluations_vec(num_vars, w_vec);
let mut challenge =
<PolyIOP<Fr> as PermutationCheck<Fr>>::generate_challenge(&mut transcript)?;
let s_id = identity_permutation_mle::<Fr>(num_vars);
let prod_x_and_aux =
<PolyIOP<Fr> as PermutationCheck<Fr>>::compute_products(&challenge, f, g, s_perm)?;
let s_perm_vec: Vec<Fr> = (0..(1 << num_vars)).map(|_| Fr::rand(&mut rng)).collect();
let s_perm = DenseMultilinearExtension::from_evaluations_vec(num_vars, s_perm_vec);
let prod_x_binding = mock_commit(&prod_x_and_aux[0]);
let beta = Fr::rand(&mut rng);
let gamma = Fr::rand(&mut rng);
<PolyIOP<Fr> as PermutationCheck<Fr>>::update_challenge(
&mut challenge,
&mut transcript,
&prod_x_binding,
)?;
let alpha = challenge.alpha.unwrap();
let prod_0 = compute_prod_0(&beta, &gamma, &w, &s_id, &s_perm)?;
prove_internal(&prod_x_and_aux, &alpha, &mut transcript)
}
for i in 0..1 << num_vars {
let r: Vec<Fr> = bit_decompose(i, num_vars)
.iter()
.map(|&x| Fr::from(x))
.collect();
fn test_permutation_check(nv: usize) -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
let eval = prod_0.evaluate(&r).unwrap();
let poly_info = VPAuxInfo {
max_degree: 2,
num_variables: nv,
phantom: PhantomData::default(),
};
{
// good path: w is a permutation of w itself under the identify map
let w = DenseMultilinearExtension::rand(nv, &mut rng);
// s_perm is the identity map
let s_perm = identity_permutation_mle(nv);
let (proof, q_x) = test_permutation_check_helper(&w, &w, &s_perm)?;
let mut transcript = <PolyIOP<Fr> as PermutationCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let subclaim =
<PolyIOP<Fr> as PermutationCheck<Fr>>::verify(&proof, &poly_info, &mut transcript)?
.zero_check_sub_claim;
assert_eq!(
q_x.evaluate(&subclaim.sum_check_sub_claim.point).unwrap(),
subclaim.sum_check_sub_claim.expected_evaluation,
"wrong subclaim"
);
// test q_x is a 0 over boolean hypercube
for i in 0..1 << nv {
let bit_sequence = bit_decompose(i, nv);
let eval: Vec<Fr> = bit_sequence.iter().map(|x| Fr::from(*x as u64)).collect();
let res = q_x.evaluate(&eval)?;
assert!(res.is_zero())
}
}
let w_eval = w.evaluate(&r).unwrap();
let s_id_eval = s_id.evaluate(&r).unwrap();
let s_perm_eval = s_perm.evaluate(&r).unwrap();
let eval_rec =
(w_eval + beta * s_id_eval + gamma) / (w_eval + beta * s_perm_eval + gamma);
{
// bad path 1: w is a not permutation of w itself under a random map
let w = DenseMultilinearExtension::rand(nv, &mut rng);
// s_perm is a random map
let s_perm = random_permutation_mle(nv, &mut rng);
let (proof, q_x) = test_permutation_check_helper(&w, &w, &s_perm)?;
let mut transcript = <PolyIOP<Fr> as PermutationCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
if nv != 1 {
assert!(<PolyIOP<Fr> as PermutationCheck<Fr>>::verify(
&proof,
&poly_info,
&mut transcript
)
.is_err());
} else {
// a trivial poly is always a permutation of itself, so the zero check should
// pass
let subclaim = <PolyIOP<Fr> as PermutationCheck<Fr>>::verify(
&proof,
&poly_info,
&mut transcript,
)?
.zero_check_sub_claim;
// the evaluation should fail because a different s_perm is used for proof and
// for w |-> w mapping
assert_ne!(
q_x.evaluate(&subclaim.sum_check_sub_claim.point).unwrap(),
subclaim.sum_check_sub_claim.expected_evaluation,
"wrong subclaim"
);
}
}
assert_eq!(eval, eval_rec);
{
// bad path 2: f is a not permutation of g under a identity map
let f = DenseMultilinearExtension::rand(nv, &mut rng);
let g = DenseMultilinearExtension::rand(nv, &mut rng);
// s_perm is the identity map
let s_perm = identity_permutation_mle(nv);
let (proof, q_x) = test_permutation_check_helper(&f, &g, &s_perm)?;
let mut transcript = <PolyIOP<Fr> as PermutationCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
if nv != 1 {
assert!(<PolyIOP<Fr> as PermutationCheck<Fr>>::verify(
&proof,
&poly_info,
&mut transcript
)
.is_err());
} else {
// a trivial poly is always a permutation of itself, so the zero check should
// pass
let subclaim = <PolyIOP<Fr> as PermutationCheck<Fr>>::verify(
&proof,
&poly_info,
&mut transcript,
)?
.zero_check_sub_claim;
// the evaluation should fail because a different s_perm is used for proof and
// for f |-> g mapping
assert_ne!(
q_x.evaluate(&subclaim.sum_check_sub_claim.point).unwrap(),
subclaim.sum_check_sub_claim.expected_evaluation,
"wrong subclaim"
);
}
}
Ok(())
}
#[test]
fn test_trivial_polynomial() -> Result<(), PolyIOPErrors> {
test_permutation_check(1)
}
#[test]
fn test_normal_polynomial() -> Result<(), PolyIOPErrors> {
test_permutation_check(5)
}
#[test]
fn zero_polynomial_should_error() -> Result<(), PolyIOPErrors> {
assert!(test_permutation_check(0).is_err());
Ok(())
}
@ -208,19 +621,20 @@ mod test {
let mut rng = test_rng();
for num_vars in 2..6 {
let w_vec: Vec<Fr> = (0..(1 << num_vars)).map(|_| Fr::rand(&mut rng)).collect();
let w = DenseMultilinearExtension::from_evaluations_vec(num_vars, w_vec);
let f = DenseMultilinearExtension::rand(num_vars, &mut rng);
let g = DenseMultilinearExtension::rand(num_vars, &mut rng);
let s_id = identity_permutation_mle::<Fr>(num_vars);
let s_perm = random_permutation_mle(num_vars, &mut rng);
let s_perm_vec: Vec<Fr> = (0..(1 << num_vars)).map(|_| Fr::rand(&mut rng)).collect();
let s_perm = DenseMultilinearExtension::from_evaluations_vec(num_vars, s_perm_vec);
let beta = Fr::rand(&mut rng);
let gamma = Fr::rand(&mut rng);
let mut transcript = <PolyIOP<Fr> as PermutationCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let challenge =
<PolyIOP<Fr> as PermutationCheck<Fr>>::generate_challenge(&mut transcript)?;
// TODO: also test other 4 polynomials
let res = compute_products(&beta, &gamma, &w, &s_id, &s_perm)?;
let res = <PolyIOP<Fr> as PermutationCheck<Fr>>::compute_products(
&challenge, &f, &g, &s_perm,
)?;
for i in 0..1 << num_vars {
let r: Vec<Fr> = bit_decompose(i, num_vars)
@ -230,11 +644,12 @@ mod test {
let eval = res[1].evaluate(&r).unwrap();
let w_eval = w.evaluate(&r).unwrap();
let f_eval = f.evaluate(&r).unwrap();
let g_eval = g.evaluate(&r).unwrap();
let s_id_eval = s_id.evaluate(&r).unwrap();
let s_perm_eval = s_perm.evaluate(&r).unwrap();
let eval_rec =
(w_eval + beta * s_id_eval + gamma) / (w_eval + beta * s_perm_eval + gamma);
let eval_rec = (f_eval + challenge.beta * s_id_eval + challenge.gamma)
/ (g_eval + challenge.beta * s_perm_eval + challenge.gamma);
assert_eq!(eval, eval_rec);
}

+ 143
- 0
poly-iop/src/perm_check/util.rs

@ -0,0 +1,143 @@
//! This module implements useful functions for the permutation check protocol.
use crate::PolyIOPErrors;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, rand::RngCore, start_timer};
/// Returns three MLEs:
/// - prod(0,x)
/// - numerator
/// - denominator
///
/// where
/// - `prod(0,x) := prod(0, x1, …, xn)` which is the MLE over the
/// evaluations of the following polynomial on the boolean hypercube {0,1}^n:
///
/// (f(x) + \beta s_id(x) + \gamma)/(g(x) + \beta s_perm(x) + \gamma)
///
/// where
/// - beta and gamma are challenges
/// - f(x), g(x), s_id(x), s_perm(x) are mle-s
///
/// - numerator is the MLE for `f(x) + \beta s_id(x) + \gamma`
/// - denominator is the MLE for `g(x) + \beta s_perm(x) + \gamma`
///
/// The caller needs to check num_vars matches in f/g/s_id/s_perm
/// Cost: linear in N.
#[allow(clippy::type_complexity)]
pub(super) fn compute_prod_0<F: PrimeField>(
beta: &F,
gamma: &F,
fx: &DenseMultilinearExtension<F>,
gx: &DenseMultilinearExtension<F>,
s_perm: &DenseMultilinearExtension<F>,
) -> Result<
(
DenseMultilinearExtension<F>,
DenseMultilinearExtension<F>,
DenseMultilinearExtension<F>,
),
PolyIOPErrors,
> {
let start = start_timer!(|| "compute prod(1,x)");
let num_vars = fx.num_vars;
let mut prod_0x_evals = vec![];
let mut numerator_evals = vec![];
let mut denominator_evals = vec![];
let s_id = identity_permutation_mle::<F>(num_vars);
for (&fi, (&gi, (&s_id_i, &s_perm_i))) in
fx.iter().zip(gx.iter().zip(s_id.iter().zip(s_perm.iter())))
{
let numerator = fi + *beta * s_id_i + gamma;
let denominator = gi + *beta * s_perm_i + gamma;
prod_0x_evals.push(numerator / denominator);
numerator_evals.push(numerator);
denominator_evals.push(denominator);
}
let prod_0x = DenseMultilinearExtension::from_evaluations_vec(num_vars, prod_0x_evals);
let numerator = DenseMultilinearExtension::from_evaluations_vec(num_vars, numerator_evals);
let denominator = DenseMultilinearExtension::from_evaluations_vec(num_vars, denominator_evals);
end_timer!(start);
Ok((prod_0x, numerator, denominator))
}
/// An MLE that represent an identity permutation: `f(index) \mapto index`
pub fn identity_permutation_mle<F: PrimeField>(num_vars: usize) -> DenseMultilinearExtension<F> {
let s_id_vec = (0..1u64 << num_vars).map(F::from).collect();
DenseMultilinearExtension::from_evaluations_vec(num_vars, s_id_vec)
}
/// An MLE that represent a random permutation
pub fn random_permutation_mle<F: PrimeField, R: RngCore>(
num_vars: usize,
rng: &mut R,
) -> DenseMultilinearExtension<F> {
let len = 1u64 << num_vars;
let mut s_id_vec: Vec<F> = (0..len).map(F::from).collect();
let mut s_perm_vec = vec![];
for _ in 0..len {
let index = rng.next_u64() as usize % s_id_vec.len();
s_perm_vec.push(s_id_vec.remove(index));
}
DenseMultilinearExtension::from_evaluations_vec(num_vars, s_perm_vec)
}
#[cfg(test)]
mod test {
use super::*;
use crate::utils::bit_decompose;
use ark_bls12_381::Fr;
use ark_ff::UniformRand;
use ark_poly::MultilinearExtension;
use ark_std::test_rng;
#[test]
fn test_compute_prod_0() -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
for num_vars in 2..6 {
let f = DenseMultilinearExtension::rand(num_vars, &mut rng);
let g = DenseMultilinearExtension::rand(num_vars, &mut rng);
let s_id = identity_permutation_mle::<Fr>(num_vars);
let s_perm = random_permutation_mle(num_vars, &mut rng);
let beta = Fr::rand(&mut rng);
let gamma = Fr::rand(&mut rng);
let (prod_0, numerator, denominator) = compute_prod_0(&beta, &gamma, &f, &g, &s_perm)?;
for i in 0..1 << num_vars {
let r: Vec<Fr> = bit_decompose(i, num_vars)
.iter()
.map(|&x| Fr::from(x))
.collect();
let prod_0_eval = prod_0.evaluate(&r).unwrap();
let numerator_eval = numerator.evaluate(&r).unwrap();
let denominator_eval = denominator.evaluate(&r).unwrap();
let f_eval = f.evaluate(&r).unwrap();
let g_eval = g.evaluate(&r).unwrap();
let s_id_eval = s_id.evaluate(&r).unwrap();
let s_perm_eval = s_perm.evaluate(&r).unwrap();
let numerator_eval_rec = f_eval + beta * s_id_eval + gamma;
let denominator_eval_rec = g_eval + beta * s_perm_eval + gamma;
let prod_0_eval_rec = numerator_eval_rec / denominator_eval_rec;
assert_eq!(numerator_eval, numerator_eval_rec);
assert_eq!(denominator_eval, denominator_eval_rec);
assert_eq!(prod_0_eval, prod_0_eval_rec);
}
}
Ok(())
}
}

+ 0
- 10
poly-iop/src/structs.rs

@ -3,16 +3,6 @@
use crate::VirtualPolynomial;
use ark_ff::PrimeField;
/// A Subclaim is a claim generated by the verifier at the end of verification
/// when it is convinced.
pub struct SubClaim<F: PrimeField> {
/// the multi-dimensional point that this multilinear extension is evaluated
/// to
pub point: Vec<F>,
/// the expected evaluation
pub expected_evaluation: F,
}
/// An IOP proof is a collections of messages from prover to verifier at each
/// round through the interactive protocol.
#[derive(Clone, Debug, Default, PartialEq)]

+ 23
- 8
poly-iop/src/sum_check/mod.rs

@ -2,12 +2,13 @@
use crate::{
errors::PolyIOPErrors,
structs::{IOPProof, IOPProverState, IOPVerifierState, SubClaim},
structs::{IOPProof, IOPProverState, IOPVerifierState},
transcript::IOPTranscript,
virtual_poly::{VPAuxInfo, VirtualPolynomial},
PolyIOP,
};
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, start_timer};
mod prover;
@ -15,11 +16,13 @@ mod verifier;
/// Trait for doing sum check protocols.
pub trait SumCheck<F: PrimeField> {
type Proof;
type VirtualPolynomial;
type VPAuxInfo;
type SubClaim;
type MultilinearExtension;
type Proof;
type Transcript;
type SumCheckSubClaim;
/// Extract sum from the proof
fn extract_sum(proof: &Self::Proof) -> F;
@ -46,7 +49,7 @@ pub trait SumCheck {
proof: &Self::Proof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::SubClaim, PolyIOPErrors>;
) -> Result<Self::SumCheckSubClaim, PolyIOPErrors>;
}
/// Trait for sum check protocol prover side APIs.
@ -77,7 +80,7 @@ pub trait SumCheckVerifier {
type ProverMessage;
type Challenge;
type Transcript;
type SubClaim;
type SumCheckSubClaim;
/// Initialize the verifier's state.
fn verifier_init(index_info: &Self::VPAuxInfo) -> Self;
@ -105,14 +108,26 @@ pub trait SumCheckVerifier {
fn check_and_generate_subclaim(
&self,
asserted_sum: &F,
) -> Result<Self::SubClaim, PolyIOPErrors>;
) -> Result<Self::SumCheckSubClaim, PolyIOPErrors>;
}
/// A SumCheckSubClaim is a claim generated by the verifier at the end of
/// verification when it is convinced.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SumCheckSubClaim<F: PrimeField> {
/// the multi-dimensional point that this multilinear extension is evaluated
/// to
pub point: Vec<F>,
/// the expected evaluation
pub expected_evaluation: F,
}
impl<F: PrimeField> SumCheck<F> for PolyIOP<F> {
type Proof = IOPProof<F>;
type VirtualPolynomial = VirtualPolynomial<F>;
type VPAuxInfo = VPAuxInfo<F>;
type SubClaim = SubClaim<F>;
type MultilinearExtension = DenseMultilinearExtension<F>;
type SumCheckSubClaim = SumCheckSubClaim<F>;
type Transcript = IOPTranscript<F>;
/// Extract sum from the proof
@ -170,7 +185,7 @@ impl SumCheck for PolyIOP {
proof: &Self::Proof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::SubClaim, PolyIOPErrors> {
) -> Result<Self::SumCheckSubClaim, PolyIOPErrors> {
let start = start_timer!(|| "sum check verify");
transcript.append_aux_info(aux_info)?;

+ 11
- 11
poly-iop/src/sum_check/verifier.rs

@ -1,9 +1,9 @@
//! Verifier subroutines for a SumCheck protocol.
use super::SumCheckVerifier;
use super::{SumCheckSubClaim, SumCheckVerifier};
use crate::{
errors::PolyIOPErrors,
structs::{IOPProverMessage, IOPVerifierState, SubClaim},
structs::{IOPProverMessage, IOPVerifierState},
transcript::IOPTranscript,
virtual_poly::VPAuxInfo,
};
@ -18,7 +18,7 @@ impl SumCheckVerifier for IOPVerifierState {
type ProverMessage = IOPProverMessage<F>;
type Challenge = F;
type Transcript = IOPTranscript<F>;
type SubClaim = SubClaim<F>;
type SumCheckSubClaim = SumCheckSubClaim<F>;
/// Initialize the verifier's state.
fn verifier_init(index_info: &Self::VPAuxInfo) -> Self {
@ -91,7 +91,7 @@ impl SumCheckVerifier for IOPVerifierState {
fn check_and_generate_subclaim(
&self,
asserted_sum: &F,
) -> Result<Self::SubClaim, PolyIOPErrors> {
) -> Result<Self::SumCheckSubClaim, PolyIOPErrors> {
let start = start_timer!(|| "sum check check and generate subclaim");
if !self.finished {
return Err(PolyIOPErrors::InvalidVerifier(
@ -161,7 +161,7 @@ impl SumCheckVerifier for IOPVerifierState {
}
}
end_timer!(start);
Ok(SubClaim {
Ok(SumCheckSubClaim {
point: self.challenges.clone(),
// the last expected value (not checked within this function) will be included in the
// subclaim
@ -221,7 +221,7 @@ fn interpolate_uni_poly(p_i: &[F], eval_at: F) -> Result
if p_i.len() <= 20 {
let last_denominator = F::from(u64_factorial(len - 1));
let mut ratio_numerator = 1i64;
let mut ratio_enumerator = 1u64;
let mut ratio_denominator = 1u64;
for i in (0..len).rev() {
let ratio_numerator_f = if ratio_numerator < 0 {
@ -230,19 +230,19 @@ fn interpolate_uni_poly(p_i: &[F], eval_at: F) -> Result
F::from(ratio_numerator as u64)
};
res += p_i[i] * prod * F::from(ratio_enumerator)
res += p_i[i] * prod * F::from(ratio_denominator)
/ (last_denominator * ratio_numerator_f * evals[i]);
// compute denom for the next step is current_denom * (len-i)/i
if i != 0 {
ratio_numerator *= -(len as i64 - i as i64);
ratio_enumerator *= i as u64;
ratio_denominator *= i as u64;
}
}
} else if p_i.len() <= 33 {
let last_denominator = F::from(u128_factorial(len - 1));
let mut ratio_numerator = 1i128;
let mut ratio_enumerator = 1u128;
let mut ratio_denominator = 1u128;
for i in (0..len).rev() {
let ratio_numerator_f = if ratio_numerator < 0 {
@ -251,13 +251,13 @@ fn interpolate_uni_poly(p_i: &[F], eval_at: F) -> Result
F::from(ratio_numerator as u128)
};
res += p_i[i] * prod * F::from(ratio_enumerator)
res += p_i[i] * prod * F::from(ratio_denominator)
/ (last_denominator * ratio_numerator_f * evals[i]);
// compute denom for the next step is current_denom * (len-i)/i
if i != 0 {
ratio_numerator *= -(len as i128 - i as i128);
ratio_enumerator *= i as u128;
ratio_denominator *= i as u128;
}
}
} else {

+ 2
- 2
poly-iop/src/virtual_poly.rs

@ -23,7 +23,7 @@ use std::{cmp::max, collections::HashMap, marker::PhantomData, ops::Add, rc::Rc}
///
/// The resulting polynomial is
///
/// $$\sum_{i=0}^{n} c_i \cdot \prod_{j=0}^{m_i} P_{ij} $$
/// $$ \sum_{i=0}^{n} c_i \cdot \prod_{j=0}^{m_i} P_{ij} $$
///
/// Example:
/// f = c0 * f0 * f1 * f2 + c1 * f3 * f4
@ -60,7 +60,7 @@ pub struct VPAuxInfo {
pub num_variables: usize,
/// Associated field
#[doc(hidden)]
pub(crate) phantom: PhantomData<F>,
pub phantom: PhantomData<F>,
}
impl<F: PrimeField> Add for &VirtualPolynomial<F> {

+ 25
- 25
poly-iop/src/zero_check/mod.rs

@ -1,22 +1,23 @@
//! Main module for the ZeroCheck protocol.
use crate::{
errors::PolyIOPErrors,
structs::{IOPProof, SubClaim},
sum_check::SumCheck,
transcript::IOPTranscript,
virtual_poly::{VPAuxInfo, VirtualPolynomial},
PolyIOP,
};
use crate::{errors::PolyIOPErrors, sum_check::SumCheck, transcript::IOPTranscript, PolyIOP};
use ark_ff::PrimeField;
use ark_std::{end_timer, start_timer};
pub trait ZeroCheck<F: PrimeField> {
type Proof;
type VirtualPolynomial;
type VPAuxInfo;
type SubClaim;
type Transcript;
/// A zero check IOP subclaim for \hat f(x) is 0, consists of the following:
/// - the SubClaim from the SumCheck
/// - the initial challenge r which is used to build eq(x, r) in ZeroCheck
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ZeroCheckSubClaim<F: PrimeField, SC: SumCheck<F>> {
// the SubClaim from the SumCheck
pub sum_check_sub_claim: SC::SumCheckSubClaim,
// the initial challenge r which is used to build eq(x, r)
pub init_challenge: Vec<F>,
}
/// A ZeroCheck is derived from SumCheck.
pub trait ZeroCheck<F: PrimeField>: SumCheck<F> {
type ZeroCheckSubClaim;
/// Initialize the system with a transcript
///
@ -38,19 +39,14 @@ pub trait ZeroCheck {
proof: &Self::Proof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::SubClaim, PolyIOPErrors>;
) -> Result<Self::ZeroCheckSubClaim, PolyIOPErrors>;
}
impl<F: PrimeField> ZeroCheck<F> for PolyIOP<F> {
type Proof = IOPProof<F>;
type VirtualPolynomial = VirtualPolynomial<F>;
type VPAuxInfo = VPAuxInfo<F>;
/// A ZeroCheck SubClaim consists of
/// - the SubClaim from the ZeroCheck
/// - the SubClaim from the SumCheck
/// - the initial challenge r which is used to build eq(x, r)
type SubClaim = (SubClaim<F>, Vec<F>);
type Transcript = IOPTranscript<F>;
type ZeroCheckSubClaim = ZeroCheckSubClaim<F, Self>;
/// Initialize the system with a transcript
///
@ -98,7 +94,7 @@ impl ZeroCheck for PolyIOP {
proof: &Self::Proof,
fx_aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::SubClaim, PolyIOPErrors> {
) -> Result<Self::ZeroCheckSubClaim, PolyIOPErrors> {
let start = start_timer!(|| "zero check verify");
// check that the sum is zero
@ -120,7 +116,10 @@ impl ZeroCheck for PolyIOP {
<Self as SumCheck<F>>::verify(F::zero(), proof, &hat_fx_aux_info, transcript)?;
end_timer!(start);
Ok((subclaim, r))
Ok(ZeroCheckSubClaim {
sum_check_sub_claim: subclaim,
init_challenge: r,
})
}
}
@ -152,7 +151,8 @@ mod test {
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let subclaim =
<PolyIOP<Fr> as ZeroCheck<Fr>>::verify(&proof, &poly_info, &mut transcript)?.0;
<PolyIOP<Fr> as ZeroCheck<Fr>>::verify(&proof, &poly_info, &mut transcript)?
.sum_check_sub_claim;
assert!(
poly.evaluate(&subclaim.point)? == subclaim.expected_evaluation,
"wrong subclaim"

Loading…
Cancel
Save