@ -1,4 +1,6 @@ |
|||||
[workspace] |
[workspace] |
||||
members = [ |
members = [ |
||||
"hyperplonk" |
|
||||
|
"hyperplonk", |
||||
|
"pcs", |
||||
|
"poly-iop" |
||||
] |
] |
@ -0,0 +1,8 @@ |
|||||
|
[package] |
||||
|
name = "pcs" |
||||
|
version = "0.1.0" |
||||
|
edition = "2021" |
||||
|
|
||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
|
||||
|
[dependencies] |
@ -0,0 +1,3 @@ |
|||||
|
KZG based multilinear polynomial commitment |
||||
|
----- |
||||
|
|
@ -0,0 +1,8 @@ |
|||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
#[test]
|
||||
|
fn it_works() {
|
||||
|
let result = 2 + 2;
|
||||
|
assert_eq!(result, 4);
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,32 @@ |
|||||
|
[package] |
||||
|
name = "poly-iop" |
||||
|
version = "0.1.0" |
||||
|
edition = "2021" |
||||
|
|
||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
|
||||
|
[dependencies] |
||||
|
|
||||
|
ark-ff = { version = "^0.3.0", default-features = false } |
||||
|
ark-std = { version = "^0.3.0", default-features = false } |
||||
|
ark-poly = { version = "^0.3.0", default-features = false } |
||||
|
ark-serialize = { version = "^0.3.0", default-features = false } |
||||
|
ark-bls12-381 = { version = "0.3.0", default-features = false, features = [ "curve" ] } |
||||
|
|
||||
|
rand_chacha = { version = "0.3.0", default-features = false } |
||||
|
merlin = { version = "3.0.0", default-features = false } |
||||
|
displaydoc = { version = "0.2.3", default-features = false } |
||||
|
|
||||
|
rayon = { version = "1.5.2", default-features = false, optional = true } |
||||
|
|
||||
|
[features] |
||||
|
default = [ "parallel" ] |
||||
|
parallel = [ |
||||
|
"rayon", |
||||
|
"ark-std/parallel", |
||||
|
"ark-ff/parallel", |
||||
|
"ark-poly/parallel" |
||||
|
] |
||||
|
print-trace = [ |
||||
|
"ark-std/print-trace" |
||||
|
] |
@ -0,0 +1,7 @@ |
|||||
|
Poly IOP |
||||
|
----- |
||||
|
|
||||
|
Implements the following protocols |
||||
|
|
||||
|
- [ ] sum checks |
||||
|
- [ ] zero checks |
@ -0,0 +1,27 @@ |
|||||
|
//! Error module.
|
||||
|
|
||||
|
use ark_std::string::String;
|
||||
|
use displaydoc::Display;
|
||||
|
|
||||
|
/// A `enum` specifying the possible failure modes of the PolyIOP.
|
||||
|
#[derive(Display, Debug)]
|
||||
|
pub enum PolyIOPErrors {
|
||||
|
/// Invalid Prover
|
||||
|
InvalidProver(String),
|
||||
|
/// Invalid Verifier
|
||||
|
InvalidVerifier(String),
|
||||
|
/// Invalid Proof
|
||||
|
InvalidProof(String),
|
||||
|
/// Invalid parameters
|
||||
|
InvalidParameters(String),
|
||||
|
/// Invalid Transcript
|
||||
|
InvalidTranscript(String),
|
||||
|
/// An error during (de)serialization
|
||||
|
SerializationError(ark_serialize::SerializationError),
|
||||
|
}
|
||||
|
|
||||
|
impl From<ark_serialize::SerializationError> for PolyIOPErrors {
|
||||
|
fn from(e: ark_serialize::SerializationError) -> Self {
|
||||
|
Self::SerializationError(e)
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,23 @@ |
|||||
|
#![allow(dead_code)]
|
||||
|
|
||||
|
use std::marker::PhantomData;
|
||||
|
|
||||
|
use ark_ff::PrimeField;
|
||||
|
|
||||
|
mod errors;
|
||||
|
mod structs;
|
||||
|
mod sum_check;
|
||||
|
mod transcript;
|
||||
|
mod utils;
|
||||
|
mod virtual_poly;
|
||||
|
// mod zero_check;
|
||||
|
|
||||
|
pub use virtual_poly::VirtualPolynomial;
|
||||
|
|
||||
|
/// Struct for PolyIOP protocol.
|
||||
|
/// It is instantiated with
|
||||
|
/// - SumCheck protocol.
|
||||
|
/// - ZeroCheck protocol. (WIP)
|
||||
|
pub struct PolyIOP<F: PrimeField> {
|
||||
|
phantom: PhantomData<F>,
|
||||
|
}
|
@ -0,0 +1,40 @@ |
|||||
|
//! Structs for polynomials and extensions.
|
||||
|
|
||||
|
use ark_ff::PrimeField;
|
||||
|
use std::marker::PhantomData;
|
||||
|
|
||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
|
/// Auxiliary information about the multilinear polynomial
|
||||
|
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> {
|
||||
|
/// the multi-dimensional point that this multilinear extension is evaluated
|
||||
|
/// to
|
||||
|
pub point: Vec<F>,
|
||||
|
/// the expected evaluation
|
||||
|
pub expected_evaluation: F,
|
||||
|
}
|
||||
|
|
||||
|
/// An IOP proof is a list of messages from prover to verifier
|
||||
|
/// through the interactive protocol.
|
||||
|
/// It is a shared struct for both sumcheck and zerocheck protocols.
|
||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
|
pub struct IOPProof<F: PrimeField> {
|
||||
|
pub proofs: Vec<IOPProverMessage<F>>,
|
||||
|
}
|
||||
|
|
||||
|
/// A message from the prover to the verifier at a given round
|
||||
|
/// is a list of evaluations.
|
||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
|
pub struct IOPProverMessage<F: PrimeField> {
|
||||
|
pub(crate) evaluations: Vec<F>,
|
||||
|
}
|
@ -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"
|
||||
|
);
|
||||
|
}
|
||||
|
}
|
@ -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,
|
||||
|
})
|
||||
|
}
|
||||
|
}
|
@ -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
|
||||
|
}
|
@ -0,0 +1,106 @@ |
|||||
|
use std::marker::PhantomData;
|
||||
|
|
||||
|
use ark_ff::PrimeField;
|
||||
|
use merlin::Transcript;
|
||||
|
|
||||
|
use crate::{
|
||||
|
errors::PolyIOPErrors,
|
||||
|
structs::{DomainInfo, IOPProverMessage},
|
||||
|
to_bytes,
|
||||
|
};
|
||||
|
|
||||
|
pub struct IOPTranscript<F: PrimeField> {
|
||||
|
transcript: Transcript,
|
||||
|
is_empty: bool,
|
||||
|
#[doc(hidden)]
|
||||
|
phantom: PhantomData<F>,
|
||||
|
}
|
||||
|
|
||||
|
impl<F: PrimeField> IOPTranscript<F> {
|
||||
|
/// create a new IOP transcript
|
||||
|
pub(crate) fn new(label: &'static [u8]) -> Self {
|
||||
|
Self {
|
||||
|
transcript: Transcript::new(label),
|
||||
|
is_empty: true,
|
||||
|
phantom: PhantomData::default(),
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
// append the message to the transcript
|
||||
|
pub(crate) fn append_message(
|
||||
|
&mut self,
|
||||
|
label: &'static [u8],
|
||||
|
msg: &[u8],
|
||||
|
) -> Result<(), PolyIOPErrors> {
|
||||
|
self.transcript.append_message(label, msg);
|
||||
|
self.is_empty = false;
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
|
||||
|
pub(crate) fn append_domain_info(
|
||||
|
&mut self,
|
||||
|
domain_info: &DomainInfo<F>,
|
||||
|
) -> Result<(), PolyIOPErrors> {
|
||||
|
let message = format!(
|
||||
|
"max_mul {} num_var {}",
|
||||
|
domain_info.max_degree, domain_info.num_variables
|
||||
|
);
|
||||
|
self.append_message(b"aux info", message.as_bytes())?;
|
||||
|
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
|
||||
|
// append the message to the transcript
|
||||
|
pub(crate) fn append_field_element(
|
||||
|
&mut self,
|
||||
|
label: &'static [u8],
|
||||
|
field_elem: &F,
|
||||
|
) -> Result<(), PolyIOPErrors> {
|
||||
|
self.append_message(label, &to_bytes!(field_elem)?)
|
||||
|
}
|
||||
|
|
||||
|
pub(crate) fn append_prover_message(
|
||||
|
&mut self,
|
||||
|
prover_message: &IOPProverMessage<F>,
|
||||
|
) -> Result<(), PolyIOPErrors> {
|
||||
|
for e in prover_message.evaluations.iter() {
|
||||
|
self.append_field_element(b"prover_message", e)?;
|
||||
|
}
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
|
||||
|
// generate the challenge for the current transcript
|
||||
|
// and append it to the transcript
|
||||
|
pub(crate) fn get_and_append_challenge(
|
||||
|
&mut self,
|
||||
|
label: &'static [u8],
|
||||
|
) -> Result<F, PolyIOPErrors> {
|
||||
|
if self.is_empty {
|
||||
|
return Err(PolyIOPErrors::InvalidTranscript(
|
||||
|
"transcript is empty".to_string(),
|
||||
|
));
|
||||
|
}
|
||||
|
|
||||
|
let mut buf = [0u8; 64];
|
||||
|
self.transcript.challenge_bytes(label, &mut buf);
|
||||
|
let challenge = F::from_le_bytes_mod_order(&buf);
|
||||
|
self.transcript
|
||||
|
.append_message(label, &to_bytes!(&challenge)?);
|
||||
|
Ok(challenge)
|
||||
|
}
|
||||
|
|
||||
|
// generate a list of challenges for the current transcript
|
||||
|
// and append it to the transcript
|
||||
|
pub(crate) fn get_and_append_challenge_vectors(
|
||||
|
&mut self,
|
||||
|
label: &'static [u8],
|
||||
|
len: usize,
|
||||
|
) -> Result<Vec<F>, PolyIOPErrors> {
|
||||
|
// we need to reject when transcript is empty
|
||||
|
let mut res = vec![];
|
||||
|
for _ in 0..len {
|
||||
|
res.push(self.get_and_append_challenge(label)?)
|
||||
|
}
|
||||
|
Ok(res)
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,24 @@ |
|||||
|
//! useful macros.
|
||||
|
|
||||
|
/// Takes as input a struct, and converts them to a series of bytes. All traits
|
||||
|
/// that implement `CanonicalSerialize` can be automatically converted to bytes
|
||||
|
/// in this manner.
|
||||
|
#[macro_export]
|
||||
|
macro_rules! to_bytes {
|
||||
|
($x:expr) => {{
|
||||
|
let mut buf = ark_std::vec![];
|
||||
|
ark_serialize::CanonicalSerialize::serialize($x, &mut buf).map(|_| buf)
|
||||
|
}};
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_to_bytes() {
|
||||
|
use ark_bls12_381::Fr;
|
||||
|
use ark_serialize::CanonicalSerialize;
|
||||
|
use ark_std::One;
|
||||
|
let f1 = Fr::one();
|
||||
|
|
||||
|
let mut bytes = ark_std::vec![];
|
||||
|
f1.serialize(&mut bytes).unwrap();
|
||||
|
assert_eq!(bytes, to_bytes!(&f1).unwrap());
|
||||
|
}
|
@ -0,0 +1,211 @@ |
|||||
|
use crate::{errors::PolyIOPErrors, structs::DomainInfo};
|
||||
|
use ark_ff::PrimeField;
|
||||
|
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
|
||||
|
use ark_std::{end_timer, start_timer};
|
||||
|
use std::{cmp::max, collections::HashMap, marker::PhantomData, rc::Rc};
|
||||
|
|
||||
|
/// A virtual polynomial is a list of multilinear polynomials
|
||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
|
pub struct VirtualPolynomial<F: PrimeField> {
|
||||
|
/// Aux information about the multilinear polynomial
|
||||
|
pub domain_info: DomainInfo<F>,
|
||||
|
/// list of reference to products (as usize) of multilinear extension
|
||||
|
pub products: Vec<(F, Vec<usize>)>,
|
||||
|
/// Stores multilinear extensions in which product multiplicand can refer
|
||||
|
/// to.
|
||||
|
pub flattened_ml_extensions: Vec<Rc<DenseMultilinearExtension<F>>>,
|
||||
|
/// Pointers to the above poly extensions
|
||||
|
raw_pointers_lookup_table: HashMap<*const DenseMultilinearExtension<F>, usize>,
|
||||
|
}
|
||||
|
|
||||
|
impl<F: PrimeField> VirtualPolynomial<F> {
|
||||
|
/// Returns an empty polynomial
|
||||
|
pub fn new(num_variables: usize) -> Self {
|
||||
|
VirtualPolynomial {
|
||||
|
domain_info: DomainInfo {
|
||||
|
max_degree: 0,
|
||||
|
num_variables,
|
||||
|
phantom: PhantomData::default(),
|
||||
|
},
|
||||
|
products: Vec::new(),
|
||||
|
flattened_ml_extensions: Vec::new(),
|
||||
|
raw_pointers_lookup_table: HashMap::new(),
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// Add a list of multilinear extensions that is meant to be multiplied
|
||||
|
/// together. The resulting polynomial will be multiplied by the scalar
|
||||
|
/// `coefficient`.
|
||||
|
pub fn add_product(
|
||||
|
&mut self,
|
||||
|
product: impl IntoIterator<Item = Rc<DenseMultilinearExtension<F>>>,
|
||||
|
coefficient: F,
|
||||
|
) -> Result<(), PolyIOPErrors> {
|
||||
|
let product: Vec<Rc<DenseMultilinearExtension<F>>> = product.into_iter().collect();
|
||||
|
let mut indexed_product = Vec::with_capacity(product.len());
|
||||
|
assert!(!product.is_empty());
|
||||
|
self.domain_info.max_degree = max(self.domain_info.max_degree, product.len());
|
||||
|
for m in product {
|
||||
|
if m.num_vars != self.domain_info.num_variables {
|
||||
|
return Err(PolyIOPErrors::InvalidParameters(format!(
|
||||
|
"product has a multiplicand with wrong number of variables {} vs {}",
|
||||
|
m.num_vars, self.domain_info.num_variables
|
||||
|
)));
|
||||
|
}
|
||||
|
|
||||
|
let m_ptr: *const DenseMultilinearExtension<F> = Rc::as_ptr(&m);
|
||||
|
if let Some(index) = self.raw_pointers_lookup_table.get(&m_ptr) {
|
||||
|
indexed_product.push(*index)
|
||||
|
} else {
|
||||
|
let curr_index = self.flattened_ml_extensions.len();
|
||||
|
self.flattened_ml_extensions.push(m.clone());
|
||||
|
self.raw_pointers_lookup_table.insert(m_ptr, curr_index);
|
||||
|
indexed_product.push(curr_index);
|
||||
|
}
|
||||
|
}
|
||||
|
self.products.push((coefficient, indexed_product));
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
|
||||
|
/// Evaluate the polynomial at point `point`
|
||||
|
pub fn evaluate(&self, point: &[F]) -> Result<F, PolyIOPErrors> {
|
||||
|
let start = start_timer!(|| "begin evaluation");
|
||||
|
|
||||
|
if self.domain_info.num_variables != point.len() {
|
||||
|
return Err(PolyIOPErrors::InvalidParameters(format!(
|
||||
|
"wrong number of variables {} vs {}",
|
||||
|
self.domain_info.num_variables,
|
||||
|
point.len()
|
||||
|
)));
|
||||
|
}
|
||||
|
|
||||
|
let evals: Vec<F> = self
|
||||
|
.flattened_ml_extensions
|
||||
|
.iter()
|
||||
|
.map(|x| {
|
||||
|
x.evaluate(point).unwrap() // safe unwrap here since we have
|
||||
|
// already checked that num_var
|
||||
|
// matches
|
||||
|
})
|
||||
|
.collect();
|
||||
|
|
||||
|
let res = self
|
||||
|
.products
|
||||
|
.iter()
|
||||
|
.map(|(c, p)| *c * p.iter().map(|&i| evals[i]).product::<F>())
|
||||
|
.sum();
|
||||
|
|
||||
|
end_timer!(start);
|
||||
|
Ok(res)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
pub(crate) mod test {
|
||||
|
use super::*;
|
||||
|
use ark_std::rand::{Rng, RngCore};
|
||||
|
|
||||
|
pub fn random_product<F: PrimeField, R: RngCore>(
|
||||
|
nv: usize,
|
||||
|
num_multiplicands: usize,
|
||||
|
rng: &mut R,
|
||||
|
) -> (Vec<Rc<DenseMultilinearExtension<F>>>, F) {
|
||||
|
let mut multiplicands = Vec::with_capacity(num_multiplicands);
|
||||
|
for _ in 0..num_multiplicands {
|
||||
|
multiplicands.push(Vec::with_capacity(1 << nv))
|
||||
|
}
|
||||
|
let mut sum = F::zero();
|
||||
|
|
||||
|
for _ in 0..(1 << nv) {
|
||||
|
let mut product = F::one();
|
||||
|
for i in 0..num_multiplicands {
|
||||
|
let val = F::rand(rng);
|
||||
|
multiplicands[i].push(val);
|
||||
|
product *= val;
|
||||
|
}
|
||||
|
sum += product;
|
||||
|
}
|
||||
|
|
||||
|
(
|
||||
|
multiplicands
|
||||
|
.into_iter()
|
||||
|
.map(|x| Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x)))
|
||||
|
.collect(),
|
||||
|
sum,
|
||||
|
)
|
||||
|
}
|
||||
|
|
||||
|
pub(crate) fn random_list_of_products<F: PrimeField, R: RngCore>(
|
||||
|
nv: usize,
|
||||
|
num_multiplicands_range: (usize, usize),
|
||||
|
num_products: usize,
|
||||
|
rng: &mut R,
|
||||
|
) -> (VirtualPolynomial<F>, F) {
|
||||
|
let mut sum = F::zero();
|
||||
|
let mut poly = VirtualPolynomial::new(nv);
|
||||
|
for _ in 0..num_products {
|
||||
|
let num_multiplicands =
|
||||
|
rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1);
|
||||
|
let (product, product_sum) = random_product(nv, num_multiplicands, rng);
|
||||
|
let coefficient = F::rand(rng);
|
||||
|
poly.add_product(product.into_iter(), coefficient).unwrap();
|
||||
|
sum += product_sum * coefficient;
|
||||
|
}
|
||||
|
|
||||
|
(poly, sum)
|
||||
|
}
|
||||
|
|
||||
|
// pub fn random_zero_product<F: PrimeField, R: RngCore>(
|
||||
|
// nv: usize,
|
||||
|
// num_multiplicands: usize,
|
||||
|
// rng: &mut R,
|
||||
|
// ) -> Vec<Rc<DenseMultilinearExtension<F>>> {
|
||||
|
// let degree = 2;
|
||||
|
// let mut multiplicands = Vec::with_capacity(degree);
|
||||
|
// for _ in 0..degree {
|
||||
|
// multiplicands.push(Vec::with_capacity(1 << nv))
|
||||
|
// }
|
||||
|
// let mut sum = F::zero();
|
||||
|
|
||||
|
// for _ in 0..(1 << nv) {
|
||||
|
// let mut product = F::one();
|
||||
|
// for i in 0..degree {
|
||||
|
// let val = F::zero(); // F::rand(rng);
|
||||
|
// multiplicands[i].push(val);
|
||||
|
// product *= val;
|
||||
|
// }
|
||||
|
// sum += product;
|
||||
|
// }
|
||||
|
|
||||
|
// // // last nv offsets the poly to 0
|
||||
|
// // for i in 0..num_multiplicands - 1 {
|
||||
|
// // multiplicands[i].push(F::one());
|
||||
|
// // }
|
||||
|
// // multiplicands[num_multiplicands - 1].push(-sum);
|
||||
|
|
||||
|
// multiplicands
|
||||
|
// .into_iter()
|
||||
|
// .map(|x|
|
||||
|
// Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x)))
|
||||
|
// .collect()
|
||||
|
// }
|
||||
|
|
||||
|
// pub(crate) fn random_zero_list_of_products<F: PrimeField, R: RngCore>(
|
||||
|
// nv: usize,
|
||||
|
// num_multiplicands_range: (usize, usize),
|
||||
|
// num_products: usize,
|
||||
|
// rng: &mut R,
|
||||
|
// ) -> VirtualPolynomial<F> {
|
||||
|
// let mut poly = VirtualPolynomial::new(nv);
|
||||
|
// for _ in 0..num_products {
|
||||
|
// let num_multiplicands =
|
||||
|
//
|
||||
|
// rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1);
|
||||
|
// let product = random_zero_product(nv, num_multiplicands, rng);
|
||||
|
// let coefficient = F::rand(rng);
|
||||
|
// poly.add_product(product.into_iter(), coefficient);
|
||||
|
// }
|
||||
|
|
||||
|
// poly
|
||||
|
// }
|
||||
|
}
|
@ -0,0 +1,316 @@ |
|||||
|
mod prover;
|
||||
|
mod verifier;
|
||||
|
|
||||
|
use ark_ff::PrimeField;
|
||||
|
use ark_poly::DenseMultilinearExtension;
|
||||
|
pub use prover::ProverState;
|
||||
|
use std::rc::Rc;
|
||||
|
pub use verifier::VerifierState;
|
||||
|
|
||||
|
use crate::{
|
||||
|
errors::PolyIOPErrors,
|
||||
|
structs::{DomainInfo, IOPProof, SubClaim},
|
||||
|
sum_check::SumCheck,
|
||||
|
transcript::IOPTranscript,
|
||||
|
virtual_poly::VirtualPolynomial,
|
||||
|
PolyIOP,
|
||||
|
};
|
||||
|
|
||||
|
pub trait ZeroCheck<F: PrimeField> {
|
||||
|
type Proof;
|
||||
|
type PolyList;
|
||||
|
type DomainInfo;
|
||||
|
type SubClaim;
|
||||
|
type Transcript;
|
||||
|
|
||||
|
/// Initialize the system with a transcript
|
||||
|
///
|
||||
|
/// This function is optional -- in the case where a ZeroCheck is
|
||||
|
/// an building block for a more complex protocol, the transcript
|
||||
|
/// may be initialized by this complex protocol, and passed to the
|
||||
|
/// ZeroCheck prover/verifier.
|
||||
|
fn init_transcript() -> Self::Transcript;
|
||||
|
|
||||
|
fn prove(
|
||||
|
poly: &Self::PolyList,
|
||||
|
transcript: &mut Self::Transcript,
|
||||
|
) -> Result<Self::Proof, PolyIOPErrors>;
|
||||
|
|
||||
|
/// verify the claimed sum using the proof
|
||||
|
fn verify(
|
||||
|
proof: &Self::Proof,
|
||||
|
domain_info: &Self::DomainInfo,
|
||||
|
transcript: &mut Self::Transcript,
|
||||
|
) -> Result<Self::SubClaim, PolyIOPErrors>;
|
||||
|
}
|
||||
|
|
||||
|
impl<F: PrimeField> ZeroCheck<F> for PolyIOP<F> {
|
||||
|
type Proof = IOPProof<F>;
|
||||
|
type PolyList = VirtualPolynomial<F>;
|
||||
|
type DomainInfo = DomainInfo<F>;
|
||||
|
type SubClaim = SubClaim<F>;
|
||||
|
type Transcript = IOPTranscript<F>;
|
||||
|
|
||||
|
/// Initialize the system with a transcript
|
||||
|
///
|
||||
|
/// This function is optional -- in the case where a ZeroCheck is
|
||||
|
/// an building block for a more complex protocol, the transcript
|
||||
|
/// may be initialized by this complex protocol, and passed to the
|
||||
|
/// ZeroCheck prover/verifier.
|
||||
|
fn init_transcript() -> Self::Transcript {
|
||||
|
IOPTranscript::<F>::new(b"Initializing ZeroCheck transcript")
|
||||
|
}
|
||||
|
|
||||
|
fn prove(
|
||||
|
poly: &Self::PolyList,
|
||||
|
transcript: &mut Self::Transcript,
|
||||
|
) -> Result<Self::Proof, PolyIOPErrors> {
|
||||
|
let length = poly.domain_info.num_variables;
|
||||
|
let r = transcript.get_and_append_challenge_vectors(b"vector r", length)?;
|
||||
|
let f_hat = build_f_hat(poly, r.as_ref());
|
||||
|
<Self as SumCheck<F>>::prove(&f_hat, transcript)
|
||||
|
}
|
||||
|
|
||||
|
/// Verify the claimed sum using the proof.
|
||||
|
/// Caller needs to makes sure that `\hat f = f * eq(x, r)`
|
||||
|
fn verify(
|
||||
|
proof: &Self::Proof,
|
||||
|
domain_info: &Self::DomainInfo,
|
||||
|
transcript: &mut Self::Transcript,
|
||||
|
) -> Result<Self::SubClaim, PolyIOPErrors> {
|
||||
|
println!(
|
||||
|
"sum: {}",
|
||||
|
proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1]
|
||||
|
);
|
||||
|
|
||||
|
<Self as SumCheck<F>>::verify(F::zero(), proof, domain_info, transcript)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
// 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]) -> VirtualPolynomial<F> {
|
||||
|
assert_eq!(poly.domain_info.num_variables, r.len());
|
||||
|
let mut res = poly.clone();
|
||||
|
let eq_x_r = build_eq_x_r(r);
|
||||
|
res.add_product([eq_x_r; 1], F::one());
|
||||
|
// // First, we build array for {1 - r_i}
|
||||
|
// let one_minus_r: Vec<F> = r.iter().map(|ri| F::one() - ri).collect();
|
||||
|
|
||||
|
// let mut eval = vec![];
|
||||
|
// // let eq_x_r = build_eq_x_r(r);
|
||||
|
// let num_var = r.len();
|
||||
|
// let mut res = VirtualPolynomial::new(num_var);
|
||||
|
// // res.add_product([eq_x_r; 1], F::one());
|
||||
|
|
||||
|
// for i in 0..1 << num_var {
|
||||
|
// let bit_sequence = bit_decompose(i, num_var);
|
||||
|
// let bit_points: Vec<F> = bit_sequence.iter().map(|&x| F::from(x as
|
||||
|
// u64)).collect(); let mut eq_eval = F::one();
|
||||
|
// for (&bit, (ri, one_minus_ri)) in
|
||||
|
// bit_sequence.iter().zip(r.iter().zip(one_minus_r.iter())) {
|
||||
|
// if bit {
|
||||
|
// eq_eval *= ri;
|
||||
|
// } else {
|
||||
|
// eq_eval *= one_minus_ri;
|
||||
|
// }
|
||||
|
// }
|
||||
|
// eval.push(eq_eval * poly.evaluate(&bit_points))
|
||||
|
// }
|
||||
|
// let hat_f = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
|
||||
|
// num_var, eval,
|
||||
|
// ));
|
||||
|
// res.add_product([hat_f; 1], F::one());
|
||||
|
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))
|
||||
|
fn build_eq_x_r<F: PrimeField>(r: &[F]) -> Rc<DenseMultilinearExtension<F>> {
|
||||
|
// 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![];
|
||||
|
|
||||
|
// TODO: optimize the following code
|
||||
|
// currently, a naive implementation requires num_var * 2^num_var
|
||||
|
// field multiplications.
|
||||
|
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()))
|
||||
|
{
|
||||
|
if bit {
|
||||
|
current_eval *= *ri;
|
||||
|
} else {
|
||||
|
current_eval *= *one_minus_ri;
|
||||
|
}
|
||||
|
}
|
||||
|
eval.push(current_eval);
|
||||
|
}
|
||||
|
let res = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
|
||||
|
num_var, eval,
|
||||
|
));
|
||||
|
|
||||
|
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
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod test {
|
||||
|
|
||||
|
use super::ZeroCheck;
|
||||
|
use crate::{virtual_poly::test::random_zero_list_of_products, PolyIOP, VirtualPolynomial};
|
||||
|
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_polynomial(nv: usize, num_multiplicands_range: (usize, usize), num_products: usize) {
|
||||
|
let mut rng = test_rng();
|
||||
|
let mut transcript = PolyIOP::init_transcript();
|
||||
|
transcript
|
||||
|
.append_message(b"testing", b"initializing transcript for testing")
|
||||
|
.unwrap();
|
||||
|
let poly = random_zero_list_of_products::<Fr, _>(
|
||||
|
nv,
|
||||
|
num_multiplicands_range,
|
||||
|
num_products,
|
||||
|
&mut rng,
|
||||
|
);
|
||||
|
// println!("{:?}", poly);
|
||||
|
|
||||
|
let proof = PolyIOP::prove(&poly, &mut transcript).expect("fail to prove");
|
||||
|
println!(
|
||||
|
"{}",
|
||||
|
proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1]
|
||||
|
);
|
||||
|
|
||||
|
let poly_info = poly.domain_info.clone();
|
||||
|
let mut transcript = PolyIOP::init_transcript();
|
||||
|
transcript
|
||||
|
.append_message(b"testing", b"initializing transcript for testing")
|
||||
|
.unwrap();
|
||||
|
let subclaim =
|
||||
|
PolyIOP::verify(&proof, &poly_info, &mut transcript).expect("fail to verify");
|
||||
|
assert!(
|
||||
|
poly.evaluate(&subclaim.point) == subclaim.expected_evaluation,
|
||||
|
"wrong subclaim"
|
||||
|
);
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_trivial_polynomial() {
|
||||
|
let nv = 1;
|
||||
|
let num_multiplicands_range = (4, 5);
|
||||
|
let num_products = 1;
|
||||
|
|
||||
|
test_polynomial(nv, num_multiplicands_range, num_products);
|
||||
|
}
|
||||
|
#[test]
|
||||
|
fn test_normal_polynomial() {
|
||||
|
let nv = 16;
|
||||
|
let num_multiplicands_range = (4, 9);
|
||||
|
let num_products = 5;
|
||||
|
|
||||
|
test_polynomial(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_polynomial(nv, num_multiplicands_range, num_products);
|
||||
|
}
|
||||
|
|
||||
|
#[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),
|
||||
|
);
|
||||
|
poly.add_product(
|
||||
|
vec![
|
||||
|
ml_extensions[1].clone(),
|
||||
|
ml_extensions[4].clone(),
|
||||
|
ml_extensions[4].clone(),
|
||||
|
],
|
||||
|
Fr::rand(&mut rng),
|
||||
|
);
|
||||
|
poly.add_product(
|
||||
|
vec![
|
||||
|
ml_extensions[3].clone(),
|
||||
|
ml_extensions[2].clone(),
|
||||
|
ml_extensions[1].clone(),
|
||||
|
],
|
||||
|
Fr::rand(&mut rng),
|
||||
|
);
|
||||
|
poly.add_product(
|
||||
|
vec![ml_extensions[0].clone(), ml_extensions[0].clone()],
|
||||
|
Fr::rand(&mut rng),
|
||||
|
);
|
||||
|
poly.add_product(vec![ml_extensions[4].clone()], Fr::rand(&mut rng));
|
||||
|
|
||||
|
assert_eq!(poly.flattened_ml_extensions.len(), 5);
|
||||
|
|
||||
|
let mut transcript = PolyIOP::init_transcript();
|
||||
|
|
||||
|
transcript
|
||||
|
.append_message(b"testing", b"initializing transcript for testing")
|
||||
|
.unwrap();
|
||||
|
let poly_info = poly.domain_info.clone();
|
||||
|
let proof = PolyIOP::prove(&poly, &mut transcript).expect("fail to prove");
|
||||
|
|
||||
|
let mut transcript = PolyIOP::init_transcript();
|
||||
|
|
||||
|
transcript
|
||||
|
.append_message(b"testing", b"initializing transcript for testing")
|
||||
|
.unwrap();
|
||||
|
let subclaim =
|
||||
|
PolyIOP::verify(&proof, &poly_info, &mut transcript).expect("fail to verify");
|
||||
|
assert!(
|
||||
|
poly.evaluate(&subclaim.point) == subclaim.expected_evaluation,
|
||||
|
"wrong subclaim"
|
||||
|
);
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,17 @@ |
|||||
|
use ark_ff::PrimeField;
|
||||
|
use ark_poly::DenseMultilinearExtension;
|
||||
|
|
||||
|
/// Prover State
|
||||
|
pub struct ProverState<F: PrimeField> {
|
||||
|
/// sampled randomness given by the verifier
|
||||
|
pub challenges: Vec<F>,
|
||||
|
/// Stores the list of products that is meant to be added together. Each
|
||||
|
/// multiplicand is represented by the index in flattened_ml_extensions
|
||||
|
pub list_of_products: Vec<(F, Vec<usize>)>,
|
||||
|
/// Stores a list of multilinear extensions in which `self.list_of_products`
|
||||
|
/// points to
|
||||
|
pub flattened_ml_extensions: Vec<DenseMultilinearExtension<F>>,
|
||||
|
pub(crate) num_vars: usize,
|
||||
|
pub(crate) max_degree: usize,
|
||||
|
pub(crate) round: usize,
|
||||
|
}
|
@ -0,0 +1,14 @@ |
|||||
|
use ark_ff::PrimeField;
|
||||
|
|
||||
|
/// Verifier State
|
||||
|
pub struct VerifierState<F: PrimeField> {
|
||||
|
round: usize,
|
||||
|
nv: 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>,
|
||||
|
}
|