mirror of
https://github.com/arnaucube/hyperplonk.git
synced 2026-01-12 00:51:27 +01:00
initial implemetation of Sumcheck protocol (#7)
This commit is contained in:
404
poly-iop/src/sum_check/mod.rs
Normal file
404
poly-iop/src/sum_check/mod.rs
Normal file
@@ -0,0 +1,404 @@
|
||||
//! This module implements the sum check protocol.
|
||||
//! Currently this is a simple wrapper of the sumcheck protocol
|
||||
//! from Arkworks.
|
||||
|
||||
use crate::{
|
||||
errors::PolyIOPErrors,
|
||||
structs::{DomainInfo, IOPProof, SubClaim},
|
||||
transcript::IOPTranscript,
|
||||
virtual_poly::VirtualPolynomial,
|
||||
PolyIOP,
|
||||
};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_std::{end_timer, start_timer};
|
||||
|
||||
mod prover;
|
||||
mod verifier;
|
||||
|
||||
pub use prover::ProverState;
|
||||
pub use verifier::VerifierState;
|
||||
|
||||
pub trait SumCheck<F: PrimeField> {
|
||||
type Proof;
|
||||
type PolyList;
|
||||
type DomainInfo;
|
||||
type SubClaim;
|
||||
type Transcript;
|
||||
|
||||
/// extract sum from the proof
|
||||
fn extract_sum(proof: &Self::Proof) -> F;
|
||||
|
||||
/// Initialize the system with a transcript
|
||||
///
|
||||
/// This function is optional -- in the case where a SumCheck is
|
||||
/// an building block for a more complex protocol, the transcript
|
||||
/// may be initialized by this complex protocol, and passed to the
|
||||
/// SumCheck prover/verifier.
|
||||
fn init_transcript() -> Self::Transcript;
|
||||
|
||||
/// generate proof of the sum of polynomial over {0,1}^`num_vars`
|
||||
///
|
||||
/// The polynomial is represented by a list of products of polynomials along
|
||||
/// with its coefficient that is meant to be added together.
|
||||
///
|
||||
/// This data structure of the polynomial is a list of list of
|
||||
/// `(coefficient, DenseMultilinearExtension)`.
|
||||
/// * Number of products n = `polynomial.products.len()`,
|
||||
/// * Number of multiplicands of ith product m_i =
|
||||
/// `polynomial.products[i].1.len()`,
|
||||
/// * Coefficient of ith product c_i = `polynomial.products[i].0`
|
||||
///
|
||||
/// The resulting polynomial is
|
||||
///
|
||||
/// $$\sum_{i=0}^{n}C_i\cdot\prod_{j=0}^{m_i}P_{ij}$$
|
||||
fn prove(
|
||||
poly: &Self::PolyList,
|
||||
transcript: &mut Self::Transcript,
|
||||
) -> Result<Self::Proof, PolyIOPErrors>;
|
||||
|
||||
/// verify the claimed sum using the proof
|
||||
fn verify(
|
||||
sum: F,
|
||||
proof: &Self::Proof,
|
||||
domain_info: &Self::DomainInfo,
|
||||
transcript: &mut Self::Transcript,
|
||||
) -> Result<Self::SubClaim, PolyIOPErrors>;
|
||||
}
|
||||
|
||||
pub trait SumCheckProver<F: PrimeField>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
type PolyList;
|
||||
type ProverMessage;
|
||||
|
||||
/// initialize the prover to argue for the sum of polynomial over
|
||||
/// {0,1}^`num_vars`
|
||||
///
|
||||
/// The polynomial is represented by a list of products of polynomials along
|
||||
/// with its coefficient that is meant to be added together.
|
||||
///
|
||||
/// This data structure of the polynomial is a list of list of
|
||||
/// `(coefficient, DenseMultilinearExtension)`.
|
||||
/// * Number of products n = `polynomial.products.len()`,
|
||||
/// * Number of multiplicands of ith product m_i =
|
||||
/// `polynomial.products[i].1.len()`,
|
||||
/// * Coefficient of ith product c_i = `polynomial.products[i].0`
|
||||
///
|
||||
/// The resulting polynomial is
|
||||
///
|
||||
/// $$\sum_{i=0}^{n}C_i\cdot\prod_{j=0}^{m_i}P_{ij}$$
|
||||
fn prover_init(polynomial: &Self::PolyList) -> Result<Self, PolyIOPErrors>;
|
||||
|
||||
/// receive message from verifier, generate prover message, and proceed to
|
||||
/// next round
|
||||
///
|
||||
/// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2).
|
||||
fn prove_round_and_update_state(
|
||||
&mut self,
|
||||
challenge: &Option<F>,
|
||||
) -> Result<Self::ProverMessage, PolyIOPErrors>;
|
||||
}
|
||||
|
||||
pub trait SumCheckVerifier<F: PrimeField> {
|
||||
type DomainInfo;
|
||||
type ProverMessage;
|
||||
type Challenge;
|
||||
type Transcript;
|
||||
type SubClaim;
|
||||
|
||||
/// initialize the verifier
|
||||
fn verifier_init(index_info: &Self::DomainInfo) -> Self;
|
||||
|
||||
/// Run verifier at current round, given prover message
|
||||
///
|
||||
/// Normally, this function should perform actual verification. Instead,
|
||||
/// `verify_round` only samples and stores randomness and perform
|
||||
/// verifications altogether in `check_and_generate_subclaim` at
|
||||
/// the last step.
|
||||
fn verify_round_and_update_state(
|
||||
&mut self,
|
||||
prover_msg: &Self::ProverMessage,
|
||||
transcript: &mut Self::Transcript,
|
||||
) -> Result<Self::Challenge, PolyIOPErrors>;
|
||||
|
||||
/// verify the sumcheck phase, and generate the subclaim
|
||||
///
|
||||
/// If the asserted sum is correct, then the multilinear polynomial
|
||||
/// evaluated at `subclaim.point` is `subclaim.expected_evaluation`.
|
||||
/// Otherwise, it is highly unlikely that those two will be equal.
|
||||
/// Larger field size guarantees smaller soundness error.
|
||||
fn check_and_generate_subclaim(
|
||||
&self,
|
||||
asserted_sum: &F,
|
||||
) -> Result<Self::SubClaim, PolyIOPErrors>;
|
||||
}
|
||||
|
||||
impl<F: PrimeField> SumCheck<F> for PolyIOP<F> {
|
||||
type Proof = IOPProof<F>;
|
||||
|
||||
type PolyList = VirtualPolynomial<F>;
|
||||
|
||||
type DomainInfo = DomainInfo<F>;
|
||||
|
||||
type SubClaim = SubClaim<F>;
|
||||
|
||||
type Transcript = IOPTranscript<F>;
|
||||
|
||||
fn extract_sum(proof: &Self::Proof) -> F {
|
||||
let start = start_timer!(|| "extract sum");
|
||||
let res = proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1];
|
||||
end_timer!(start);
|
||||
res
|
||||
}
|
||||
|
||||
/// Initialize the system with a transcript
|
||||
///
|
||||
/// This function is optional -- in the case where a SumCheck is
|
||||
/// an building block for a more complex protocol, the transcript
|
||||
/// may be initialized by this complex protocol, and passed to the
|
||||
/// SumCheck prover/verifier.
|
||||
fn init_transcript() -> Self::Transcript {
|
||||
let start = start_timer!(|| "init transcript");
|
||||
let res = IOPTranscript::<F>::new(b"Initializing SumCheck transcript");
|
||||
end_timer!(start);
|
||||
res
|
||||
}
|
||||
|
||||
/// generate proof of the sum of polynomial over {0,1}^`num_vars`
|
||||
///
|
||||
/// The polynomial is represented by a list of products of polynomials along
|
||||
/// with its coefficient that is meant to be added together.
|
||||
///
|
||||
/// This data structure of the polynomial is a list of list of
|
||||
/// `(coefficient, DenseMultilinearExtension)`.
|
||||
/// * Number of products n = `polynomial.products.len()`,
|
||||
/// * Number of multiplicands of ith product m_i =
|
||||
/// `polynomial.products[i].1.len()`,
|
||||
/// * Coefficient of ith product c_i = `polynomial.products[i].0`
|
||||
///
|
||||
/// The resulting polynomial is
|
||||
///
|
||||
/// $$\sum_{i=0}^{n}C_i\cdot\prod_{j=0}^{m_i}P_{ij}$$
|
||||
fn prove(
|
||||
poly: &Self::PolyList,
|
||||
transcript: &mut Self::Transcript,
|
||||
) -> Result<Self::Proof, PolyIOPErrors> {
|
||||
let start = start_timer!(|| "sum check prove");
|
||||
|
||||
transcript.append_domain_info(&poly.domain_info)?;
|
||||
|
||||
let mut prover_state = ProverState::prover_init(poly)?;
|
||||
let mut challenge = None;
|
||||
let mut prover_msgs = Vec::with_capacity(poly.domain_info.num_variables);
|
||||
for _ in 0..poly.domain_info.num_variables {
|
||||
let prover_msg =
|
||||
ProverState::prove_round_and_update_state(&mut prover_state, &challenge)?;
|
||||
transcript.append_prover_message(&prover_msg)?;
|
||||
prover_msgs.push(prover_msg);
|
||||
challenge = Some(transcript.get_and_append_challenge(b"Internal round")?);
|
||||
}
|
||||
|
||||
end_timer!(start);
|
||||
Ok(IOPProof {
|
||||
proofs: prover_msgs,
|
||||
})
|
||||
}
|
||||
|
||||
/// verify the claimed sum using the proof
|
||||
fn verify(
|
||||
claimed_sum: F,
|
||||
proof: &Self::Proof,
|
||||
domain_info: &Self::DomainInfo,
|
||||
transcript: &mut Self::Transcript,
|
||||
) -> Result<Self::SubClaim, PolyIOPErrors> {
|
||||
let start = start_timer!(|| "sum check prove");
|
||||
|
||||
transcript.append_domain_info(domain_info)?;
|
||||
let mut verifier_state = VerifierState::verifier_init(domain_info);
|
||||
for i in 0..domain_info.num_variables {
|
||||
let prover_msg = proof.proofs.get(i).expect("proof is incomplete");
|
||||
transcript.append_prover_message(prover_msg)?;
|
||||
VerifierState::verify_round_and_update_state(
|
||||
&mut verifier_state,
|
||||
prover_msg,
|
||||
transcript,
|
||||
)?;
|
||||
}
|
||||
|
||||
let res = VerifierState::check_and_generate_subclaim(&verifier_state, &claimed_sum);
|
||||
|
||||
end_timer!(start);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
use crate::virtual_poly::test::random_list_of_products;
|
||||
use ark_bls12_381::Fr;
|
||||
use ark_ff::UniformRand;
|
||||
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
|
||||
use ark_std::test_rng;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn test_sumcheck(nv: usize, num_multiplicands_range: (usize, usize), num_products: usize) {
|
||||
let mut rng = test_rng();
|
||||
let mut transcript = PolyIOP::init_transcript();
|
||||
|
||||
let (poly, asserted_sum) =
|
||||
random_list_of_products::<Fr, _>(nv, num_multiplicands_range, num_products, &mut rng);
|
||||
let proof = PolyIOP::prove(&poly, &mut transcript).expect("fail to prove");
|
||||
let poly_info = poly.domain_info.clone();
|
||||
let mut transcript = PolyIOP::init_transcript();
|
||||
let subclaim = PolyIOP::verify(asserted_sum, &proof, &poly_info, &mut transcript)
|
||||
.expect("fail to verify");
|
||||
assert!(
|
||||
poly.evaluate(&subclaim.point).unwrap() == subclaim.expected_evaluation,
|
||||
"wrong subclaim"
|
||||
);
|
||||
}
|
||||
|
||||
fn test_sumcheck_internal(
|
||||
nv: usize,
|
||||
num_multiplicands_range: (usize, usize),
|
||||
num_products: usize,
|
||||
) {
|
||||
let mut rng = test_rng();
|
||||
let (poly, asserted_sum) =
|
||||
random_list_of_products::<Fr, _>(nv, num_multiplicands_range, num_products, &mut rng);
|
||||
let poly_info = poly.domain_info.clone();
|
||||
let mut prover_state = ProverState::prover_init(&poly).unwrap();
|
||||
let mut verifier_state = VerifierState::verifier_init(&poly_info);
|
||||
let mut challenge = None;
|
||||
let mut transcript = IOPTranscript::new(b"a test transcript");
|
||||
transcript
|
||||
.append_message(b"testing", b"initializing transcript for testing")
|
||||
.unwrap();
|
||||
for _ in 0..poly.domain_info.num_variables {
|
||||
let prover_message =
|
||||
ProverState::prove_round_and_update_state(&mut prover_state, &challenge).unwrap();
|
||||
|
||||
challenge = Some(
|
||||
VerifierState::verify_round_and_update_state(
|
||||
&mut verifier_state,
|
||||
&prover_message,
|
||||
&mut transcript,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
let subclaim = VerifierState::check_and_generate_subclaim(&verifier_state, &asserted_sum)
|
||||
.expect("fail to generate subclaim");
|
||||
assert!(
|
||||
poly.evaluate(&subclaim.point).unwrap() == subclaim.expected_evaluation,
|
||||
"wrong subclaim"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trivial_polynomial() {
|
||||
let nv = 1;
|
||||
let num_multiplicands_range = (4, 13);
|
||||
let num_products = 5;
|
||||
|
||||
test_sumcheck(nv, num_multiplicands_range, num_products);
|
||||
test_sumcheck_internal(nv, num_multiplicands_range, num_products);
|
||||
}
|
||||
#[test]
|
||||
fn test_normal_polynomial() {
|
||||
let nv = 12;
|
||||
let num_multiplicands_range = (4, 9);
|
||||
let num_products = 5;
|
||||
|
||||
test_sumcheck(nv, num_multiplicands_range, num_products);
|
||||
test_sumcheck_internal(nv, num_multiplicands_range, num_products);
|
||||
}
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn zero_polynomial_should_error() {
|
||||
let nv = 0;
|
||||
let num_multiplicands_range = (4, 13);
|
||||
let num_products = 5;
|
||||
|
||||
test_sumcheck(nv, num_multiplicands_range, num_products);
|
||||
test_sumcheck_internal(nv, num_multiplicands_range, num_products);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_sum() {
|
||||
let mut rng = test_rng();
|
||||
let mut transcript = PolyIOP::init_transcript();
|
||||
let (poly, asserted_sum) = random_list_of_products::<Fr, _>(8, (3, 4), 3, &mut rng);
|
||||
|
||||
let proof = PolyIOP::prove(&poly, &mut transcript).expect("fail to prove");
|
||||
assert_eq!(PolyIOP::extract_sum(&proof), asserted_sum);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Test that the memory usage of shared-reference is linear to number of
|
||||
/// unique MLExtensions instead of total number of multiplicands.
|
||||
fn test_shared_reference() {
|
||||
let mut rng = test_rng();
|
||||
let ml_extensions: Vec<_> = (0..5)
|
||||
.map(|_| Rc::new(DenseMultilinearExtension::<Fr>::rand(8, &mut rng)))
|
||||
.collect();
|
||||
let mut poly = VirtualPolynomial::new(8);
|
||||
poly.add_product(
|
||||
vec![
|
||||
ml_extensions[2].clone(),
|
||||
ml_extensions[3].clone(),
|
||||
ml_extensions[0].clone(),
|
||||
],
|
||||
Fr::rand(&mut rng),
|
||||
)
|
||||
.unwrap();
|
||||
poly.add_product(
|
||||
vec![
|
||||
ml_extensions[1].clone(),
|
||||
ml_extensions[4].clone(),
|
||||
ml_extensions[4].clone(),
|
||||
],
|
||||
Fr::rand(&mut rng),
|
||||
)
|
||||
.unwrap();
|
||||
poly.add_product(
|
||||
vec![
|
||||
ml_extensions[3].clone(),
|
||||
ml_extensions[2].clone(),
|
||||
ml_extensions[1].clone(),
|
||||
],
|
||||
Fr::rand(&mut rng),
|
||||
)
|
||||
.unwrap();
|
||||
poly.add_product(
|
||||
vec![ml_extensions[0].clone(), ml_extensions[0].clone()],
|
||||
Fr::rand(&mut rng),
|
||||
)
|
||||
.unwrap();
|
||||
poly.add_product(vec![ml_extensions[4].clone()], Fr::rand(&mut rng))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(poly.flattened_ml_extensions.len(), 5);
|
||||
|
||||
// test memory usage for prover
|
||||
let prover = ProverState::prover_init(&poly).unwrap();
|
||||
assert_eq!(prover.poly.flattened_ml_extensions.len(), 5);
|
||||
drop(prover);
|
||||
|
||||
let mut transcript = PolyIOP::init_transcript();
|
||||
let poly_info = poly.domain_info.clone();
|
||||
let proof = PolyIOP::prove(&poly, &mut transcript).expect("fail to prove");
|
||||
let asserted_sum = PolyIOP::extract_sum(&proof);
|
||||
|
||||
let mut transcript = PolyIOP::init_transcript();
|
||||
let subclaim = PolyIOP::verify(asserted_sum, &proof, &poly_info, &mut transcript)
|
||||
.expect("fail to verify");
|
||||
assert!(
|
||||
poly.evaluate(&subclaim.point).unwrap() == subclaim.expected_evaluation,
|
||||
"wrong subclaim"
|
||||
);
|
||||
}
|
||||
}
|
||||
175
poly-iop/src/sum_check/prover.rs
Normal file
175
poly-iop/src/sum_check/prover.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
//! Prover
|
||||
use std::rc::Rc;
|
||||
|
||||
// TODO: some of the struct is generic for Sum Checks and Zero Checks.
|
||||
// If so move them to src/structs.rs
|
||||
use super::SumCheckProver;
|
||||
use crate::{errors::PolyIOPErrors, structs::IOPProverMessage, virtual_poly::VirtualPolynomial};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
|
||||
use ark_std::{end_timer, start_timer, vec::Vec};
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||
|
||||
/// Prover State
|
||||
pub struct ProverState<F: PrimeField> {
|
||||
/// sampled randomness given by the verifier
|
||||
pub challenges: Vec<F>,
|
||||
/// the current round number
|
||||
pub(crate) round: usize,
|
||||
/// pointer to the virtual polynomial
|
||||
pub(crate) poly: VirtualPolynomial<F>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> SumCheckProver<F> for ProverState<F> {
|
||||
type PolyList = VirtualPolynomial<F>;
|
||||
type ProverMessage = IOPProverMessage<F>;
|
||||
|
||||
/// initialize the prover to argue for the sum of polynomial over
|
||||
/// {0,1}^`num_vars`
|
||||
///
|
||||
/// The polynomial is represented by a list of products of polynomials along
|
||||
/// with its coefficient that is meant to be added together.
|
||||
///
|
||||
/// This data structure of the polynomial is a list of list of
|
||||
/// `(coefficient, DenseMultilinearExtension)`.
|
||||
/// * Number of products n = `polynomial.products.len()`,
|
||||
/// * Number of multiplicands of ith product m_i =
|
||||
/// `polynomial.products[i].1.len()`,
|
||||
/// * Coefficient of ith product c_i = `polynomial.products[i].0`
|
||||
///
|
||||
/// The resulting polynomial is
|
||||
///
|
||||
/// $$\sum_{i=0}^{n}C_i\cdot\prod_{j=0}^{m_i}P_{ij}$$
|
||||
fn prover_init(polynomial: &Self::PolyList) -> Result<Self, PolyIOPErrors> {
|
||||
let start = start_timer!(|| "prover init");
|
||||
if polynomial.domain_info.num_variables == 0 {
|
||||
return Err(PolyIOPErrors::InvalidParameters(
|
||||
"Attempt to prove a constant.".to_string(),
|
||||
));
|
||||
}
|
||||
end_timer!(start);
|
||||
|
||||
Ok(ProverState {
|
||||
challenges: Vec::with_capacity(polynomial.domain_info.num_variables),
|
||||
round: 0,
|
||||
poly: polynomial.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// receive message from verifier, generate prover message, and proceed to
|
||||
/// next round
|
||||
///
|
||||
/// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2).
|
||||
fn prove_round_and_update_state(
|
||||
&mut self,
|
||||
challenge: &Option<F>,
|
||||
) -> Result<Self::ProverMessage, PolyIOPErrors> {
|
||||
let start = start_timer!(|| format!("prove {}-th round and update state", self.round));
|
||||
|
||||
let fix_argument = start_timer!(|| "fix argument");
|
||||
|
||||
let mut flattened_ml_extensions: Vec<DenseMultilinearExtension<F>> = self
|
||||
.poly
|
||||
.flattened_ml_extensions
|
||||
.iter()
|
||||
.map(|x| x.as_ref().clone())
|
||||
.collect();
|
||||
let products = self.poly.products.clone();
|
||||
|
||||
if let Some(chal) = challenge {
|
||||
if self.round == 0 {
|
||||
return Err(PolyIOPErrors::InvalidProver(
|
||||
"first round should be prover first.".to_string(),
|
||||
));
|
||||
}
|
||||
self.challenges.push(*chal);
|
||||
|
||||
// fix argument
|
||||
let i = self.round;
|
||||
let r = self.challenges[i - 1];
|
||||
#[cfg(feature = "parallel")]
|
||||
flattened_ml_extensions
|
||||
.par_iter_mut()
|
||||
.for_each(|multiplicand| *multiplicand = multiplicand.fix_variables(&[r]));
|
||||
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
flattened_ml_extensions
|
||||
.iter_mut()
|
||||
.for_each(|multiplicand| *multiplicand = multiplicand.fix_variables(&[r]));
|
||||
} else if self.round > 0 {
|
||||
return Err(PolyIOPErrors::InvalidProver(
|
||||
"verifier message is empty".to_string(),
|
||||
));
|
||||
}
|
||||
end_timer!(fix_argument);
|
||||
|
||||
self.round += 1;
|
||||
|
||||
if self.round > self.poly.domain_info.num_variables {
|
||||
return Err(PolyIOPErrors::InvalidProver(
|
||||
"Prover is not active".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let i = self.round;
|
||||
let nv = self.poly.domain_info.num_variables;
|
||||
let degree = self.poly.domain_info.max_degree; // the degree of univariate polynomial sent by prover at this round
|
||||
|
||||
let mut products_sum = Vec::with_capacity(degree + 1);
|
||||
products_sum.resize(degree + 1, F::zero());
|
||||
|
||||
let compute_sum = start_timer!(|| "compute sum");
|
||||
// generate sum
|
||||
for b in 0..1 << (nv - i) {
|
||||
#[cfg(feature = "parallel")]
|
||||
products_sum
|
||||
.par_iter_mut()
|
||||
.take(degree + 1)
|
||||
.enumerate()
|
||||
.for_each(|(i, e)| {
|
||||
// evaluate P_round(t)
|
||||
for (coefficient, products) in products.iter() {
|
||||
let num_multiplicands = products.len();
|
||||
let mut product = *coefficient;
|
||||
for &f in products.iter().take(num_multiplicands) {
|
||||
let table = &flattened_ml_extensions[f]; // f's range is checked in init
|
||||
product *= table[b << 1] * (F::one() - F::from(i as u64))
|
||||
+ table[(b << 1) + 1] * F::from(i as u64);
|
||||
}
|
||||
*e += product;
|
||||
}
|
||||
});
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
products_sum
|
||||
.iter_mut()
|
||||
.take(degree + 1)
|
||||
.enumerate()
|
||||
.for_each(|(i, e)| {
|
||||
// evaluate P_round(t)
|
||||
for (coefficient, products) in products.iter() {
|
||||
let num_multiplicands = products.len();
|
||||
let mut product = *coefficient;
|
||||
for &f in products.iter().take(num_multiplicands) {
|
||||
let table = &flattened_ml_extensions[f]; // f's range is checked in init
|
||||
product *= table[b << 1] * (F::one() - F::from(i as u64))
|
||||
+ table[(b << 1) + 1] * F::from(i as u64);
|
||||
}
|
||||
*e += product;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.poly.flattened_ml_extensions = flattened_ml_extensions
|
||||
.iter()
|
||||
.map(|x| Rc::new(x.clone()))
|
||||
.collect();
|
||||
|
||||
end_timer!(compute_sum);
|
||||
end_timer!(start);
|
||||
Ok(IOPProverMessage {
|
||||
evaluations: products_sum,
|
||||
})
|
||||
}
|
||||
}
|
||||
196
poly-iop/src/sum_check/verifier.rs
Normal file
196
poly-iop/src/sum_check/verifier.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
// TODO: some of the struct is generic for Sum Checks and Zero Checks.
|
||||
// If so move them to src/structs.rs
|
||||
|
||||
use super::SumCheckVerifier;
|
||||
use crate::{
|
||||
errors::PolyIOPErrors,
|
||||
structs::{DomainInfo, IOPProverMessage, SubClaim},
|
||||
transcript::IOPTranscript,
|
||||
};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_std::{end_timer, start_timer};
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
||||
|
||||
/// Verifier State
|
||||
pub struct VerifierState<F: PrimeField> {
|
||||
round: usize,
|
||||
num_vars: usize,
|
||||
max_degree: usize,
|
||||
finished: bool,
|
||||
/// a list storing the univariate polynomial in evaluation form sent by the
|
||||
/// prover at each round
|
||||
polynomials_received: Vec<Vec<F>>,
|
||||
/// a list storing the randomness sampled by the verifier at each round
|
||||
challenges: Vec<F>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> SumCheckVerifier<F> for VerifierState<F> {
|
||||
type DomainInfo = DomainInfo<F>;
|
||||
type ProverMessage = IOPProverMessage<F>;
|
||||
type Challenge = F;
|
||||
type Transcript = IOPTranscript<F>;
|
||||
type SubClaim = SubClaim<F>;
|
||||
|
||||
/// initialize the verifier
|
||||
fn verifier_init(index_info: &Self::DomainInfo) -> Self {
|
||||
let start = start_timer!(|| "verifier init");
|
||||
let res = VerifierState {
|
||||
round: 1,
|
||||
num_vars: index_info.num_variables,
|
||||
max_degree: index_info.max_degree,
|
||||
finished: false,
|
||||
polynomials_received: Vec::with_capacity(index_info.num_variables),
|
||||
challenges: Vec::with_capacity(index_info.num_variables),
|
||||
};
|
||||
end_timer!(start);
|
||||
res
|
||||
}
|
||||
|
||||
/// Run verifier at current round, given prover message
|
||||
///
|
||||
/// Normally, this function should perform actual verification. Instead,
|
||||
/// `verify_round` only samples and stores randomness and perform
|
||||
/// verifications altogether in `check_and_generate_subclaim` at
|
||||
/// the last step.
|
||||
fn verify_round_and_update_state(
|
||||
&mut self,
|
||||
prover_msg: &Self::ProverMessage,
|
||||
transcript: &mut Self::Transcript,
|
||||
) -> Result<Self::Challenge, PolyIOPErrors> {
|
||||
let start = start_timer!(|| format!("verify {}-th round and update state", self.round));
|
||||
|
||||
if self.finished {
|
||||
return Err(PolyIOPErrors::InvalidVerifier(
|
||||
"Incorrect verifier state: Verifier is already finished.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Now, verifier should check if the received P(0) + P(1) = expected. The check
|
||||
// is moved to `check_and_generate_subclaim`, and will be done after the
|
||||
// last round.
|
||||
|
||||
let challenge = transcript.get_and_append_challenge(b"Internal round")?;
|
||||
self.challenges.push(challenge);
|
||||
self.polynomials_received
|
||||
.push(prover_msg.evaluations.to_vec());
|
||||
|
||||
// Now, verifier should set `expected` to P(r).
|
||||
// This operation is also moved to `check_and_generate_subclaim`,
|
||||
// and will be done after the last round.
|
||||
|
||||
if self.round == self.num_vars {
|
||||
// accept and close
|
||||
self.finished = true;
|
||||
} else {
|
||||
self.round += 1;
|
||||
}
|
||||
|
||||
end_timer!(start);
|
||||
Ok(challenge)
|
||||
}
|
||||
|
||||
/// verify the sumcheck phase, and generate the subclaim
|
||||
///
|
||||
/// If the asserted sum is correct, then the multilinear polynomial
|
||||
/// evaluated at `subclaim.point` is `subclaim.expected_evaluation`.
|
||||
/// Otherwise, it is highly unlikely that those two will be equal.
|
||||
/// Larger field size guarantees smaller soundness error.
|
||||
fn check_and_generate_subclaim(
|
||||
&self,
|
||||
asserted_sum: &F,
|
||||
) -> Result<Self::SubClaim, PolyIOPErrors> {
|
||||
let start = start_timer!(|| "check_and_generate_subclaim");
|
||||
if !self.finished {
|
||||
return Err(PolyIOPErrors::InvalidVerifier(
|
||||
"Incorrect verifier state: Verifier has not finished.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if self.polynomials_received.len() != self.num_vars {
|
||||
return Err(PolyIOPErrors::InvalidVerifier(
|
||||
"insufficient rounds".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
let mut expected_vec = self
|
||||
.polynomials_received
|
||||
.clone()
|
||||
.into_par_iter()
|
||||
.zip(self.challenges.clone().into_par_iter())
|
||||
.map(|(evaluations, challenge)| {
|
||||
if evaluations.len() != self.max_degree + 1 {
|
||||
return Err(PolyIOPErrors::InvalidVerifier(format!(
|
||||
"incorrect number of evaluations: {} vs {}",
|
||||
evaluations.len(),
|
||||
self.max_degree + 1
|
||||
)));
|
||||
}
|
||||
Ok(interpolate_uni_poly::<F>(&evaluations, challenge))
|
||||
})
|
||||
.collect::<Result<Vec<_>, PolyIOPErrors>>()?;
|
||||
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let mut expected_vec = self
|
||||
.polynomials_received
|
||||
.clone()
|
||||
.into_iter()
|
||||
.zip(self.challenges.clone().into_iter())
|
||||
.map(|(evaluations, challenge)| {
|
||||
if evaluations.len() != self.max_degree + 1 {
|
||||
return Err(PolyIOPErrors::InvalidVerifier(format!(
|
||||
"incorrect number of evaluations: {} vs {}",
|
||||
evaluations.len(),
|
||||
self.max_degree + 1
|
||||
)));
|
||||
}
|
||||
Ok(interpolate_uni_poly::<F>(&evaluations, challenge))
|
||||
})
|
||||
.collect::<Result<Vec<_>, PolyIOPErrors>>()?;
|
||||
// insert the asserted_sum to the first position of the expected vector
|
||||
expected_vec.insert(0, *asserted_sum);
|
||||
|
||||
for (evaluations, &expected) in self
|
||||
.polynomials_received
|
||||
.iter()
|
||||
.zip(expected_vec.iter())
|
||||
.take(self.num_vars)
|
||||
{
|
||||
if evaluations[0] + evaluations[1] != expected {
|
||||
return Err(PolyIOPErrors::InvalidProof(
|
||||
"Prover message is not consistent with the claim.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
end_timer!(start);
|
||||
Ok(SubClaim {
|
||||
point: self.challenges.to_vec(),
|
||||
// the last expected value (unchecked) will be included in the subclaim
|
||||
expected_evaluation: expected_vec[self.num_vars],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// interpolate a uni-variate degree-`p_i.len()-1` polynomial and evaluate this
|
||||
/// polynomial at `eval_at`.
|
||||
pub(crate) fn interpolate_uni_poly<F: PrimeField>(p_i: &[F], eval_at: F) -> F {
|
||||
let start = start_timer!(|| "interpolate_uni_poly");
|
||||
let mut result = F::zero();
|
||||
let mut i = F::zero();
|
||||
for term in p_i.iter() {
|
||||
let mut term = *term;
|
||||
let mut j = F::zero();
|
||||
for _ in 0..p_i.len() {
|
||||
if j != i {
|
||||
term = term * (eval_at - j) / (i - j)
|
||||
}
|
||||
j += F::one();
|
||||
}
|
||||
i += F::one();
|
||||
result += term;
|
||||
}
|
||||
end_timer!(start);
|
||||
result
|
||||
}
|
||||
Reference in New Issue
Block a user