//! 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, IOPProverState, IOPVerifierState, SubClaim}, transcript::IOPTranscript, virtual_poly::VirtualPolynomial, PolyIOP, }; use ark_ff::PrimeField; use ark_std::{end_timer, start_timer}; mod prover; mod verifier; /// Trait for doing sum check protocols. pub trait SumCheck { 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 in the form of a VirtualPolynomial. fn prove( poly: &Self::PolyList, transcript: &mut Self::Transcript, ) -> Result; /// Verify the claimed sum using the proof fn verify( sum: F, proof: &Self::Proof, domain_info: &Self::DomainInfo, transcript: &mut Self::Transcript, ) -> Result; } /// Trait for sum check protocol prover side APIs. pub trait SumCheckProver 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 in the form of a VirtualPolynomial. fn prover_init(polynomial: &Self::PolyList) -> Result; /// 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, ) -> Result; } /// Trait for sum check protocol verifier side APIs. pub trait SumCheckVerifier { 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; /// 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; } impl SumCheck for PolyIOP { type Proof = IOPProof; type PolyList = VirtualPolynomial; type DomainInfo = DomainInfo; type SubClaim = SubClaim; type Transcript = IOPTranscript; 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::::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 in the form of a VirtualPolynomial. fn prove( poly: &Self::PolyList, transcript: &mut Self::Transcript, ) -> Result { let start = start_timer!(|| "sum check prove"); transcript.append_domain_info(&poly.domain_info)?; let mut prover_state = IOPProverState::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 = IOPProverState::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 { let start = start_timer!(|| "sum check verify"); transcript.append_domain_info(domain_info)?; let mut verifier_state = IOPVerifierState::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)?; IOPVerifierState::verify_round_and_update_state( &mut verifier_state, prover_msg, transcript, )?; } let res = IOPVerifierState::check_and_generate_subclaim(&verifier_state, &claimed_sum); end_timer!(start); res } } #[cfg(test)] mod test { use super::*; 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, ) -> Result<(), PolyIOPErrors> { let mut rng = test_rng(); let mut transcript = as SumCheck>::init_transcript(); let (poly, asserted_sum) = VirtualPolynomial::rand(nv, num_multiplicands_range, num_products, &mut rng)?; let proof = as SumCheck>::prove(&poly, &mut transcript)?; let poly_info = poly.domain_info.clone(); let mut transcript = as SumCheck>::init_transcript(); let subclaim = as SumCheck>::verify( asserted_sum, &proof, &poly_info, &mut transcript, )?; assert!( poly.evaluate(&subclaim.point).unwrap() == subclaim.expected_evaluation, "wrong subclaim" ); Ok(()) } fn test_sumcheck_internal( nv: usize, num_multiplicands_range: (usize, usize), num_products: usize, ) -> Result<(), PolyIOPErrors> { let mut rng = test_rng(); let (poly, asserted_sum) = VirtualPolynomial::::rand(nv, num_multiplicands_range, num_products, &mut rng)?; let poly_info = poly.domain_info.clone(); let mut prover_state = IOPProverState::prover_init(&poly)?; let mut verifier_state = IOPVerifierState::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 = IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge) .unwrap(); challenge = Some( IOPVerifierState::verify_round_and_update_state( &mut verifier_state, &prover_message, &mut transcript, ) .unwrap(), ); } let subclaim = IOPVerifierState::check_and_generate_subclaim(&verifier_state, &asserted_sum) .expect("fail to generate subclaim"); assert!( poly.evaluate(&subclaim.point).unwrap() == subclaim.expected_evaluation, "wrong subclaim" ); Ok(()) } #[test] fn test_trivial_polynomial() -> Result<(), PolyIOPErrors> { 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() -> Result<(), PolyIOPErrors> { 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] fn zero_polynomial_should_error() { let nv = 0; let num_multiplicands_range = (4, 13); let num_products = 5; assert!(test_sumcheck(nv, num_multiplicands_range, num_products).is_err()); assert!(test_sumcheck_internal(nv, num_multiplicands_range, num_products).is_err()); } #[test] fn test_extract_sum() -> Result<(), PolyIOPErrors> { let mut rng = test_rng(); let mut transcript = as SumCheck>::init_transcript(); let (poly, asserted_sum) = VirtualPolynomial::::rand(8, (3, 4), 3, &mut rng)?; let proof = as SumCheck>::prove(&poly, &mut transcript)?; assert_eq!( as SumCheck>::extract_sum(&proof), asserted_sum ); Ok(()) } #[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() -> Result<(), PolyIOPErrors> { let mut rng = test_rng(); let ml_extensions: Vec<_> = (0..5) .map(|_| Rc::new(DenseMultilinearExtension::::rand(8, &mut rng))) .collect(); let mut poly = VirtualPolynomial::new(8); poly.add_mle_list( vec![ ml_extensions[2].clone(), ml_extensions[3].clone(), ml_extensions[0].clone(), ], Fr::rand(&mut rng), )?; poly.add_mle_list( vec![ ml_extensions[1].clone(), ml_extensions[4].clone(), ml_extensions[4].clone(), ], Fr::rand(&mut rng), )?; poly.add_mle_list( vec![ ml_extensions[3].clone(), ml_extensions[2].clone(), ml_extensions[1].clone(), ], Fr::rand(&mut rng), )?; poly.add_mle_list( vec![ml_extensions[0].clone(), ml_extensions[0].clone()], Fr::rand(&mut rng), )?; poly.add_mle_list(vec![ml_extensions[4].clone()], Fr::rand(&mut rng))?; assert_eq!(poly.flattened_ml_extensions.len(), 5); // test memory usage for prover let prover = IOPProverState::prover_init(&poly).unwrap(); assert_eq!(prover.poly.flattened_ml_extensions.len(), 5); drop(prover); let mut transcript = as SumCheck>::init_transcript(); let poly_info = poly.domain_info.clone(); let proof = as SumCheck>::prove(&poly, &mut transcript)?; let asserted_sum = as SumCheck>::extract_sum(&proof); let mut transcript = as SumCheck>::init_transcript(); let subclaim = as SumCheck>::verify( asserted_sum, &proof, &poly_info, &mut transcript, )?; assert!( poly.evaluate(&subclaim.point)? == subclaim.expected_evaluation, "wrong subclaim" ); Ok(()) } }