mirror of
https://github.com/arnaucube/hyperplonk.git
synced 2026-01-11 16:41:28 +01:00
polish IOP code base (#24)
This commit is contained in:
@@ -23,7 +23,7 @@ fn bench_sum_check() -> Result<(), PolyIOPErrors> {
|
|||||||
|
|
||||||
let (poly, asserted_sum) =
|
let (poly, asserted_sum) =
|
||||||
VirtualPolynomial::rand(nv, (degree, degree + 1), 2, &mut rng)?;
|
VirtualPolynomial::rand(nv, (degree, degree + 1), 2, &mut rng)?;
|
||||||
let poly_info = poly.domain_info.clone();
|
let poly_info = poly.aux_info.clone();
|
||||||
let proof = {
|
let proof = {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
for _ in 0..repetition {
|
for _ in 0..repetition {
|
||||||
@@ -84,7 +84,7 @@ fn bench_zero_check() -> Result<(), PolyIOPErrors> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let poly = VirtualPolynomial::rand_zero(nv, (degree, degree + 1), 2, &mut rng)?;
|
let poly = VirtualPolynomial::rand_zero(nv, (degree, degree + 1), 2, &mut rng)?;
|
||||||
let poly_info = poly.domain_info.clone();
|
let poly_info = poly.aux_info.clone();
|
||||||
let proof = {
|
let proof = {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
|
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
|
||||||
|
|||||||
@@ -12,12 +12,23 @@ mod zero_check;
|
|||||||
pub use errors::PolyIOPErrors;
|
pub use errors::PolyIOPErrors;
|
||||||
pub use sum_check::SumCheck;
|
pub use sum_check::SumCheck;
|
||||||
pub use virtual_poly::VirtualPolynomial;
|
pub use virtual_poly::VirtualPolynomial;
|
||||||
pub use zero_check::{build_eq_x_r, ZeroCheck};
|
pub use zero_check::ZeroCheck;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Copy)]
|
||||||
/// Struct for PolyIOP protocol.
|
/// Struct for PolyIOP protocol.
|
||||||
/// It is instantiated with
|
/// It has an associated type `F` that defines the prime field the multi-variate
|
||||||
|
/// polynomial operates on.
|
||||||
|
///
|
||||||
|
/// An PolyIOP may be instantiated with one of the following:
|
||||||
/// - SumCheck protocol.
|
/// - SumCheck protocol.
|
||||||
/// - ZeroCheck protocol.
|
/// - ZeroCheck protocol.
|
||||||
|
/// - PermutationCheck protocol.
|
||||||
|
///
|
||||||
|
/// Those individual protocol may have similar or identical APIs.
|
||||||
|
/// The systematic way to invoke specific protocol is, for example
|
||||||
|
/// `<PolyIOP<F> as SumCheck<F>>::prove()`
|
||||||
pub struct PolyIOP<F: PrimeField> {
|
pub struct PolyIOP<F: PrimeField> {
|
||||||
|
/// Associated field
|
||||||
|
#[doc(hidden)]
|
||||||
phantom: PhantomData<F>,
|
phantom: PhantomData<F>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,10 @@
|
|||||||
//! Structs for polynomials and extensions.
|
//! This module defines structs that are shared by all sub protocols.
|
||||||
|
|
||||||
use crate::VirtualPolynomial;
|
use crate::VirtualPolynomial;
|
||||||
use ark_ff::PrimeField;
|
use ark_ff::PrimeField;
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
/// A Subclaim is a claim generated by the verifier at the end of verification
|
||||||
/// Auxiliary information about the multilinear polynomial
|
/// when it is convinced.
|
||||||
pub struct DomainInfo<F: PrimeField> {
|
|
||||||
/// max number of multiplicands in each product
|
|
||||||
pub max_degree: usize,
|
|
||||||
/// number of variables of the polynomial
|
|
||||||
pub num_variables: usize,
|
|
||||||
/// Associated field
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub(crate) phantom: PhantomData<F>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subclaim when verifier is convinced
|
|
||||||
pub struct SubClaim<F: PrimeField> {
|
pub struct SubClaim<F: PrimeField> {
|
||||||
/// the multi-dimensional point that this multilinear extension is evaluated
|
/// the multi-dimensional point that this multilinear extension is evaluated
|
||||||
/// to
|
/// to
|
||||||
@@ -25,9 +13,8 @@ pub struct SubClaim<F: PrimeField> {
|
|||||||
pub expected_evaluation: F,
|
pub expected_evaluation: F,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An IOP proof is a list of messages from prover to verifier
|
/// An IOP proof is a collections of messages from prover to verifier at each
|
||||||
/// through the interactive protocol.
|
/// round through the interactive protocol.
|
||||||
/// It is a shared struct for both sumcheck and zerocheck protocols.
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct IOPProof<F: PrimeField> {
|
pub struct IOPProof<F: PrimeField> {
|
||||||
pub proofs: Vec<IOPProverMessage<F>>,
|
pub proofs: Vec<IOPProverMessage<F>>,
|
||||||
@@ -40,7 +27,7 @@ pub struct IOPProverMessage<F: PrimeField> {
|
|||||||
pub(crate) evaluations: Vec<F>,
|
pub(crate) evaluations: Vec<F>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prover State of a PolyIOP
|
/// Prover State of a PolyIOP.
|
||||||
pub struct IOPProverState<F: PrimeField> {
|
pub struct IOPProverState<F: PrimeField> {
|
||||||
/// sampled randomness given by the verifier
|
/// sampled randomness given by the verifier
|
||||||
pub challenges: Vec<F>,
|
pub challenges: Vec<F>,
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
//! This module implements the sum check protocol.
|
//! This module implements the sum check protocol.
|
||||||
//! Currently this is a simple wrapper of the sumcheck protocol
|
|
||||||
//! from Arkworks.
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::PolyIOPErrors,
|
errors::PolyIOPErrors,
|
||||||
structs::{DomainInfo, IOPProof, IOPProverState, IOPVerifierState, SubClaim},
|
structs::{IOPProof, IOPProverState, IOPVerifierState, SubClaim},
|
||||||
transcript::IOPTranscript,
|
transcript::IOPTranscript,
|
||||||
virtual_poly::VirtualPolynomial,
|
virtual_poly::{VPAuxInfo, VirtualPolynomial},
|
||||||
PolyIOP,
|
PolyIOP,
|
||||||
};
|
};
|
||||||
use ark_ff::PrimeField;
|
use ark_ff::PrimeField;
|
||||||
@@ -18,12 +16,12 @@ mod verifier;
|
|||||||
/// Trait for doing sum check protocols.
|
/// Trait for doing sum check protocols.
|
||||||
pub trait SumCheck<F: PrimeField> {
|
pub trait SumCheck<F: PrimeField> {
|
||||||
type Proof;
|
type Proof;
|
||||||
type PolyList;
|
type VirtualPolynomial;
|
||||||
type DomainInfo;
|
type VPAuxInfo;
|
||||||
type SubClaim;
|
type SubClaim;
|
||||||
type Transcript;
|
type Transcript;
|
||||||
|
|
||||||
/// extract sum from the proof
|
/// Extract sum from the proof
|
||||||
fn extract_sum(proof: &Self::Proof) -> F;
|
fn extract_sum(proof: &Self::Proof) -> F;
|
||||||
|
|
||||||
/// Initialize the system with a transcript
|
/// Initialize the system with a transcript
|
||||||
@@ -38,7 +36,7 @@ pub trait SumCheck<F: PrimeField> {
|
|||||||
///
|
///
|
||||||
/// The polynomial is represented in the form of a VirtualPolynomial.
|
/// The polynomial is represented in the form of a VirtualPolynomial.
|
||||||
fn prove(
|
fn prove(
|
||||||
poly: &Self::PolyList,
|
poly: &Self::VirtualPolynomial,
|
||||||
transcript: &mut Self::Transcript,
|
transcript: &mut Self::Transcript,
|
||||||
) -> Result<Self::Proof, PolyIOPErrors>;
|
) -> Result<Self::Proof, PolyIOPErrors>;
|
||||||
|
|
||||||
@@ -46,7 +44,7 @@ pub trait SumCheck<F: PrimeField> {
|
|||||||
fn verify(
|
fn verify(
|
||||||
sum: F,
|
sum: F,
|
||||||
proof: &Self::Proof,
|
proof: &Self::Proof,
|
||||||
domain_info: &Self::DomainInfo,
|
aux_info: &Self::VPAuxInfo,
|
||||||
transcript: &mut Self::Transcript,
|
transcript: &mut Self::Transcript,
|
||||||
) -> Result<Self::SubClaim, PolyIOPErrors>;
|
) -> Result<Self::SubClaim, PolyIOPErrors>;
|
||||||
}
|
}
|
||||||
@@ -56,17 +54,15 @@ pub trait SumCheckProver<F: PrimeField>
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
type PolyList;
|
type VirtualPolynomial;
|
||||||
type ProverMessage;
|
type ProverMessage;
|
||||||
|
|
||||||
/// Initialize the prover to argue for the sum of polynomial over
|
/// Initialize the prover state to argue for the sum of the input polynomial
|
||||||
/// {0,1}^`num_vars`
|
/// over {0,1}^`num_vars`.
|
||||||
///
|
fn prover_init(polynomial: &Self::VirtualPolynomial) -> Result<Self, PolyIOPErrors>;
|
||||||
/// The polynomial is represented in the form of a VirtualPolynomial.
|
|
||||||
fn prover_init(polynomial: &Self::PolyList) -> Result<Self, PolyIOPErrors>;
|
|
||||||
|
|
||||||
/// receive message from verifier, generate prover message, and proceed to
|
/// Receive message from verifier, generate prover message, and proceed to
|
||||||
/// next round
|
/// next round.
|
||||||
///
|
///
|
||||||
/// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2).
|
/// 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(
|
fn prove_round_and_update_state(
|
||||||
@@ -77,31 +73,33 @@ where
|
|||||||
|
|
||||||
/// Trait for sum check protocol verifier side APIs.
|
/// Trait for sum check protocol verifier side APIs.
|
||||||
pub trait SumCheckVerifier<F: PrimeField> {
|
pub trait SumCheckVerifier<F: PrimeField> {
|
||||||
type DomainInfo;
|
type VPAuxInfo;
|
||||||
type ProverMessage;
|
type ProverMessage;
|
||||||
type Challenge;
|
type Challenge;
|
||||||
type Transcript;
|
type Transcript;
|
||||||
type SubClaim;
|
type SubClaim;
|
||||||
|
|
||||||
/// initialize the verifier
|
/// Initialize the verifier's state.
|
||||||
fn verifier_init(index_info: &Self::DomainInfo) -> Self;
|
fn verifier_init(index_info: &Self::VPAuxInfo) -> Self;
|
||||||
|
|
||||||
/// Run verifier at current round, given prover message
|
/// Run verifier for the current round, given a prover message.
|
||||||
///
|
///
|
||||||
/// Normally, this function should perform actual verification. Instead,
|
/// Note that `verify_round_and_update_state` only samples and stores
|
||||||
/// `verify_round` only samples and stores randomness and perform
|
/// challenges; and update the verifier's state accordingly. The actual
|
||||||
/// verifications altogether in `check_and_generate_subclaim` at
|
/// verifications are deferred (in batch) to `check_and_generate_subclaim`
|
||||||
/// the last step.
|
/// at the last step.
|
||||||
fn verify_round_and_update_state(
|
fn verify_round_and_update_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
prover_msg: &Self::ProverMessage,
|
prover_msg: &Self::ProverMessage,
|
||||||
transcript: &mut Self::Transcript,
|
transcript: &mut Self::Transcript,
|
||||||
) -> Result<Self::Challenge, PolyIOPErrors>;
|
) -> Result<Self::Challenge, PolyIOPErrors>;
|
||||||
|
|
||||||
/// verify the sumcheck phase, and generate the subclaim
|
/// This function verifies the deferred checks in the interactive version of
|
||||||
|
/// the protocol; and generate the subclaim. Returns an error if the
|
||||||
|
/// proof failed to verify.
|
||||||
///
|
///
|
||||||
/// If the asserted sum is correct, then the multilinear polynomial
|
/// If the asserted sum is correct, then the multilinear polynomial
|
||||||
/// evaluated at `subclaim.point` is `subclaim.expected_evaluation`.
|
/// evaluated at `subclaim.point` will be `subclaim.expected_evaluation`.
|
||||||
/// Otherwise, it is highly unlikely that those two will be equal.
|
/// Otherwise, it is highly unlikely that those two will be equal.
|
||||||
/// Larger field size guarantees smaller soundness error.
|
/// Larger field size guarantees smaller soundness error.
|
||||||
fn check_and_generate_subclaim(
|
fn check_and_generate_subclaim(
|
||||||
@@ -112,15 +110,12 @@ pub trait SumCheckVerifier<F: PrimeField> {
|
|||||||
|
|
||||||
impl<F: PrimeField> SumCheck<F> for PolyIOP<F> {
|
impl<F: PrimeField> SumCheck<F> for PolyIOP<F> {
|
||||||
type Proof = IOPProof<F>;
|
type Proof = IOPProof<F>;
|
||||||
|
type VirtualPolynomial = VirtualPolynomial<F>;
|
||||||
type PolyList = VirtualPolynomial<F>;
|
type VPAuxInfo = VPAuxInfo<F>;
|
||||||
|
|
||||||
type DomainInfo = DomainInfo<F>;
|
|
||||||
|
|
||||||
type SubClaim = SubClaim<F>;
|
type SubClaim = SubClaim<F>;
|
||||||
|
|
||||||
type Transcript = IOPTranscript<F>;
|
type Transcript = IOPTranscript<F>;
|
||||||
|
|
||||||
|
/// Extract sum from the proof
|
||||||
fn extract_sum(proof: &Self::Proof) -> F {
|
fn extract_sum(proof: &Self::Proof) -> F {
|
||||||
let start = start_timer!(|| "extract sum");
|
let start = start_timer!(|| "extract sum");
|
||||||
let res = proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1];
|
let res = proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1];
|
||||||
@@ -145,17 +140,17 @@ impl<F: PrimeField> SumCheck<F> for PolyIOP<F> {
|
|||||||
///
|
///
|
||||||
/// The polynomial is represented in the form of a VirtualPolynomial.
|
/// The polynomial is represented in the form of a VirtualPolynomial.
|
||||||
fn prove(
|
fn prove(
|
||||||
poly: &Self::PolyList,
|
poly: &Self::VirtualPolynomial,
|
||||||
transcript: &mut Self::Transcript,
|
transcript: &mut Self::Transcript,
|
||||||
) -> Result<Self::Proof, PolyIOPErrors> {
|
) -> Result<Self::Proof, PolyIOPErrors> {
|
||||||
let start = start_timer!(|| "sum check prove");
|
let start = start_timer!(|| "sum check prove");
|
||||||
|
|
||||||
transcript.append_domain_info(&poly.domain_info)?;
|
transcript.append_aux_info(&poly.aux_info)?;
|
||||||
|
|
||||||
let mut prover_state = IOPProverState::prover_init(poly)?;
|
let mut prover_state = IOPProverState::prover_init(poly)?;
|
||||||
let mut challenge = None;
|
let mut challenge = None;
|
||||||
let mut prover_msgs = Vec::with_capacity(poly.domain_info.num_variables);
|
let mut prover_msgs = Vec::with_capacity(poly.aux_info.num_variables);
|
||||||
for _ in 0..poly.domain_info.num_variables {
|
for _ in 0..poly.aux_info.num_variables {
|
||||||
let prover_msg =
|
let prover_msg =
|
||||||
IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)?;
|
IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)?;
|
||||||
transcript.append_prover_message(&prover_msg)?;
|
transcript.append_prover_message(&prover_msg)?;
|
||||||
@@ -169,18 +164,18 @@ impl<F: PrimeField> SumCheck<F> for PolyIOP<F> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// verify the claimed sum using the proof
|
/// Verify the claimed sum using the proof
|
||||||
fn verify(
|
fn verify(
|
||||||
claimed_sum: F,
|
claimed_sum: F,
|
||||||
proof: &Self::Proof,
|
proof: &Self::Proof,
|
||||||
domain_info: &Self::DomainInfo,
|
aux_info: &Self::VPAuxInfo,
|
||||||
transcript: &mut Self::Transcript,
|
transcript: &mut Self::Transcript,
|
||||||
) -> Result<Self::SubClaim, PolyIOPErrors> {
|
) -> Result<Self::SubClaim, PolyIOPErrors> {
|
||||||
let start = start_timer!(|| "sum check verify");
|
let start = start_timer!(|| "sum check verify");
|
||||||
|
|
||||||
transcript.append_domain_info(domain_info)?;
|
transcript.append_aux_info(aux_info)?;
|
||||||
let mut verifier_state = IOPVerifierState::verifier_init(domain_info);
|
let mut verifier_state = IOPVerifierState::verifier_init(aux_info);
|
||||||
for i in 0..domain_info.num_variables {
|
for i in 0..aux_info.num_variables {
|
||||||
let prover_msg = proof.proofs.get(i).expect("proof is incomplete");
|
let prover_msg = proof.proofs.get(i).expect("proof is incomplete");
|
||||||
transcript.append_prover_message(prover_msg)?;
|
transcript.append_prover_message(prover_msg)?;
|
||||||
IOPVerifierState::verify_round_and_update_state(
|
IOPVerifierState::verify_round_and_update_state(
|
||||||
@@ -218,7 +213,7 @@ mod test {
|
|||||||
let (poly, asserted_sum) =
|
let (poly, asserted_sum) =
|
||||||
VirtualPolynomial::rand(nv, num_multiplicands_range, num_products, &mut rng)?;
|
VirtualPolynomial::rand(nv, num_multiplicands_range, num_products, &mut rng)?;
|
||||||
let proof = <PolyIOP<Fr> as SumCheck<Fr>>::prove(&poly, &mut transcript)?;
|
let proof = <PolyIOP<Fr> as SumCheck<Fr>>::prove(&poly, &mut transcript)?;
|
||||||
let poly_info = poly.domain_info.clone();
|
let poly_info = poly.aux_info.clone();
|
||||||
let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript();
|
let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript();
|
||||||
let subclaim = <PolyIOP<Fr> as SumCheck<Fr>>::verify(
|
let subclaim = <PolyIOP<Fr> as SumCheck<Fr>>::verify(
|
||||||
asserted_sum,
|
asserted_sum,
|
||||||
@@ -241,7 +236,7 @@ mod test {
|
|||||||
let mut rng = test_rng();
|
let mut rng = test_rng();
|
||||||
let (poly, asserted_sum) =
|
let (poly, asserted_sum) =
|
||||||
VirtualPolynomial::<Fr>::rand(nv, num_multiplicands_range, num_products, &mut rng)?;
|
VirtualPolynomial::<Fr>::rand(nv, num_multiplicands_range, num_products, &mut rng)?;
|
||||||
let poly_info = poly.domain_info.clone();
|
let poly_info = poly.aux_info.clone();
|
||||||
let mut prover_state = IOPProverState::prover_init(&poly)?;
|
let mut prover_state = IOPProverState::prover_init(&poly)?;
|
||||||
let mut verifier_state = IOPVerifierState::verifier_init(&poly_info);
|
let mut verifier_state = IOPVerifierState::verifier_init(&poly_info);
|
||||||
let mut challenge = None;
|
let mut challenge = None;
|
||||||
@@ -249,7 +244,7 @@ mod test {
|
|||||||
transcript
|
transcript
|
||||||
.append_message(b"testing", b"initializing transcript for testing")
|
.append_message(b"testing", b"initializing transcript for testing")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
for _ in 0..poly.domain_info.num_variables {
|
for _ in 0..poly.aux_info.num_variables {
|
||||||
let prover_message =
|
let prover_message =
|
||||||
IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)
|
IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -362,7 +357,7 @@ mod test {
|
|||||||
drop(prover);
|
drop(prover);
|
||||||
|
|
||||||
let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript();
|
let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript();
|
||||||
let poly_info = poly.domain_info.clone();
|
let poly_info = poly.aux_info.clone();
|
||||||
let proof = <PolyIOP<Fr> as SumCheck<Fr>>::prove(&poly, &mut transcript)?;
|
let proof = <PolyIOP<Fr> as SumCheck<Fr>>::prove(&poly, &mut transcript)?;
|
||||||
let asserted_sum = <PolyIOP<Fr> as SumCheck<Fr>>::extract_sum(&proof);
|
let asserted_sum = <PolyIOP<Fr> as SumCheck<Fr>>::extract_sum(&proof);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! Prover
|
//! Prover subroutines for a SumCheck protocol.
|
||||||
|
|
||||||
use super::SumCheckProver;
|
use super::SumCheckProver;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -15,14 +15,14 @@ use std::rc::Rc;
|
|||||||
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||||
|
|
||||||
impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> {
|
impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> {
|
||||||
type PolyList = VirtualPolynomial<F>;
|
type VirtualPolynomial = VirtualPolynomial<F>;
|
||||||
type ProverMessage = IOPProverMessage<F>;
|
type ProverMessage = IOPProverMessage<F>;
|
||||||
|
|
||||||
/// Initialize the prover to argue for the sum of polynomial over
|
/// Initialize the prover state to argue for the sum of the input polynomial
|
||||||
/// {0,1}^`num_vars`
|
/// over {0,1}^`num_vars`.
|
||||||
fn prover_init(polynomial: &Self::PolyList) -> Result<Self, PolyIOPErrors> {
|
fn prover_init(polynomial: &Self::VirtualPolynomial) -> Result<Self, PolyIOPErrors> {
|
||||||
let start = start_timer!(|| "sum check prover init");
|
let start = start_timer!(|| "sum check prover init");
|
||||||
if polynomial.domain_info.num_variables == 0 {
|
if polynomial.aux_info.num_variables == 0 {
|
||||||
return Err(PolyIOPErrors::InvalidParameters(
|
return Err(PolyIOPErrors::InvalidParameters(
|
||||||
"Attempt to prove a constant.".to_string(),
|
"Attempt to prove a constant.".to_string(),
|
||||||
));
|
));
|
||||||
@@ -30,14 +30,14 @@ impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> {
|
|||||||
end_timer!(start);
|
end_timer!(start);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
challenges: Vec::with_capacity(polynomial.domain_info.num_variables),
|
challenges: Vec::with_capacity(polynomial.aux_info.num_variables),
|
||||||
round: 0,
|
round: 0,
|
||||||
poly: polynomial.clone(),
|
poly: polynomial.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive message from verifier, generate prover message, and proceed to
|
/// Receive message from verifier, generate prover message, and proceed to
|
||||||
/// next round
|
/// next round.
|
||||||
///
|
///
|
||||||
/// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2).
|
/// 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(
|
fn prove_round_and_update_state(
|
||||||
@@ -47,8 +47,25 @@ impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> {
|
|||||||
let start =
|
let start =
|
||||||
start_timer!(|| format!("sum check prove {}-th round and update state", self.round));
|
start_timer!(|| format!("sum check prove {}-th round and update state", self.round));
|
||||||
|
|
||||||
|
if self.round >= self.poly.aux_info.num_variables {
|
||||||
|
return Err(PolyIOPErrors::InvalidProver(
|
||||||
|
"Prover is not active".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let fix_argument = start_timer!(|| "fix argument");
|
let fix_argument = start_timer!(|| "fix argument");
|
||||||
|
|
||||||
|
// Step 1:
|
||||||
|
// fix argument and evaluate f(x) over x_m = r; where r is the challenge
|
||||||
|
// for the current round, and m is the round number, indexed from 1
|
||||||
|
//
|
||||||
|
// i.e.:
|
||||||
|
// at round m <=n, for each mle g(x_1, ... x_n) within the flattened_mle
|
||||||
|
// which has already been evaluated to
|
||||||
|
//
|
||||||
|
// g(r_1, ..., r_{m-1}, x_m ... x_n)
|
||||||
|
//
|
||||||
|
// eval g over r_m, and mutate g to g(r_1, ... r_m,, x_{m+1}... x_n)
|
||||||
let mut flattened_ml_extensions: Vec<DenseMultilinearExtension<F>> = self
|
let mut flattened_ml_extensions: Vec<DenseMultilinearExtension<F>> = self
|
||||||
.poly
|
.poly
|
||||||
.flattened_ml_extensions
|
.flattened_ml_extensions
|
||||||
@@ -64,18 +81,16 @@ impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> {
|
|||||||
}
|
}
|
||||||
self.challenges.push(*chal);
|
self.challenges.push(*chal);
|
||||||
|
|
||||||
// fix argument
|
let r = self.challenges[self.round - 1];
|
||||||
let i = self.round;
|
|
||||||
let r = self.challenges[i - 1];
|
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
flattened_ml_extensions
|
flattened_ml_extensions
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.for_each(|multiplicand| *multiplicand = multiplicand.fix_variables(&[r]));
|
.for_each(|mle| *mle = mle.fix_variables(&[r]));
|
||||||
|
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
flattened_ml_extensions
|
flattened_ml_extensions
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|multiplicand| *multiplicand = multiplicand.fix_variables(&[r]));
|
.for_each(|mle| *mle = mle.fix_variables(&[r]));
|
||||||
} else if self.round > 0 {
|
} else if self.round > 0 {
|
||||||
return Err(PolyIOPErrors::InvalidProver(
|
return Err(PolyIOPErrors::InvalidProver(
|
||||||
"verifier message is empty".to_string(),
|
"verifier message is empty".to_string(),
|
||||||
@@ -85,30 +100,22 @@ impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> {
|
|||||||
|
|
||||||
self.round += 1;
|
self.round += 1;
|
||||||
|
|
||||||
if self.round > self.poly.domain_info.num_variables {
|
|
||||||
return Err(PolyIOPErrors::InvalidProver(
|
|
||||||
"Prover is not active".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let products_list = self.poly.products.clone();
|
let products_list = self.poly.products.clone();
|
||||||
let i = self.round;
|
let mut products_sum = Vec::with_capacity(self.poly.aux_info.max_degree + 1);
|
||||||
let nv = self.poly.domain_info.num_variables;
|
products_sum.resize(self.poly.aux_info.max_degree + 1, F::zero());
|
||||||
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");
|
let compute_sum = start_timer!(|| "compute sum");
|
||||||
// generate sum
|
// Step 2: generate sum for the partial evaluated polynomial:
|
||||||
|
// f(r_1, ... r_m,, x_{m+1}... x_n)
|
||||||
|
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
products_sum.par_iter_mut().enumerate().for_each(|(t, e)| {
|
products_sum.par_iter_mut().enumerate().for_each(|(t, e)| {
|
||||||
for b in 0..1 << (nv - i) {
|
for b in 0..1 << (self.poly.aux_info.num_variables - self.round) {
|
||||||
// evaluate P_round(t)
|
// evaluate P_round(t)
|
||||||
for (coefficient, products) in products_list.iter() {
|
for (coefficient, products) in products_list.iter() {
|
||||||
let num_multiplicands = products.len();
|
let num_mles = products.len();
|
||||||
let mut product = *coefficient;
|
let mut product = *coefficient;
|
||||||
for &f in products.iter().take(num_multiplicands) {
|
for &f in products.iter().take(num_mles) {
|
||||||
let table = &flattened_ml_extensions[f]; // f's range is checked in init
|
let table = &flattened_ml_extensions[f]; // f's range is checked in init
|
||||||
product *= table[b << 1] * (F::one() - F::from(t as u64))
|
product *= table[b << 1] * (F::one() - F::from(t as u64))
|
||||||
+ table[(b << 1) + 1] * F::from(t as u64);
|
+ table[(b << 1) + 1] * F::from(t as u64);
|
||||||
@@ -119,26 +126,23 @@ impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
for b in 0..1 << (nv - i) {
|
products_sum.iter_mut().enumerate().for_each(|(t, e)| {
|
||||||
products_sum
|
for b in 0..1 << (self.poly.aux_info.num_variables - self.round) {
|
||||||
.iter_mut()
|
|
||||||
.take(degree + 1)
|
|
||||||
.enumerate()
|
|
||||||
.for_each(|(t, e)| {
|
|
||||||
// evaluate P_round(t)
|
// evaluate P_round(t)
|
||||||
for (coefficient, products) in products_list.iter() {
|
for (coefficient, products) in products_list.iter() {
|
||||||
let num_multiplicands = products.len();
|
let num_mles = products.len();
|
||||||
let mut product = *coefficient;
|
let mut product = *coefficient;
|
||||||
for &f in products.iter().take(num_multiplicands) {
|
for &f in products.iter().take(num_mles) {
|
||||||
let table = &flattened_ml_extensions[f]; // f's range is checked in init
|
let table = &flattened_ml_extensions[f]; // f's range is checked in init
|
||||||
product *= table[b << 1] * (F::one() - F::from(t as u64))
|
product *= table[b << 1] * (F::one() - F::from(t as u64))
|
||||||
+ table[(b << 1) + 1] * F::from(t as u64);
|
+ table[(b << 1) + 1] * F::from(t as u64);
|
||||||
}
|
}
|
||||||
*e += product;
|
*e += product;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// update prover's state to the partial evaluated polynomial
|
||||||
self.poly.flattened_ml_extensions = flattened_ml_extensions
|
self.poly.flattened_ml_extensions = flattened_ml_extensions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| Rc::new(x.clone()))
|
.map(|x| Rc::new(x.clone()))
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// TODO: some of the struct is generic for Sum Checks and Zero Checks.
|
//! Verifier subroutines for a SumCheck protocol.
|
||||||
// If so move them to src/structs.rs
|
|
||||||
|
|
||||||
use super::SumCheckVerifier;
|
use super::SumCheckVerifier;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::PolyIOPErrors,
|
errors::PolyIOPErrors,
|
||||||
structs::{DomainInfo, IOPProverMessage, IOPVerifierState, SubClaim},
|
structs::{IOPProverMessage, IOPVerifierState, SubClaim},
|
||||||
transcript::IOPTranscript,
|
transcript::IOPTranscript,
|
||||||
|
virtual_poly::VPAuxInfo,
|
||||||
};
|
};
|
||||||
use ark_ff::PrimeField;
|
use ark_ff::PrimeField;
|
||||||
use ark_std::{end_timer, start_timer};
|
use ark_std::{end_timer, start_timer};
|
||||||
@@ -14,14 +14,14 @@ use ark_std::{end_timer, start_timer};
|
|||||||
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
||||||
|
|
||||||
impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
|
impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
|
||||||
type DomainInfo = DomainInfo<F>;
|
type VPAuxInfo = VPAuxInfo<F>;
|
||||||
type ProverMessage = IOPProverMessage<F>;
|
type ProverMessage = IOPProverMessage<F>;
|
||||||
type Challenge = F;
|
type Challenge = F;
|
||||||
type Transcript = IOPTranscript<F>;
|
type Transcript = IOPTranscript<F>;
|
||||||
type SubClaim = SubClaim<F>;
|
type SubClaim = SubClaim<F>;
|
||||||
|
|
||||||
/// initialize the verifier
|
/// Initialize the verifier's state.
|
||||||
fn verifier_init(index_info: &Self::DomainInfo) -> Self {
|
fn verifier_init(index_info: &Self::VPAuxInfo) -> Self {
|
||||||
let start = start_timer!(|| "sum check verifier init");
|
let start = start_timer!(|| "sum check verifier init");
|
||||||
let res = Self {
|
let res = Self {
|
||||||
round: 1,
|
round: 1,
|
||||||
@@ -35,12 +35,12 @@ impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run verifier at current round, given prover message
|
/// Run verifier for the current round, given a prover message.
|
||||||
///
|
///
|
||||||
/// Normally, this function should perform actual verification. Instead,
|
/// Note that `verify_round_and_update_state` only samples and stores
|
||||||
/// `verify_round` only samples and stores randomness and perform
|
/// challenges; and update the verifier's state accordingly. The actual
|
||||||
/// verifications altogether in `check_and_generate_subclaim` at
|
/// verifications are deferred (in batch) to `check_and_generate_subclaim`
|
||||||
/// the last step.
|
/// at the last step.
|
||||||
fn verify_round_and_update_state(
|
fn verify_round_and_update_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
prover_msg: &Self::ProverMessage,
|
prover_msg: &Self::ProverMessage,
|
||||||
@@ -55,23 +55,24 @@ impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, verifier should check if the received P(0) + P(1) = expected. The check
|
// In an interactive protocol, the verifier should
|
||||||
// is moved to `check_and_generate_subclaim`, and will be done after the
|
//
|
||||||
// last round.
|
// 1. check if the received 'P(0) + P(1) = expected`.
|
||||||
|
// 2. set `expected` to P(r)`
|
||||||
|
//
|
||||||
|
// When we turn the protocol to a non-interactive one, it is sufficient to defer
|
||||||
|
// such checks to `check_and_generate_subclaim` after the last round.
|
||||||
|
|
||||||
let challenge = transcript.get_and_append_challenge(b"Internal round")?;
|
let challenge = transcript.get_and_append_challenge(b"Internal round")?;
|
||||||
self.challenges.push(challenge);
|
self.challenges.push(challenge);
|
||||||
self.polynomials_received
|
self.polynomials_received
|
||||||
.push(prover_msg.evaluations.to_vec());
|
.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 {
|
if self.round == self.num_vars {
|
||||||
// accept and close
|
// accept and close
|
||||||
self.finished = true;
|
self.finished = true;
|
||||||
} else {
|
} else {
|
||||||
|
// proceed to the next round
|
||||||
self.round += 1;
|
self.round += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,10 +80,12 @@ impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
|
|||||||
Ok(challenge)
|
Ok(challenge)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// verify the sumcheck phase, and generate the subclaim
|
/// This function verifies the deferred checks in the interactive version of
|
||||||
|
/// the protocol; and generate the subclaim. Returns an error if the
|
||||||
|
/// proof failed to verify.
|
||||||
///
|
///
|
||||||
/// If the asserted sum is correct, then the multilinear polynomial
|
/// If the asserted sum is correct, then the multilinear polynomial
|
||||||
/// evaluated at `subclaim.point` is `subclaim.expected_evaluation`.
|
/// evaluated at `subclaim.point` will be `subclaim.expected_evaluation`.
|
||||||
/// Otherwise, it is highly unlikely that those two will be equal.
|
/// Otherwise, it is highly unlikely that those two will be equal.
|
||||||
/// Larger field size guarantees smaller soundness error.
|
/// Larger field size guarantees smaller soundness error.
|
||||||
fn check_and_generate_subclaim(
|
fn check_and_generate_subclaim(
|
||||||
@@ -102,6 +105,8 @@ impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the deferred check during the interactive phase:
|
||||||
|
// 2. set `expected` to P(r)`
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
let mut expected_vec = self
|
let mut expected_vec = self
|
||||||
.polynomials_received
|
.polynomials_received
|
||||||
@@ -137,6 +142,7 @@ impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
|
|||||||
interpolate_uni_poly::<F>(&evaluations, challenge)
|
interpolate_uni_poly::<F>(&evaluations, challenge)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, PolyIOPErrors>>()?;
|
.collect::<Result<Vec<_>, PolyIOPErrors>>()?;
|
||||||
|
|
||||||
// insert the asserted_sum to the first position of the expected vector
|
// insert the asserted_sum to the first position of the expected vector
|
||||||
expected_vec.insert(0, *asserted_sum);
|
expected_vec.insert(0, *asserted_sum);
|
||||||
|
|
||||||
@@ -146,6 +152,8 @@ impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
|
|||||||
.zip(expected_vec.iter())
|
.zip(expected_vec.iter())
|
||||||
.take(self.num_vars)
|
.take(self.num_vars)
|
||||||
{
|
{
|
||||||
|
// the deferred check during the interactive phase:
|
||||||
|
// 1. check if the received 'P(0) + P(1) = expected`.
|
||||||
if evaluations[0] + evaluations[1] != expected {
|
if evaluations[0] + evaluations[1] != expected {
|
||||||
return Err(PolyIOPErrors::InvalidProof(
|
return Err(PolyIOPErrors::InvalidProof(
|
||||||
"Prover message is not consistent with the claim.".to_string(),
|
"Prover message is not consistent with the claim.".to_string(),
|
||||||
@@ -154,8 +162,9 @@ impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
|
|||||||
}
|
}
|
||||||
end_timer!(start);
|
end_timer!(start);
|
||||||
Ok(SubClaim {
|
Ok(SubClaim {
|
||||||
point: self.challenges.to_vec(),
|
point: self.challenges.clone(),
|
||||||
// the last expected value (unchecked) will be included in the subclaim
|
// the last expected value (not checked within this function) will be included in the
|
||||||
|
// subclaim
|
||||||
expected_evaluation: expected_vec[self.num_vars],
|
expected_evaluation: expected_vec[self.num_vars],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -163,19 +172,20 @@ impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
|
|||||||
|
|
||||||
/// Interpolate a uni-variate degree-`p_i.len()-1` polynomial and evaluate this
|
/// Interpolate a uni-variate degree-`p_i.len()-1` polynomial and evaluate this
|
||||||
/// polynomial at `eval_at`:
|
/// polynomial at `eval_at`:
|
||||||
|
///
|
||||||
/// \sum_{i=0}^len p_i * (\prod_{j!=i} (eval_at - j)/(i-j) )
|
/// \sum_{i=0}^len p_i * (\prod_{j!=i} (eval_at - j)/(i-j) )
|
||||||
|
///
|
||||||
/// This implementation is linear in number of inputs in terms of field
|
/// This implementation is linear in number of inputs in terms of field
|
||||||
/// operations. It also has a quadratic term in primitive operations which is
|
/// operations. It also has a quadratic term in primitive operations which is
|
||||||
/// negligible compared to field operations.
|
/// negligible compared to field operations.
|
||||||
pub(crate) fn interpolate_uni_poly<F: PrimeField>(
|
fn interpolate_uni_poly<F: PrimeField>(p_i: &[F], eval_at: F) -> Result<F, PolyIOPErrors> {
|
||||||
p_i: &[F],
|
|
||||||
eval_at: F,
|
|
||||||
) -> Result<F, PolyIOPErrors> {
|
|
||||||
let start = start_timer!(|| "sum check interpolate uni poly opt");
|
let start = start_timer!(|| "sum check interpolate uni poly opt");
|
||||||
|
|
||||||
let mut res = F::zero();
|
let mut res = F::zero();
|
||||||
|
|
||||||
// prod = \prod_{j!=i} (eval_at - j)
|
// compute
|
||||||
|
// - prod = \prod (eval_at - j)
|
||||||
|
// - evals = [eval_at - j]
|
||||||
let mut evals = vec![];
|
let mut evals = vec![];
|
||||||
let len = p_i.len();
|
let len = p_i.len();
|
||||||
let mut prod = eval_at;
|
let mut prod = eval_at;
|
||||||
@@ -188,6 +198,7 @@ pub(crate) fn interpolate_uni_poly<F: PrimeField>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..len {
|
for i in 0..len {
|
||||||
|
// res += p_i * prod / (divisor * (eval_at - j))
|
||||||
let divisor = get_divisor(i, len)?;
|
let divisor = get_divisor(i, len)?;
|
||||||
let divisor_f = {
|
let divisor_f = {
|
||||||
if divisor < 0 {
|
if divisor < 0 {
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
use std::marker::PhantomData;
|
//! Module for PolyIOP transcript.
|
||||||
|
//! TODO(ZZ): move this module to HyperPlonk where the transcript will also be
|
||||||
|
//! useful.
|
||||||
|
//! TODO(ZZ): decide which APIs need to be public.
|
||||||
|
|
||||||
use ark_ff::PrimeField;
|
use ark_ff::PrimeField;
|
||||||
use merlin::Transcript;
|
use merlin::Transcript;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{
|
use crate::{errors::PolyIOPErrors, structs::IOPProverMessage, to_bytes, virtual_poly::VPAuxInfo};
|
||||||
errors::PolyIOPErrors,
|
|
||||||
structs::{DomainInfo, IOPProverMessage},
|
|
||||||
to_bytes,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/// An IOP transcript consists of a Merlin transcript and a flag `is_empty` to
|
||||||
|
/// indicate that if the transcript is empty.
|
||||||
|
///
|
||||||
|
/// It is associated with a prime field `F` for which challenges are generated
|
||||||
|
/// over.
|
||||||
|
///
|
||||||
|
/// The `is_empty` flag is useful in the case where a protocol is initiated by
|
||||||
|
/// the verifier, in which case the prover should start its phase by receiving a
|
||||||
|
/// `non-empty` transcript.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct IOPTranscript<F: PrimeField> {
|
pub struct IOPTranscript<F: PrimeField> {
|
||||||
transcript: Transcript,
|
transcript: Transcript,
|
||||||
is_empty: bool,
|
is_empty: bool,
|
||||||
@@ -17,7 +27,7 @@ pub struct IOPTranscript<F: PrimeField> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<F: PrimeField> IOPTranscript<F> {
|
impl<F: PrimeField> IOPTranscript<F> {
|
||||||
/// create a new IOP transcript
|
/// Create a new IOP transcript.
|
||||||
pub(crate) fn new(label: &'static [u8]) -> Self {
|
pub(crate) fn new(label: &'static [u8]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
transcript: Transcript::new(label),
|
transcript: Transcript::new(label),
|
||||||
@@ -26,7 +36,7 @@ impl<F: PrimeField> IOPTranscript<F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the message to the transcript
|
// Append the message to the transcript.
|
||||||
pub fn append_message(
|
pub fn append_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: &'static [u8],
|
label: &'static [u8],
|
||||||
@@ -37,20 +47,18 @@ impl<F: PrimeField> IOPTranscript<F> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn append_domain_info(
|
// Append the aux information for a virtual polynomial.
|
||||||
&mut self,
|
pub(crate) fn append_aux_info(&mut self, aux_info: &VPAuxInfo<F>) -> Result<(), PolyIOPErrors> {
|
||||||
domain_info: &DomainInfo<F>,
|
|
||||||
) -> Result<(), PolyIOPErrors> {
|
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"max_mul {} num_var {}",
|
"max_mul {} num_var {}",
|
||||||
domain_info.max_degree, domain_info.num_variables
|
aux_info.max_degree, aux_info.num_variables
|
||||||
);
|
);
|
||||||
self.append_message(b"aux info", message.as_bytes())?;
|
self.append_message(b"aux info", message.as_bytes())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the message to the transcript
|
// Append the message to the transcript.
|
||||||
pub(crate) fn append_field_element(
|
pub(crate) fn append_field_element(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: &'static [u8],
|
label: &'static [u8],
|
||||||
@@ -59,6 +67,7 @@ impl<F: PrimeField> IOPTranscript<F> {
|
|||||||
self.append_message(label, &to_bytes!(field_elem)?)
|
self.append_message(label, &to_bytes!(field_elem)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append a prover message to the transcript.
|
||||||
pub(crate) fn append_prover_message(
|
pub(crate) fn append_prover_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
prover_message: &IOPProverMessage<F>,
|
prover_message: &IOPProverMessage<F>,
|
||||||
@@ -69,12 +78,16 @@ impl<F: PrimeField> IOPTranscript<F> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the challenge for the current transcript
|
// Generate the challenge from the current transcript
|
||||||
// and append it to the transcript
|
// and append it to the transcript.
|
||||||
|
//
|
||||||
|
// The output field element is statistical uniform as long
|
||||||
|
// as the field has a size less than 2^384.
|
||||||
pub(crate) fn get_and_append_challenge(
|
pub(crate) fn get_and_append_challenge(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: &'static [u8],
|
label: &'static [u8],
|
||||||
) -> Result<F, PolyIOPErrors> {
|
) -> Result<F, PolyIOPErrors> {
|
||||||
|
// we need to reject when transcript is empty
|
||||||
if self.is_empty {
|
if self.is_empty {
|
||||||
return Err(PolyIOPErrors::InvalidTranscript(
|
return Err(PolyIOPErrors::InvalidTranscript(
|
||||||
"transcript is empty".to_string(),
|
"transcript is empty".to_string(),
|
||||||
@@ -89,14 +102,23 @@ impl<F: PrimeField> IOPTranscript<F> {
|
|||||||
Ok(challenge)
|
Ok(challenge)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a list of challenges for the current transcript
|
// Generate a list of challenges from the current transcript
|
||||||
// and append it to the transcript
|
// and append them to the transcript.
|
||||||
|
//
|
||||||
|
// The output field element are statistical uniform as long
|
||||||
|
// as the field has a size less than 2^384.
|
||||||
pub(crate) fn get_and_append_challenge_vectors(
|
pub(crate) fn get_and_append_challenge_vectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: &'static [u8],
|
label: &'static [u8],
|
||||||
len: usize,
|
len: usize,
|
||||||
) -> Result<Vec<F>, PolyIOPErrors> {
|
) -> Result<Vec<F>, PolyIOPErrors> {
|
||||||
// we need to reject when transcript is empty
|
// we need to reject when transcript is empty
|
||||||
|
if self.is_empty {
|
||||||
|
return Err(PolyIOPErrors::InvalidTranscript(
|
||||||
|
"transcript is empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
for _ in 0..len {
|
for _ in 0..len {
|
||||||
res.push(self.get_and_append_challenge(label)?)
|
res.push(self.get_and_append_challenge(label)?)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use crate::{errors::PolyIOPErrors, structs::DomainInfo};
|
//! This module defines our main mathematical object `VirtualPolynomial`; and
|
||||||
|
//! various functions associated with it.
|
||||||
|
|
||||||
|
use crate::errors::PolyIOPErrors;
|
||||||
use ark_ff::PrimeField;
|
use ark_ff::PrimeField;
|
||||||
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
|
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
|
||||||
use ark_std::{
|
use ark_std::{
|
||||||
@@ -38,7 +41,7 @@ use std::{cmp::max, collections::HashMap, marker::PhantomData, ops::Add, rc::Rc}
|
|||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct VirtualPolynomial<F: PrimeField> {
|
pub struct VirtualPolynomial<F: PrimeField> {
|
||||||
/// Aux information about the multilinear polynomial
|
/// Aux information about the multilinear polynomial
|
||||||
pub domain_info: DomainInfo<F>,
|
pub aux_info: VPAuxInfo<F>,
|
||||||
/// list of reference to products (as usize) of multilinear extension
|
/// list of reference to products (as usize) of multilinear extension
|
||||||
pub products: Vec<(F, Vec<usize>)>,
|
pub products: Vec<(F, Vec<usize>)>,
|
||||||
/// Stores multilinear extensions in which product multiplicand can refer
|
/// Stores multilinear extensions in which product multiplicand can refer
|
||||||
@@ -48,6 +51,18 @@ pub struct VirtualPolynomial<F: PrimeField> {
|
|||||||
raw_pointers_lookup_table: HashMap<*const DenseMultilinearExtension<F>, usize>,
|
raw_pointers_lookup_table: HashMap<*const DenseMultilinearExtension<F>, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
/// Auxiliary information about the multilinear polynomial
|
||||||
|
pub struct VPAuxInfo<F: PrimeField> {
|
||||||
|
/// max number of multiplicands in each product
|
||||||
|
pub max_degree: usize,
|
||||||
|
/// number of variables of the polynomial
|
||||||
|
pub num_variables: usize,
|
||||||
|
/// Associated field
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub(crate) phantom: PhantomData<F>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<F: PrimeField> Add for &VirtualPolynomial<F> {
|
impl<F: PrimeField> Add for &VirtualPolynomial<F> {
|
||||||
type Output = VirtualPolynomial<F>;
|
type Output = VirtualPolynomial<F>;
|
||||||
fn add(self, other: &VirtualPolynomial<F>) -> Self::Output {
|
fn add(self, other: &VirtualPolynomial<F>) -> Self::Output {
|
||||||
@@ -69,10 +84,10 @@ impl<F: PrimeField> Add for &VirtualPolynomial<F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<F: PrimeField> VirtualPolynomial<F> {
|
impl<F: PrimeField> VirtualPolynomial<F> {
|
||||||
/// Returns an empty polynomial
|
/// Creates an empty virtual polynomial with `num_variables`.
|
||||||
pub fn new(num_variables: usize) -> Self {
|
pub fn new(num_variables: usize) -> Self {
|
||||||
VirtualPolynomial {
|
VirtualPolynomial {
|
||||||
domain_info: DomainInfo {
|
aux_info: VPAuxInfo {
|
||||||
max_degree: 0,
|
max_degree: 0,
|
||||||
num_variables,
|
num_variables,
|
||||||
phantom: PhantomData::default(),
|
phantom: PhantomData::default(),
|
||||||
@@ -83,27 +98,31 @@ impl<F: PrimeField> VirtualPolynomial<F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an new virtual polynomial from a MLE
|
/// Creates an new virtual polynomial from a MLE and its coefficient.
|
||||||
pub fn new_from_mle(mle: Rc<DenseMultilinearExtension<F>>, coefficient: F) -> Self {
|
pub fn new_from_mle(mle: Rc<DenseMultilinearExtension<F>>, coefficient: F) -> Self {
|
||||||
let mle_ptr: *const DenseMultilinearExtension<F> = Rc::as_ptr(&mle);
|
let mle_ptr: *const DenseMultilinearExtension<F> = Rc::as_ptr(&mle);
|
||||||
let mut hm = HashMap::new();
|
let mut hm = HashMap::new();
|
||||||
hm.insert(mle_ptr, 0);
|
hm.insert(mle_ptr, 0);
|
||||||
|
|
||||||
VirtualPolynomial {
|
VirtualPolynomial {
|
||||||
domain_info: DomainInfo {
|
aux_info: VPAuxInfo {
|
||||||
// The max degree is the max degree of any individual variable
|
// The max degree is the max degree of any individual variable
|
||||||
max_degree: 1,
|
max_degree: 1,
|
||||||
num_variables: mle.num_vars,
|
num_variables: mle.num_vars,
|
||||||
phantom: PhantomData::default(),
|
phantom: PhantomData::default(),
|
||||||
},
|
},
|
||||||
|
// here `0` points to the first polynomial of `flattened_ml_extensions`
|
||||||
products: vec![(coefficient, vec![0])],
|
products: vec![(coefficient, vec![0])],
|
||||||
flattened_ml_extensions: vec![mle],
|
flattened_ml_extensions: vec![mle],
|
||||||
raw_pointers_lookup_table: hm,
|
raw_pointers_lookup_table: hm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a list of multilinear extensions that is meant to be multiplied
|
/// Add a product of list of multilinear extensions to self
|
||||||
/// together. The resulting polynomial will be multiplied by the scalar
|
/// Returns an error if the list is empty, or the MLE has a different
|
||||||
|
/// `num_vars` from self.
|
||||||
|
///
|
||||||
|
/// The MLEs will be multiplied together, and then multiplied by the scalar
|
||||||
/// `coefficient`.
|
/// `coefficient`.
|
||||||
pub fn add_mle_list(
|
pub fn add_mle_list(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -112,13 +131,20 @@ impl<F: PrimeField> VirtualPolynomial<F> {
|
|||||||
) -> Result<(), PolyIOPErrors> {
|
) -> Result<(), PolyIOPErrors> {
|
||||||
let mle_list: Vec<Rc<DenseMultilinearExtension<F>>> = mle_list.into_iter().collect();
|
let mle_list: Vec<Rc<DenseMultilinearExtension<F>>> = mle_list.into_iter().collect();
|
||||||
let mut indexed_product = Vec::with_capacity(mle_list.len());
|
let mut indexed_product = Vec::with_capacity(mle_list.len());
|
||||||
assert!(!mle_list.is_empty());
|
|
||||||
self.domain_info.max_degree = max(self.domain_info.max_degree, mle_list.len());
|
if mle_list.is_empty() {
|
||||||
|
return Err(PolyIOPErrors::InvalidParameters(
|
||||||
|
"input mle_list is empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.aux_info.max_degree = max(self.aux_info.max_degree, mle_list.len());
|
||||||
|
|
||||||
for mle in mle_list {
|
for mle in mle_list {
|
||||||
if mle.num_vars != self.domain_info.num_variables {
|
if mle.num_vars != self.aux_info.num_variables {
|
||||||
return Err(PolyIOPErrors::InvalidParameters(format!(
|
return Err(PolyIOPErrors::InvalidParameters(format!(
|
||||||
"product has a multiplicand with wrong number of variables {} vs {}",
|
"product has a multiplicand with wrong number of variables {} vs {}",
|
||||||
mle.num_vars, self.domain_info.num_variables
|
mle.num_vars, self.aux_info.num_variables
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,16 +163,26 @@ impl<F: PrimeField> VirtualPolynomial<F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Multiple the current VirtualPolynomial by an MLE:
|
/// Multiple the current VirtualPolynomial by an MLE:
|
||||||
/// - add the MLE to the MLE list
|
/// - add the MLE to the MLE list;
|
||||||
/// - multiple each product by MLE and its coefficient
|
/// - multiple each product by MLE and its coefficient.
|
||||||
|
/// Returns an error if the MLE has a different `num_vars` from self.
|
||||||
pub fn mul_by_mle(
|
pub fn mul_by_mle(
|
||||||
&mut self,
|
&mut self,
|
||||||
mle: Rc<DenseMultilinearExtension<F>>,
|
mle: Rc<DenseMultilinearExtension<F>>,
|
||||||
coefficient: F,
|
coefficient: F,
|
||||||
) -> Result<(), PolyIOPErrors> {
|
) -> Result<(), PolyIOPErrors> {
|
||||||
let start = start_timer!(|| "mul by mle");
|
let start = start_timer!(|| "mul by mle");
|
||||||
|
|
||||||
|
if mle.num_vars != self.aux_info.num_variables {
|
||||||
|
return Err(PolyIOPErrors::InvalidParameters(format!(
|
||||||
|
"product has a multiplicand with wrong number of variables {} vs {}",
|
||||||
|
mle.num_vars, self.aux_info.num_variables
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
let mle_ptr: *const DenseMultilinearExtension<F> = Rc::as_ptr(&mle);
|
let mle_ptr: *const DenseMultilinearExtension<F> = Rc::as_ptr(&mle);
|
||||||
|
|
||||||
|
// check if this mle already exists in the virtual polynomial
|
||||||
let mle_index = match self.raw_pointers_lookup_table.get(&mle_ptr) {
|
let mle_index = match self.raw_pointers_lookup_table.get(&mle_ptr) {
|
||||||
Some(&p) => p,
|
Some(&p) => p,
|
||||||
None => {
|
None => {
|
||||||
@@ -158,22 +194,27 @@ impl<F: PrimeField> VirtualPolynomial<F> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (prod_coef, indices) in self.products.iter_mut() {
|
for (prod_coef, indices) in self.products.iter_mut() {
|
||||||
|
// - add the MLE to the MLE list;
|
||||||
|
// - multiple each product by MLE and its coefficient.
|
||||||
indices.push(mle_index);
|
indices.push(mle_index);
|
||||||
*prod_coef *= coefficient;
|
*prod_coef *= coefficient;
|
||||||
}
|
}
|
||||||
self.domain_info.max_degree += 1;
|
|
||||||
|
// increase the max degree by one as the MLE has degree 1.
|
||||||
|
self.aux_info.max_degree += 1;
|
||||||
end_timer!(start);
|
end_timer!(start);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate the polynomial at point `point`
|
/// Evaluate the virtual polynomial at point `point`.
|
||||||
|
/// Returns an error is point.len() does not match `num_variables`.
|
||||||
pub fn evaluate(&self, point: &[F]) -> Result<F, PolyIOPErrors> {
|
pub fn evaluate(&self, point: &[F]) -> Result<F, PolyIOPErrors> {
|
||||||
let start = start_timer!(|| "evaluation");
|
let start = start_timer!(|| "evaluation");
|
||||||
|
|
||||||
if self.domain_info.num_variables != point.len() {
|
if self.aux_info.num_variables != point.len() {
|
||||||
return Err(PolyIOPErrors::InvalidParameters(format!(
|
return Err(PolyIOPErrors::InvalidParameters(format!(
|
||||||
"wrong number of variables {} vs {}",
|
"wrong number of variables {} vs {}",
|
||||||
self.domain_info.num_variables,
|
self.aux_info.num_variables,
|
||||||
point.len()
|
point.len()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@@ -222,8 +263,8 @@ impl<F: PrimeField> VirtualPolynomial<F> {
|
|||||||
Ok((poly, sum))
|
Ok((poly, sum))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sample a random virtual polynomial that evaluates to zero everywhere on
|
/// Sample a random virtual polynomial that evaluates to zero everywhere
|
||||||
/// the boolean hypercube.
|
/// over the boolean hypercube.
|
||||||
pub fn rand_zero<R: RngCore>(
|
pub fn rand_zero<R: RngCore>(
|
||||||
nv: usize,
|
nv: usize,
|
||||||
num_multiplicands_range: (usize, usize),
|
num_multiplicands_range: (usize, usize),
|
||||||
@@ -236,11 +277,36 @@ impl<F: PrimeField> VirtualPolynomial<F> {
|
|||||||
rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1);
|
rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1);
|
||||||
let product = random_zero_mle_list(nv, num_multiplicands, rng);
|
let product = random_zero_mle_list(nv, num_multiplicands, rng);
|
||||||
let coefficient = F::rand(rng);
|
let coefficient = F::rand(rng);
|
||||||
poly.add_mle_list(product.into_iter(), coefficient).unwrap();
|
poly.add_mle_list(product.into_iter(), coefficient)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(poly)
|
Ok(poly)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Input poly f(x) and a random vector r, output
|
||||||
|
// \hat f(x) = \sum_{x_i \in eval_x} f(x_i) eq(x, r)
|
||||||
|
// where
|
||||||
|
// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
|
||||||
|
//
|
||||||
|
// This function is used in ZeroCheck.
|
||||||
|
pub(crate) fn build_f_hat(&self, r: &[F]) -> Result<Self, PolyIOPErrors> {
|
||||||
|
let start = start_timer!(|| "zero check build hat f");
|
||||||
|
|
||||||
|
if self.aux_info.num_variables != r.len() {
|
||||||
|
return Err(PolyIOPErrors::InvalidParameters(format!(
|
||||||
|
"r.len() is different from number of variables: {} vs {}",
|
||||||
|
r.len(),
|
||||||
|
self.aux_info.num_variables
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let eq_x_r = build_eq_x_r(r)?;
|
||||||
|
let mut res = self.clone();
|
||||||
|
res.mul_by_mle(eq_x_r, F::one())?;
|
||||||
|
|
||||||
|
end_timer!(start);
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sample a random list of multilinear polynomials.
|
/// Sample a random list of multilinear polynomials.
|
||||||
@@ -307,6 +373,68 @@ pub fn random_zero_mle_list<F: PrimeField, R: RngCore>(
|
|||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function build the eq(x, r) polynomial for any given r.
|
||||||
|
//
|
||||||
|
// Evaluate
|
||||||
|
// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
|
||||||
|
// over r, which is
|
||||||
|
// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i))
|
||||||
|
fn build_eq_x_r<F: PrimeField>(r: &[F]) -> Result<Rc<DenseMultilinearExtension<F>>, PolyIOPErrors> {
|
||||||
|
let start = start_timer!(|| "zero check build eq_x_r");
|
||||||
|
|
||||||
|
// we build eq(x,r) from its evaluations
|
||||||
|
// we want to evaluate eq(x,r) over x \in {0, 1}^num_vars
|
||||||
|
// for example, with num_vars = 4, x is a binary vector of 4, then
|
||||||
|
// 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3)
|
||||||
|
// 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3)
|
||||||
|
// 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3)
|
||||||
|
// 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3)
|
||||||
|
// ....
|
||||||
|
// 1 1 1 1 -> r0 * r1 * r2 * r3
|
||||||
|
// we will need 2^num_var evaluations
|
||||||
|
|
||||||
|
let mut eval = Vec::new();
|
||||||
|
build_eq_x_r_helper(r, &mut eval)?;
|
||||||
|
|
||||||
|
let mle = DenseMultilinearExtension::from_evaluations_vec(r.len(), eval);
|
||||||
|
|
||||||
|
let res = Rc::new(mle);
|
||||||
|
end_timer!(start);
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper function to build eq(x, r) recursively.
|
||||||
|
/// This function takes `r.len()` steps, and for each step it requires a maximum
|
||||||
|
/// `r.len()-1` multiplications.
|
||||||
|
fn build_eq_x_r_helper<F: PrimeField>(r: &[F], buf: &mut Vec<F>) -> Result<(), PolyIOPErrors> {
|
||||||
|
if r.is_empty() {
|
||||||
|
return Err(PolyIOPErrors::InvalidParameters(
|
||||||
|
"r length is 0".to_string(),
|
||||||
|
));
|
||||||
|
} else if r.len() == 1 {
|
||||||
|
// initializing the buffer with [1-r_0, r_0]
|
||||||
|
buf.push(F::one() - r[0]);
|
||||||
|
buf.push(r[0]);
|
||||||
|
} else {
|
||||||
|
build_eq_x_r_helper(&r[1..], buf)?;
|
||||||
|
|
||||||
|
// suppose at the previous step we received [b_1, ..., b_k]
|
||||||
|
// for the current step we will need
|
||||||
|
// if x_0 = 0: (1-r0) * [b_1, ..., b_k]
|
||||||
|
// if x_0 = 1: r0 * [b_1, ..., b_k]
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
for &b_i in buf.iter() {
|
||||||
|
let tmp = r[0] * b_i;
|
||||||
|
res.push(b_i - tmp);
|
||||||
|
res.push(tmp);
|
||||||
|
}
|
||||||
|
*buf = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -364,4 +492,70 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_eq_xr() {
|
||||||
|
let mut rng = test_rng();
|
||||||
|
for nv in 4..10 {
|
||||||
|
let r: Vec<Fr> = (0..nv).map(|_| Fr::rand(&mut rng)).collect();
|
||||||
|
let eq_x_r = build_eq_x_r(r.as_ref()).unwrap();
|
||||||
|
let eq_x_r2 = build_eq_x_r_for_test(r.as_ref());
|
||||||
|
assert_eq!(eq_x_r, eq_x_r2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Naive method to build eq(x, r).
|
||||||
|
/// Only used for testing purpose.
|
||||||
|
// Evaluate
|
||||||
|
// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
|
||||||
|
// over r, which is
|
||||||
|
// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i))
|
||||||
|
fn build_eq_x_r_for_test<F: PrimeField>(r: &[F]) -> Rc<DenseMultilinearExtension<F>> {
|
||||||
|
let start = start_timer!(|| "zero check naive build eq_x_r");
|
||||||
|
|
||||||
|
// we build eq(x,r) from its evaluations
|
||||||
|
// we want to evaluate eq(x,r) over x \in {0, 1}^num_vars
|
||||||
|
// for example, with num_vars = 4, x is a binary vector of 4, then
|
||||||
|
// 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3)
|
||||||
|
// 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3)
|
||||||
|
// 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3)
|
||||||
|
// 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3)
|
||||||
|
// ....
|
||||||
|
// 1 1 1 1 -> r0 * r1 * r2 * r3
|
||||||
|
// we will need 2^num_var evaluations
|
||||||
|
|
||||||
|
// First, we build array for {1 - r_i}
|
||||||
|
let one_minus_r: Vec<F> = r.iter().map(|ri| F::one() - ri).collect();
|
||||||
|
|
||||||
|
let num_var = r.len();
|
||||||
|
let mut eval = vec![];
|
||||||
|
|
||||||
|
for i in 0..1 << num_var {
|
||||||
|
let mut current_eval = F::one();
|
||||||
|
let bit_sequence = bit_decompose(i, num_var);
|
||||||
|
|
||||||
|
for (&bit, (ri, one_minus_ri)) in
|
||||||
|
bit_sequence.iter().zip(r.iter().zip(one_minus_r.iter()))
|
||||||
|
{
|
||||||
|
current_eval *= if bit { *ri } else { *one_minus_ri };
|
||||||
|
}
|
||||||
|
eval.push(current_eval);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mle = DenseMultilinearExtension::from_evaluations_vec(num_var, eval);
|
||||||
|
|
||||||
|
let res = Rc::new(mle);
|
||||||
|
end_timer!(start);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bit_decompose(input: u64, num_var: usize) -> Vec<bool> {
|
||||||
|
let mut res = Vec::with_capacity(num_var);
|
||||||
|
let mut i = input;
|
||||||
|
for _ in 0..num_var {
|
||||||
|
res.push(i & 1 == 1);
|
||||||
|
i >>= 1;
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
use ark_ff::PrimeField;
|
//! Main module for the ZeroCheck protocol.
|
||||||
use ark_poly::DenseMultilinearExtension;
|
|
||||||
use ark_std::{end_timer, start_timer};
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::PolyIOPErrors,
|
errors::PolyIOPErrors,
|
||||||
structs::{DomainInfo, IOPProof, SubClaim},
|
structs::{IOPProof, SubClaim},
|
||||||
sum_check::SumCheck,
|
sum_check::SumCheck,
|
||||||
transcript::IOPTranscript,
|
transcript::IOPTranscript,
|
||||||
virtual_poly::VirtualPolynomial,
|
virtual_poly::{VPAuxInfo, VirtualPolynomial},
|
||||||
PolyIOP,
|
PolyIOP,
|
||||||
};
|
};
|
||||||
|
use ark_ff::PrimeField;
|
||||||
|
use ark_std::{end_timer, start_timer};
|
||||||
|
|
||||||
pub trait ZeroCheck<F: PrimeField> {
|
pub trait ZeroCheck<F: PrimeField> {
|
||||||
type Proof;
|
type Proof;
|
||||||
type PolyList;
|
type VirtualPolynomial;
|
||||||
type DomainInfo;
|
type VPAuxInfo;
|
||||||
type SubClaim;
|
type SubClaim;
|
||||||
type Transcript;
|
type Transcript;
|
||||||
|
|
||||||
@@ -30,22 +29,22 @@ pub trait ZeroCheck<F: PrimeField> {
|
|||||||
/// initialize the prover to argue for the sum of polynomial over
|
/// initialize the prover to argue for the sum of polynomial over
|
||||||
/// {0,1}^`num_vars` is zero.
|
/// {0,1}^`num_vars` is zero.
|
||||||
fn prove(
|
fn prove(
|
||||||
poly: &Self::PolyList,
|
poly: &Self::VirtualPolynomial,
|
||||||
transcript: &mut Self::Transcript,
|
transcript: &mut Self::Transcript,
|
||||||
) -> Result<Self::Proof, PolyIOPErrors>;
|
) -> Result<Self::Proof, PolyIOPErrors>;
|
||||||
|
|
||||||
/// verify the claimed sum using the proof
|
/// verify the claimed sum using the proof
|
||||||
fn verify(
|
fn verify(
|
||||||
proof: &Self::Proof,
|
proof: &Self::Proof,
|
||||||
domain_info: &Self::DomainInfo,
|
aux_info: &Self::VPAuxInfo,
|
||||||
transcript: &mut Self::Transcript,
|
transcript: &mut Self::Transcript,
|
||||||
) -> Result<Self::SubClaim, PolyIOPErrors>;
|
) -> Result<Self::SubClaim, PolyIOPErrors>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: PrimeField> ZeroCheck<F> for PolyIOP<F> {
|
impl<F: PrimeField> ZeroCheck<F> for PolyIOP<F> {
|
||||||
type Proof = IOPProof<F>;
|
type Proof = IOPProof<F>;
|
||||||
type PolyList = VirtualPolynomial<F>;
|
type VirtualPolynomial = VirtualPolynomial<F>;
|
||||||
type DomainInfo = DomainInfo<F>;
|
type VPAuxInfo = VPAuxInfo<F>;
|
||||||
|
|
||||||
/// A ZeroCheck SubClaim consists of
|
/// A ZeroCheck SubClaim consists of
|
||||||
/// - the SubClaim from the ZeroCheck
|
/// - the SubClaim from the ZeroCheck
|
||||||
@@ -63,29 +62,41 @@ impl<F: PrimeField> ZeroCheck<F> for PolyIOP<F> {
|
|||||||
IOPTranscript::<F>::new(b"Initializing ZeroCheck transcript")
|
IOPTranscript::<F>::new(b"Initializing ZeroCheck transcript")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// initialize the prover to argue for the sum of polynomial over
|
/// Initialize the prover to argue for the sum of polynomial f(x) over
|
||||||
/// {0,1}^`num_vars` is zero.
|
/// {0,1}^`num_vars` is zero.
|
||||||
|
///
|
||||||
|
/// f(x) is zero if \hat f(x) := f(x) * eq(x,r) is also a zero polynomial
|
||||||
|
/// for a random r sampled from transcript.
|
||||||
|
///
|
||||||
|
/// This function will build the \hat f(x) and then invoke the sumcheck
|
||||||
|
/// protocol to generate a proof for which the sum of \hat f(x) is zero
|
||||||
fn prove(
|
fn prove(
|
||||||
poly: &Self::PolyList,
|
poly: &Self::VirtualPolynomial,
|
||||||
transcript: &mut Self::Transcript,
|
transcript: &mut Self::Transcript,
|
||||||
) -> Result<Self::Proof, PolyIOPErrors> {
|
) -> Result<Self::Proof, PolyIOPErrors> {
|
||||||
let start = start_timer!(|| "zero check prove");
|
let start = start_timer!(|| "zero check prove");
|
||||||
|
|
||||||
let length = poly.domain_info.num_variables;
|
let length = poly.aux_info.num_variables;
|
||||||
let r = transcript.get_and_append_challenge_vectors(b"vector r", length)?;
|
let r = transcript.get_and_append_challenge_vectors(b"vector r", length)?;
|
||||||
let f_hat = build_f_hat(poly, r.as_ref())?;
|
let f_hat = poly.build_f_hat(r.as_ref())?;
|
||||||
let res = <Self as SumCheck<F>>::prove(&f_hat, transcript);
|
let res = <Self as SumCheck<F>>::prove(&f_hat, transcript);
|
||||||
|
|
||||||
end_timer!(start);
|
end_timer!(start);
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the claimed sum using the proof.
|
/// Verify that the polynomial's sum is zero using the proof.
|
||||||
/// the initial challenge `r` is also returned.
|
/// Return a Self::Subclaim that consists of the
|
||||||
/// The caller needs to makes sure that `\hat f = f * eq(x, r)`
|
///
|
||||||
|
/// - a Subclaim that the sum is zero
|
||||||
|
/// - the initial challenge `r` that is used to build `eq(x, r)`
|
||||||
|
///
|
||||||
|
/// This function will check that \hat f(x)'s sum is zero. It does not check
|
||||||
|
/// `\hat f(x)` is build correctly. The caller needs to makes sure that
|
||||||
|
/// `\hat f(x) = f(x) * eq(x, r)`
|
||||||
fn verify(
|
fn verify(
|
||||||
proof: &Self::Proof,
|
proof: &Self::Proof,
|
||||||
fx_domain_info: &Self::DomainInfo,
|
fx_aux_info: &Self::VPAuxInfo,
|
||||||
transcript: &mut Self::Transcript,
|
transcript: &mut Self::Transcript,
|
||||||
) -> Result<Self::SubClaim, PolyIOPErrors> {
|
) -> Result<Self::SubClaim, PolyIOPErrors> {
|
||||||
let start = start_timer!(|| "zero check verify");
|
let start = start_timer!(|| "zero check verify");
|
||||||
@@ -99,112 +110,27 @@ impl<F: PrimeField> ZeroCheck<F> for PolyIOP<F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate `r` and pass it to the caller for correctness check
|
// generate `r` and pass it to the caller for correctness check
|
||||||
let length = fx_domain_info.num_variables;
|
let length = fx_aux_info.num_variables;
|
||||||
let r = transcript.get_and_append_challenge_vectors(b"vector r", length)?;
|
let r = transcript.get_and_append_challenge_vectors(b"vector r", length)?;
|
||||||
|
|
||||||
// hat_fx's max degree is increased by eq(x, r).degree() which is 1
|
// hat_fx's max degree is increased by eq(x, r).degree() which is 1
|
||||||
let mut hat_fx_domain_info = fx_domain_info.clone();
|
let mut hat_fx_aux_info = fx_aux_info.clone();
|
||||||
hat_fx_domain_info.max_degree += 1;
|
hat_fx_aux_info.max_degree += 1;
|
||||||
let subclaim =
|
let subclaim =
|
||||||
<Self as SumCheck<F>>::verify(F::zero(), proof, &hat_fx_domain_info, transcript)?;
|
<Self as SumCheck<F>>::verify(F::zero(), proof, &hat_fx_aux_info, transcript)?;
|
||||||
|
|
||||||
end_timer!(start);
|
end_timer!(start);
|
||||||
Ok((subclaim, r))
|
Ok((subclaim, r))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input poly f(x) and a random vector r, output
|
|
||||||
// \hat f(x) = \sum_{x_i \in eval_x} f(x_i) eq(x, r)
|
|
||||||
// where
|
|
||||||
// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
|
|
||||||
fn build_f_hat<F: PrimeField>(
|
|
||||||
poly: &VirtualPolynomial<F>,
|
|
||||||
r: &[F],
|
|
||||||
) -> Result<VirtualPolynomial<F>, PolyIOPErrors> {
|
|
||||||
let start = start_timer!(|| "zero check build hat f");
|
|
||||||
|
|
||||||
assert_eq!(poly.domain_info.num_variables, r.len());
|
|
||||||
|
|
||||||
let eq_x_r = build_eq_x_r(r)?;
|
|
||||||
let mut res = poly.clone();
|
|
||||||
res.mul_by_mle(eq_x_r, F::one())?;
|
|
||||||
|
|
||||||
end_timer!(start);
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate
|
|
||||||
// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
|
|
||||||
// over r, which is
|
|
||||||
// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i))
|
|
||||||
pub fn build_eq_x_r<F: PrimeField>(
|
|
||||||
r: &[F],
|
|
||||||
) -> Result<Rc<DenseMultilinearExtension<F>>, PolyIOPErrors> {
|
|
||||||
let start = start_timer!(|| "zero check build eq_x_r");
|
|
||||||
|
|
||||||
// we build eq(x,r) from its evaluations
|
|
||||||
// we want to evaluate eq(x,r) over x \in {0, 1}^num_vars
|
|
||||||
// for example, with num_vars = 4, x is a binary vector of 4, then
|
|
||||||
// 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3)
|
|
||||||
// 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3)
|
|
||||||
// 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3)
|
|
||||||
// 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3)
|
|
||||||
// ....
|
|
||||||
// 1 1 1 1 -> r0 * r1 * r2 * r3
|
|
||||||
// we will need 2^num_var evaluations
|
|
||||||
|
|
||||||
let mut eval = Vec::new();
|
|
||||||
build_eq_x_r_helper(r, &mut eval)?;
|
|
||||||
|
|
||||||
let mle = DenseMultilinearExtension::from_evaluations_vec(r.len(), eval);
|
|
||||||
|
|
||||||
let res = Rc::new(mle);
|
|
||||||
end_timer!(start);
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A helper function to build eq(x, r) recursively.
|
|
||||||
/// This function takes `r.len()` steps, and for each step it requires a maximum
|
|
||||||
/// `r.len()-1` multiplications.
|
|
||||||
fn build_eq_x_r_helper<F: PrimeField>(r: &[F], buf: &mut Vec<F>) -> Result<(), PolyIOPErrors> {
|
|
||||||
if r.is_empty() {
|
|
||||||
return Err(PolyIOPErrors::InvalidParameters(
|
|
||||||
"r length is 0".to_string(),
|
|
||||||
));
|
|
||||||
} else if r.len() == 1 {
|
|
||||||
// initializing the buffer with [1-r0, r0]
|
|
||||||
buf.push(F::one() - r[0]);
|
|
||||||
buf.push(r[0]);
|
|
||||||
} else {
|
|
||||||
build_eq_x_r_helper(&r[1..], buf)?;
|
|
||||||
|
|
||||||
// suppose in the previous step we have [b1, ..., b_k]
|
|
||||||
// for the current step we will need
|
|
||||||
// if x0 = 0: (1-r0) * [b_1, ..., b_k]
|
|
||||||
// if x0 = 1: r0 * [b1, ..., b_k]
|
|
||||||
|
|
||||||
let mut res = vec![];
|
|
||||||
for &e in buf.iter() {
|
|
||||||
let tmp = e * r[0];
|
|
||||||
res.push(e - tmp);
|
|
||||||
res.push(tmp);
|
|
||||||
}
|
|
||||||
*buf = res;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
use super::{build_eq_x_r, ZeroCheck};
|
use super::ZeroCheck;
|
||||||
use crate::{errors::PolyIOPErrors, PolyIOP, VirtualPolynomial};
|
use crate::{errors::PolyIOPErrors, PolyIOP, VirtualPolynomial};
|
||||||
use ark_bls12_381::Fr;
|
use ark_bls12_381::Fr;
|
||||||
use ark_ff::{PrimeField, UniformRand};
|
use ark_std::test_rng;
|
||||||
use ark_poly::DenseMultilinearExtension;
|
|
||||||
use ark_std::{end_timer, start_timer, test_rng};
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
fn test_zerocheck(
|
fn test_zerocheck(
|
||||||
nv: usize,
|
nv: usize,
|
||||||
@@ -222,7 +148,7 @@ mod test {
|
|||||||
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
||||||
let proof = <PolyIOP<Fr> as ZeroCheck<Fr>>::prove(&poly, &mut transcript)?;
|
let proof = <PolyIOP<Fr> as ZeroCheck<Fr>>::prove(&poly, &mut transcript)?;
|
||||||
|
|
||||||
let poly_info = poly.domain_info.clone();
|
let poly_info = poly.aux_info.clone();
|
||||||
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
|
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
|
||||||
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
||||||
let subclaim =
|
let subclaim =
|
||||||
@@ -242,7 +168,7 @@ mod test {
|
|||||||
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
||||||
let proof = <PolyIOP<Fr> as ZeroCheck<Fr>>::prove(&poly, &mut transcript)?;
|
let proof = <PolyIOP<Fr> as ZeroCheck<Fr>>::prove(&poly, &mut transcript)?;
|
||||||
|
|
||||||
let poly_info = poly.domain_info.clone();
|
let poly_info = poly.aux_info.clone();
|
||||||
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
|
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
|
||||||
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
||||||
|
|
||||||
@@ -281,70 +207,4 @@ mod test {
|
|||||||
assert!(test_zerocheck(nv, num_multiplicands_range, num_products).is_err());
|
assert!(test_zerocheck(nv, num_multiplicands_range, num_products).is_err());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_eq_xr() {
|
|
||||||
let mut rng = test_rng();
|
|
||||||
for nv in 4..10 {
|
|
||||||
let r: Vec<Fr> = (0..nv).map(|_| Fr::rand(&mut rng)).collect();
|
|
||||||
let eq_x_r = build_eq_x_r(r.as_ref()).unwrap();
|
|
||||||
let eq_x_r2 = build_eq_x_r_for_test(r.as_ref());
|
|
||||||
assert_eq!(eq_x_r, eq_x_r2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Naive method to build eq(x, r).
|
|
||||||
/// Only used for testing purpose.
|
|
||||||
// Evaluate
|
|
||||||
// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
|
|
||||||
// over r, which is
|
|
||||||
// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i))
|
|
||||||
fn build_eq_x_r_for_test<F: PrimeField>(r: &[F]) -> Rc<DenseMultilinearExtension<F>> {
|
|
||||||
let start = start_timer!(|| "zero check naive build eq_x_r");
|
|
||||||
|
|
||||||
// we build eq(x,r) from its evaluations
|
|
||||||
// we want to evaluate eq(x,r) over x \in {0, 1}^num_vars
|
|
||||||
// for example, with num_vars = 4, x is a binary vector of 4, then
|
|
||||||
// 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3)
|
|
||||||
// 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3)
|
|
||||||
// 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3)
|
|
||||||
// 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3)
|
|
||||||
// ....
|
|
||||||
// 1 1 1 1 -> r0 * r1 * r2 * r3
|
|
||||||
// we will need 2^num_var evaluations
|
|
||||||
|
|
||||||
// First, we build array for {1 - r_i}
|
|
||||||
let one_minus_r: Vec<F> = r.iter().map(|ri| F::one() - ri).collect();
|
|
||||||
|
|
||||||
let num_var = r.len();
|
|
||||||
let mut eval = vec![];
|
|
||||||
|
|
||||||
for i in 0..1 << num_var {
|
|
||||||
let mut current_eval = F::one();
|
|
||||||
let bit_sequence = bit_decompose(i, num_var);
|
|
||||||
|
|
||||||
for (&bit, (ri, one_minus_ri)) in
|
|
||||||
bit_sequence.iter().zip(r.iter().zip(one_minus_r.iter()))
|
|
||||||
{
|
|
||||||
current_eval *= if bit { *ri } else { *one_minus_ri };
|
|
||||||
}
|
|
||||||
eval.push(current_eval);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mle = DenseMultilinearExtension::from_evaluations_vec(num_var, eval);
|
|
||||||
|
|
||||||
let res = Rc::new(mle);
|
|
||||||
end_timer!(start);
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bit_decompose(input: u64, num_var: usize) -> Vec<bool> {
|
|
||||||
let mut res = Vec::with_capacity(num_var);
|
|
||||||
let mut i = input;
|
|
||||||
for _ in 0..num_var {
|
|
||||||
res.push(i & 1 == 1);
|
|
||||||
i >>= 1;
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user