From aae0fd932f9bcab44eb4fed926045104b180ab85 Mon Sep 17 00:00:00 2001 From: goforashutosh Date: Sat, 10 Aug 2024 11:54:16 -0400 Subject: [PATCH 1/9] FiniteFiniteField corr --- src/polynomial/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/polynomial/mod.rs b/src/polynomial/mod.rs index 49d9a74..378ccfc 100644 --- a/src/polynomial/mod.rs +++ b/src/polynomial/mod.rs @@ -7,13 +7,13 @@ //! [`Monomial`] [`Basis`]. //! //! - [`Polynomial`] struct represents a polynomial in any basis. These are generic over the -//! [`Basis`] and [`FiniteFiniteField`] traits. +//! [`Basis`] and [`FiniteField`] traits. //! - [`Basis`] trait is used to specify the basis of the polynomial which can be either: //! - [`Monomial`] basis as shown above. //! - [`Lagrange`] basis which is used in the [Lagrange interpolation](https://en.wikipedia.org/wiki/Lagrange_polynomial). //! - Includes arithmetic operations such as addition, subtraction, multiplication, and division in //! the [`arithmetic`] module. The [`Polynomial`] struct is generic over the [`Basis`] and -//! [`FiniteFiniteField`] traits. +//! [`FiniteField`] traits. //! - Includes Discrete Fourier Transform (DFT) for polynomials in the [`Monomial`] basis to convert //! into the [`Lagrange`] basis via evaluation at the roots of unity. @@ -121,7 +121,7 @@ impl Polynomial { self.coefficients.iter().rev().find(|&&coeff| coeff != F::ZERO).copied().unwrap_or(F::ZERO) } - /// Evaluates the polynomial at a given [`FiniteFiniteField`] element `x` using the [`Monomial`] + /// Evaluates the polynomial at a given [`FiniteField`] element `x` using the [`Monomial`] /// basis. This is not using Horner's method or any other optimization. /// /// ## Arguments: @@ -235,8 +235,8 @@ impl Polynomial { /// evaluation of the polynomial at the roots of unity. /// /// ## Panics - /// - This function will panic in calling [`FiniteFiniteField::primitive_root_of_unity`] if the - /// field does not have roots of unity for the degree of the polynomial. + /// - This function will panic in calling [`FiniteField::primitive_root_of_unity`] if the field + /// does not have roots of unity for the degree of the polynomial. pub fn dft(&self) -> Polynomial, F, D> { let n = self.num_terms(); let primitive_root_of_unity = F::primitive_root_of_unity(n); @@ -310,11 +310,11 @@ impl Polynomial, F, D> { /// polynomial. The evaluation of the polynomial at `x` is then given by $L(x)$. /// /// ## Arguments: - /// - `x`: The field element as [`FiniteFiniteField`] at which to evaluate the polynomial. + /// - `x`: The field element as [`FiniteField`] at which to evaluate the polynomial. /// /// ## Returns: /// - The result of evaluating the polynomial at `x` which is an element of the associated - /// [`FiniteFiniteField`]. + /// [`FiniteField`]. pub fn evaluate(&self, x: F) -> F { let n = self.coefficients.len(); From 76317c979ca2136beec4343a2ad2a46d9d1de3fc Mon Sep 17 00:00:00 2001 From: goforashutosh Date: Sat, 10 Aug 2024 14:49:51 -0400 Subject: [PATCH 2/9] add basic multi_var_poly --- src/lib.rs | 1 + src/multi_var_poly/mod.rs | 63 +++++++++++++++++++++++++++++++++++++ src/multi_var_poly/tests.rs | 26 +++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 src/multi_var_poly/mod.rs create mode 100644 src/multi_var_poly/tests.rs diff --git a/src/lib.rs b/src/lib.rs index 40ae5dc..a39ace5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ pub mod ecdsa; pub mod encryption; pub mod hashes; pub mod kzg; +pub mod multi_var_poly; pub mod polynomial; pub mod tree; diff --git a/src/multi_var_poly/mod.rs b/src/multi_var_poly/mod.rs new file mode 100644 index 0000000..5f5dd25 --- /dev/null +++ b/src/multi_var_poly/mod.rs @@ -0,0 +1,63 @@ +use super::*; +use crate::algebra::field::FiniteField; + +// L is the number of variables +pub struct MultiVarPolynomial { + degree: [usize; L], + coefficients: Vec, +} + +fn generate_cartesian_product(n: usize, l: &[usize]) -> Vec> { + let mut result = vec![vec![]]; + + for i in 0..n { + let mut new_result = Vec::new(); + for item in result.iter() { + for j in 0..l[i] { + let mut new_item = item.clone(); + new_item.push(j); + new_result.push(new_item); + } + } + result = new_result; + } + + result +} + +impl MultiVarPolynomial { + pub fn new(degree: [usize; L], coefficients: Vec) -> Result { + // Calculate the expected number of coefficients + let expected_coeff_count = degree.iter().map(|&d| d + 1).fold(1, Mul::mul); + + // Check if the number of coefficients matches the expected count + if coefficients.len() != expected_coeff_count { + return Err(format!( + "Invalid number of coefficients. Expected {}, but got {}.", + expected_coeff_count, + coefficients.len() + )); + } + + Ok(Self { degree, coefficients }) + } + + fn evaluation(&self, r: [F; L]) -> F { + let degree_plus_1 = self.degree.map(|x| x + 1); + let cartesian_prod = generate_cartesian_product(L, °ree_plus_1); + let mut result = F::ZERO; + for i in 0..cartesian_prod.len() { + let cood = &cartesian_prod[i]; + let coeff = self.coefficients[i].clone(); + let mut eval_term = F::ONE; + for j in 0..cood.len() { + let exp = cood[j]; + eval_term = eval_term * (r[j].pow(exp)); + } + result += coeff * eval_term; + } + return result; + } +} + +#[cfg(test)] mod tests; diff --git a/src/multi_var_poly/tests.rs b/src/multi_var_poly/tests.rs new file mode 100644 index 0000000..b049745 --- /dev/null +++ b/src/multi_var_poly/tests.rs @@ -0,0 +1,26 @@ +use super::*; + +#[test] +fn test_multivar_polynomial_evaluation() { + // Create a polynomial: f(x, y) = 2x^2 y + 3xy + 1 + let degree = [2, 1]; // Degree 2 in x, degree 1 in y + let coefficients = vec![ + PlutoBaseField::new(1), // Constant term + PlutoBaseField::new(0), // Coefficient of y + PlutoBaseField::new(0), // Coefficient of x + PlutoBaseField::new(3), // Coefficient of xy + PlutoBaseField::new(0), // Coefficient of x^2 + PlutoBaseField::new(2), // Coefficient of yx^2 + ]; + + let poly = MultiVarPolynomial::::new(degree, coefficients).unwrap(); + + // Evaluate the polynomial at (x, y) = (2, 3) + let result = poly.evaluation([PlutoBaseField::new(2), PlutoBaseField::new(3)]); + + // Calculate the expected result + let expected = PlutoBaseField::new(43); + + println!("f(2, 3) = {:?}", result); + assert_eq!(result, expected); +} From 99228b60bc23e84dc21c7c22d57fbbf04ab94631 Mon Sep 17 00:00:00 2001 From: goforashutosh Date: Sat, 10 Aug 2024 15:38:13 -0400 Subject: [PATCH 3/9] make stuff vec --- src/multi_var_poly/mod.rs | 22 +++++++++++++--------- src/multi_var_poly/tests.rs | 6 +++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/multi_var_poly/mod.rs b/src/multi_var_poly/mod.rs index 5f5dd25..6aff263 100644 --- a/src/multi_var_poly/mod.rs +++ b/src/multi_var_poly/mod.rs @@ -2,15 +2,16 @@ use super::*; use crate::algebra::field::FiniteField; // L is the number of variables -pub struct MultiVarPolynomial { - degree: [usize; L], +pub struct MultiVarPolynomial { + degree: Vec, coefficients: Vec, } -fn generate_cartesian_product(n: usize, l: &[usize]) -> Vec> { +// generates the cartesian product (0..l[0]) X (0..l[1]) X ... X (0..l[n-1]) +fn generate_cartesian_product(l: Vec) -> Vec> { let mut result = vec![vec![]]; - for i in 0..n { + for i in 0..l.len() { let mut new_result = Vec::new(); for item in result.iter() { for j in 0..l[i] { @@ -25,8 +26,8 @@ fn generate_cartesian_product(n: usize, l: &[usize]) -> Vec> { result } -impl MultiVarPolynomial { - pub fn new(degree: [usize; L], coefficients: Vec) -> Result { +impl MultiVarPolynomial { + pub fn new(degree: Vec, coefficients: Vec) -> Result { // Calculate the expected number of coefficients let expected_coeff_count = degree.iter().map(|&d| d + 1).fold(1, Mul::mul); @@ -42,9 +43,10 @@ impl MultiVarPolynomial { Ok(Self { degree, coefficients }) } - fn evaluation(&self, r: [F; L]) -> F { - let degree_plus_1 = self.degree.map(|x| x + 1); - let cartesian_prod = generate_cartesian_product(L, °ree_plus_1); + pub fn evaluation(&self, r: Vec) -> F { + assert_eq!(r.len(), self.num_var()); + let degree_plus_1 = self.degree.iter().map(|x| x + 1).collect(); + let cartesian_prod = generate_cartesian_product(degree_plus_1); let mut result = F::ZERO; for i in 0..cartesian_prod.len() { let cood = &cartesian_prod[i]; @@ -58,6 +60,8 @@ impl MultiVarPolynomial { } return result; } + + pub fn num_var(&self) -> usize { self.degree.len() } } #[cfg(test)] mod tests; diff --git a/src/multi_var_poly/tests.rs b/src/multi_var_poly/tests.rs index b049745..e330830 100644 --- a/src/multi_var_poly/tests.rs +++ b/src/multi_var_poly/tests.rs @@ -3,7 +3,7 @@ use super::*; #[test] fn test_multivar_polynomial_evaluation() { // Create a polynomial: f(x, y) = 2x^2 y + 3xy + 1 - let degree = [2, 1]; // Degree 2 in x, degree 1 in y + let degree = vec![2, 1]; // Degree 2 in x, degree 1 in y let coefficients = vec![ PlutoBaseField::new(1), // Constant term PlutoBaseField::new(0), // Coefficient of y @@ -13,10 +13,10 @@ fn test_multivar_polynomial_evaluation() { PlutoBaseField::new(2), // Coefficient of yx^2 ]; - let poly = MultiVarPolynomial::::new(degree, coefficients).unwrap(); + let poly = MultiVarPolynomial::::new(degree, coefficients).unwrap(); // Evaluate the polynomial at (x, y) = (2, 3) - let result = poly.evaluation([PlutoBaseField::new(2), PlutoBaseField::new(3)]); + let result = poly.evaluation(vec![PlutoBaseField::new(2), PlutoBaseField::new(3)]); // Calculate the expected result let expected = PlutoBaseField::new(43); From fb44b25275e872fc03173566da76db3fd4ca6e78 Mon Sep 17 00:00:00 2001 From: goforashutosh Date: Sun, 11 Aug 2024 01:57:16 -0400 Subject: [PATCH 4/9] test passes --- src/lib.rs | 1 + src/multi_var_poly/arithmetic.rs | 84 ++++++++++++++ src/multi_var_poly/mod.rs | 75 ++++++++++++- src/multi_var_poly/tests.rs | 27 ++++- src/sumcheck/mod.rs | 184 +++++++++++++++++++++++++++++++ src/sumcheck/tests.rs | 36 ++++++ 6 files changed, 403 insertions(+), 4 deletions(-) create mode 100644 src/multi_var_poly/arithmetic.rs create mode 100644 src/sumcheck/mod.rs create mode 100644 src/sumcheck/tests.rs diff --git a/src/lib.rs b/src/lib.rs index a39ace5..3937853 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ pub mod hashes; pub mod kzg; pub mod multi_var_poly; pub mod polynomial; +pub mod sumcheck; pub mod tree; use core::{ diff --git a/src/multi_var_poly/arithmetic.rs b/src/multi_var_poly/arithmetic.rs new file mode 100644 index 0000000..25a5576 --- /dev/null +++ b/src/multi_var_poly/arithmetic.rs @@ -0,0 +1,84 @@ +use std::{ + iter::Sum, + ops::{Add, AddAssign, Neg, Sub, SubAssign}, +}; + +use super::*; + +impl Add for MultiVarPolynomial { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + assert_eq!(self.degree, rhs.degree, "Polynomials must have the same degree structure"); + + let coefficients = + self.coefficients.iter().zip(rhs.coefficients.iter()).map(|(&a, &b)| a + b).collect(); + + Self { degree: self.degree, coefficients } + } +} + +impl AddAssign for MultiVarPolynomial { + fn add_assign(&mut self, rhs: Self) { + assert_eq!(self.degree, rhs.degree, "Polynomials must have the same degree structure"); + + for (a, b) in self.coefficients.iter_mut().zip(rhs.coefficients.iter()) { + *a += *b; + } + } +} + +impl Sum for MultiVarPolynomial { + fn sum>(iter: I) -> Self { + iter.reduce(|x, y| x + y).expect("Cannot sum an empty iterator of MultiVarPolynomials") + } +} + +impl Sub for MultiVarPolynomial { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + assert_eq!(self.degree, rhs.degree, "Polynomials must have the same degree structure"); + + let coefficients = + self.coefficients.iter().zip(rhs.coefficients.iter()).map(|(&a, &b)| a - b).collect(); + + Self { degree: self.degree, coefficients } + } +} + +impl SubAssign for MultiVarPolynomial { + fn sub_assign(&mut self, rhs: Self) { + assert_eq!(self.degree, rhs.degree, "Polynomials must have the same degree structure"); + + for (a, b) in self.coefficients.iter_mut().zip(rhs.coefficients.iter()) { + *a -= *b; + } + } +} + +impl Neg for MultiVarPolynomial { + type Output = Self; + + fn neg(self) -> Self::Output { + Self { + degree: self.degree, + coefficients: self.coefficients.into_iter().map(|c| -c).collect(), + } + } +} + +impl Mul for MultiVarPolynomial { + type Output = Self; + + fn mul(self, rhs: F) -> Self::Output { self.scalar_mul(rhs) } +} + +// Implement MulAssign to support *= +impl MulAssign for MultiVarPolynomial { + fn mul_assign(&mut self, rhs: F) { + for coeff in &mut self.coefficients { + *coeff *= rhs; + } + } +} diff --git a/src/multi_var_poly/mod.rs b/src/multi_var_poly/mod.rs index 6aff263..c3abcda 100644 --- a/src/multi_var_poly/mod.rs +++ b/src/multi_var_poly/mod.rs @@ -2,9 +2,10 @@ use super::*; use crate::algebra::field::FiniteField; // L is the number of variables +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MultiVarPolynomial { - degree: Vec, - coefficients: Vec, + pub degree: Vec, + pub coefficients: Vec, } // generates the cartesian product (0..l[0]) X (0..l[1]) X ... X (0..l[n-1]) @@ -43,7 +44,55 @@ impl MultiVarPolynomial { Ok(Self { degree, coefficients }) } - pub fn evaluation(&self, r: Vec) -> F { + /// Constructs a multivariate polynomial from coordinates and coefficients. + /// + /// # Arguments + /// * `coordinates` - A vector of vectors, where each inner vector represents the degrees of a + /// term. + /// * `coefficients` - A vector of coefficients corresponding to each term. + /// + /// # Returns + /// * `Result` - The constructed polynomial or an error message. + /// + /// Doesn't take into account cases where you try to fill the same coefficient twice + pub fn from_coordinates( + coordinates: Vec>, + coefficients: Vec, + ) -> Result { + if coordinates.len() != coefficients.len() { + return Err("The number of coordinates must match the number of coefficients".to_string()); + } + + if coordinates.is_empty() { + return Err("At least one term is required".to_string()); + } + + let num_vars = coordinates[0].len(); + if !coordinates.iter().all(|coord| coord.len() == num_vars) { + return Err("All coordinates must have the same number of variables".to_string()); + } + + let degree: Vec = + (0..num_vars).map(|i| coordinates.iter().map(|coord| coord[i]).max().unwrap_or(0)).collect(); + + let total_terms: usize = degree.iter().map(|&d| d + 1).product(); + let mut full_coefficients = vec![F::ZERO; total_terms]; + + // println!("degree = {:?}", degree); + + for (coord, &coeff) in coordinates.iter().zip(coefficients.iter()) { + let index = coord.iter().enumerate().fold(0, |acc, (i, &d)| { + acc + d * degree.get(i + 1..).unwrap_or(&[]).iter().map(|&d| d + 1).product::() + }); + // println!("Accessing index={}", index); + // println!("for coord={:?}", coord); + full_coefficients[index] = coeff; + } + + Self::new(degree, full_coefficients) + } + + pub fn evaluation(&self, r: &Vec) -> F { assert_eq!(r.len(), self.num_var()); let degree_plus_1 = self.degree.iter().map(|x| x + 1).collect(); let cartesian_prod = generate_cartesian_product(degree_plus_1); @@ -62,6 +111,26 @@ impl MultiVarPolynomial { } pub fn num_var(&self) -> usize { self.degree.len() } + + pub fn sum_over_bool_hypercube(&self) -> F { + // let vec_of_2 = (0..self.num_var()).map(|_| 2 as usize).collect(); + let vec_of_2 = vec![2; self.num_var()]; + let bool_hypercube = generate_cartesian_product(vec_of_2); + let mut sum = F::ZERO; + for cood in bool_hypercube { + let cood_f: Vec = cood.iter().map(|&x| F::from(x)).collect(); + sum += self.evaluation(&cood_f); + } + return sum; + } + + pub fn scalar_mul(&self, scalar: F) -> Self { + Self { + degree: self.degree.clone(), + coefficients: self.coefficients.iter().map(|&c| c * scalar).collect(), + } + } } +pub mod arithmetic; #[cfg(test)] mod tests; diff --git a/src/multi_var_poly/tests.rs b/src/multi_var_poly/tests.rs index e330830..9d23fcd 100644 --- a/src/multi_var_poly/tests.rs +++ b/src/multi_var_poly/tests.rs @@ -16,7 +16,32 @@ fn test_multivar_polynomial_evaluation() { let poly = MultiVarPolynomial::::new(degree, coefficients).unwrap(); // Evaluate the polynomial at (x, y) = (2, 3) - let result = poly.evaluation(vec![PlutoBaseField::new(2), PlutoBaseField::new(3)]); + let result = poly.evaluation(&vec![PlutoBaseField::new(2), PlutoBaseField::new(3)]); + + // Calculate the expected result + let expected = PlutoBaseField::new(43); + + println!("f(2, 3) = {:?}", result); + assert_eq!(result, expected); +} +#[test] +fn test_multivar_from_coods() { + // Create a polynomial: f(x, y) = 2x^2 y + 3xy + 1 + + let coordinates = vec![ + vec![0, 0], // Constant term + vec![1, 1], // xy term + vec![2, 1], // yx^2 term + ]; + let coefficients = vec![ + PlutoBaseField::from(1), // Constant term + PlutoBaseField::from(3), // Coefficient of xy + PlutoBaseField::from(2), // Coefficient of yx^2 + ]; + let poly = MultiVarPolynomial::from_coordinates(coordinates, coefficients).unwrap(); + + // Evaluate the polynomial at (x, y) = (2, 3) + let result = poly.evaluation(&vec![PlutoBaseField::new(2), PlutoBaseField::new(3)]); // Calculate the expected result let expected = PlutoBaseField::new(43); diff --git a/src/sumcheck/mod.rs b/src/sumcheck/mod.rs new file mode 100644 index 0000000..454ce45 --- /dev/null +++ b/src/sumcheck/mod.rs @@ -0,0 +1,184 @@ +use rand::thread_rng; + +use super::*; +use crate::{ + algebra::field::FiniteField, multi_var_poly::MultiVarPolynomial, polynomial::Polynomial, +}; + +pub struct SumCheckProver { + pub multi_var_poly: MultiVarPolynomial, + pub current_round: usize, + pub total_rounds: usize, +} + +impl SumCheckProver { + pub fn new(poly: MultiVarPolynomial) -> Self { + let tot_rnds = poly.num_var(); + SumCheckProver { multi_var_poly: poly, current_round: 0, total_rounds: tot_rnds } + } + + pub fn sum_poly(&self) -> F { return self.multi_var_poly.sum_over_bool_hypercube(); } + + pub fn send_poly(&self) -> Vec { + if self.multi_var_poly.num_var() > 1 { + let tot_deg_ex_first: usize = + self.multi_var_poly.degree.iter().skip(1).map(|&x| x + 1).product(); + + let mut poly_to_send: Vec = vec![]; + // need to include degree[0] in the loop here + for i in 0..=self.multi_var_poly.degree[0] { + let degree_ex_first = self.multi_var_poly.degree[1..].to_vec(); + let x_to_i_coeffs = self.multi_var_poly.coefficients + [i * tot_deg_ex_first..(i + 1) * tot_deg_ex_first] + .to_vec(); + // Over here we use the fact that if + // $g(X_1, X_2, ..., X_n) = \sum_{i=0}^d c_i(X_2, X_3, ..., X_n) X^i$ + // then + // $\sum_{X_2, ..., X_n \in \{0,1\}} g(X_1, X_2, ..., X_n) = \sum_{i=0}^d [\sum_{X_2, ..., + // X_n \in \{0,1\}} c_i(X_2, X_3, ..., X_n)] X^i$ + // so the coefficients of the polynomial sent by the Prover are just the + // `sum_over_bool_hypercube` for the MultiVarPolynomials given by coefficients + // `x_to_i_coeffs` where the variables have degrees = `degree_ex_first` + poly_to_send.push( + MultiVarPolynomial::new(degree_ex_first, x_to_i_coeffs) + .unwrap() + .sum_over_bool_hypercube(), + ); + } + return poly_to_send; + } else { + return self.multi_var_poly.coefficients.clone(); + } + } + + pub fn reduce_poly(&mut self, r: F) { + if self.multi_var_poly.num_var() > 1 { + let tot_deg_ex_first: usize = + self.multi_var_poly.degree.iter().skip(1).map(|&x| x + 1).product(); + + // These clone the vectors + let degree_ex_first = self.multi_var_poly.degree[1..].to_vec(); + let x_to_0_coeffs = self.multi_var_poly.coefficients[0..tot_deg_ex_first].to_vec(); + let mut new_multi_var_poly = MultiVarPolynomial::new(degree_ex_first, x_to_0_coeffs).unwrap(); + + for i in 1..=self.multi_var_poly.degree[0] { + let degree_ex_first = self.multi_var_poly.degree[1..].to_vec(); + let x_to_i_coeffs = self.multi_var_poly.coefficients + [i * tot_deg_ex_first..(i + 1) * tot_deg_ex_first] + .to_vec(); + + // Similar to `send_poly` above if + // $g(X_1, X_2, ..., X_n) = \sum_{i=0}^d c_i(X_2, X_3, ..., X_n) X^i$ + // then + // $g(r, X_2, ..., X_n) = \sum_{i=0}^d c_i(X_2, X_3, ..., X_n) r^i$ + new_multi_var_poly += + MultiVarPolynomial::new(degree_ex_first, x_to_i_coeffs).unwrap() * r.pow(i); + } + + self.multi_var_poly = new_multi_var_poly; + } else { + self.multi_var_poly = + MultiVarPolynomial::new(vec![0], vec![self.multi_var_poly.evaluation(&vec![r])]).unwrap(); + } + self.current_round += 1; + } +} + +pub struct SumCheckVerifier { + pub current_round: usize, + pub total_rounds: usize, + pub degree: Vec, + pub result: F, + pub claim: F, + pub challenges_sent: Vec, + pub _poly_received: Vec>, +} +impl SumCheckVerifier { + fn new(c: F, deg: Vec) -> Self { + Self { + current_round: 0, + total_rounds: deg.len(), + degree: deg, + result: c, + claim: c, + challenges_sent: vec![], + _poly_received: vec![], + } + } + + fn verify_internal_rounds(&mut self, h_poly: Vec) -> F { + assert_eq!( + h_poly.len(), + self.degree[self.current_round] + 1, + "Verifier Abort: Prover's polynomial size incorrect!" + ); + let h_poly_at_0 = h_poly[0]; + let mut h_poly_at_1 = F::ZERO; + for i in 0..h_poly.len() { + h_poly_at_1 += h_poly[i]; + } + let sum = h_poly_at_0 + h_poly_at_1; + assert_eq!( + sum, self.claim, + "Verifier Abort: Prover's polynomial doesn't evaluate to claimed value" + ); + + let mut rng = thread_rng(); + let challenge = F::from(rng.gen::()); + + // This is the value the Verifier will check against in the next round + // new_claim = h_poly(challenge) as a univariate polynomial + // we are implementing univariate polynomial evaluation here, since we can't use existing + // [`Polynomial`] with variable size degree + let mut new_claim = F::ZERO; + for i in 0..h_poly.len() { + new_claim += h_poly[i] * challenge.pow(i); + } + self.claim = new_claim; + self.current_round += 1; + self.challenges_sent.push(challenge); + self._poly_received.push(h_poly); + + return challenge; + } + + fn verify_final_result(&self, oracle: impl Fn(&Vec, F) -> bool) { + assert!( + oracle(&self.challenges_sent, self.claim), + "Verifier Abort: Final value of polynomial claimed by the Prover is incorrect" + ); + } +} + +pub struct SumCheck { + pub prover: SumCheckProver, + pub verifier: SumCheckVerifier, + pub multi_var_poly: MultiVarPolynomial, +} +impl SumCheck { + fn new(poly: MultiVarPolynomial) -> Self { + let prover = SumCheckProver::new(poly.clone()); + let claimed_sum = prover.sum_poly(); + let verifier = SumCheckVerifier::new(claimed_sum, poly.degree.clone()); + Self { prover, verifier, multi_var_poly: poly } + } + + pub fn evaluation_oracle(&self, r: &Vec, claim: F) -> bool { + return self.multi_var_poly.evaluation(r) == claim; + } + + pub fn run_interactive_protocol(&mut self) { + for i in 0..self.multi_var_poly.num_var() { + assert_eq!(i, self.prover.current_round); + assert_eq!(i, self.verifier.current_round); + + let rnd_poly = self.prover.send_poly(); + let challenge = self.verifier.verify_internal_rounds(rnd_poly); + self.prover.reduce_poly(challenge); + } + let oracle = |r: &Vec, claim: F| self.evaluation_oracle(r, claim); + self.verifier.verify_final_result(oracle); + } +} + +#[cfg(test)] mod tests; diff --git a/src/sumcheck/tests.rs b/src/sumcheck/tests.rs new file mode 100644 index 0000000..5ef7dc8 --- /dev/null +++ b/src/sumcheck/tests.rs @@ -0,0 +1,36 @@ +use super::*; + +type F = PlutoBaseField; + +fn create_test_polynomial() -> MultiVarPolynomial { + // Create the polynomial: + // 3 x^2 y^2 z^2 + 2x^2 y + 5x^2 z^2 + 4yz + 6x + 1 + let coordinates = vec![ + vec![0, 0, 0], // Constant term + vec![1, 0, 0], // x term + vec![0, 1, 1], // yz term + vec![2, 0, 2], // x^2 z^2 term + vec![2, 1, 0], // x^2 y term + vec![2, 2, 2], // x^2 y^2 z^2 term + ]; + let coefficients = vec![ + F::from(1), // Constant term + F::from(6), // x term + F::from(4), // yz term + F::from(5), // x^2 z^2 term + F::from(2), // x^2 y term + F::from(3), // x^2 y^2 z^2 term + ]; + MultiVarPolynomial::from_coordinates(coordinates, coefficients).unwrap() +} + +#[test] +fn test_sumcheck_protocol() { + let poly = create_test_polynomial(); + // While summing over binary values for the variables remember a term is non-zero only if all + // the variables making it are 1. This way you can calculate the following: + let expected_sum = F::from(57); + let mut sumcheck = SumCheck::new(poly); + sumcheck.run_interactive_protocol(); + assert_eq!(sumcheck.verifier.result, expected_sum); +} From 9d44a4bdc0c291b44428218c2d5fe3ab55e559c3 Mon Sep 17 00:00:00 2001 From: goforashutosh Date: Sun, 11 Aug 2024 02:43:09 -0400 Subject: [PATCH 5/9] added testing and display --- src/sumcheck/mod.rs | 50 ++++++++++++++++++++++++++++++++++++------- src/sumcheck/tests.rs | 17 ++++++++++++++- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/sumcheck/mod.rs b/src/sumcheck/mod.rs index 454ce45..f527adf 100644 --- a/src/sumcheck/mod.rs +++ b/src/sumcheck/mod.rs @@ -1,9 +1,7 @@ use rand::thread_rng; use super::*; -use crate::{ - algebra::field::FiniteField, multi_var_poly::MultiVarPolynomial, polynomial::Polynomial, -}; +use crate::{algebra::field::FiniteField, multi_var_poly::MultiVarPolynomial}; pub struct SumCheckProver { pub multi_var_poly: MultiVarPolynomial, @@ -154,13 +152,14 @@ pub struct SumCheck { pub prover: SumCheckProver, pub verifier: SumCheckVerifier, pub multi_var_poly: MultiVarPolynomial, + pub verbose: bool, } impl SumCheck { - fn new(poly: MultiVarPolynomial) -> Self { + fn new(poly: MultiVarPolynomial, verbose: bool) -> Self { let prover = SumCheckProver::new(poly.clone()); let claimed_sum = prover.sum_poly(); let verifier = SumCheckVerifier::new(claimed_sum, poly.degree.clone()); - Self { prover, verifier, multi_var_poly: poly } + Self { prover, verifier, multi_var_poly: poly, verbose } } pub fn evaluation_oracle(&self, r: &Vec, claim: F) -> bool { @@ -168,16 +167,51 @@ impl SumCheck { } pub fn run_interactive_protocol(&mut self) { + if self.verbose { + println!("Starting Sum-Check Protocol"); + println!("Initial claim: {:?}", self.verifier.claim); + } for i in 0..self.multi_var_poly.num_var() { - assert_eq!(i, self.prover.current_round); - assert_eq!(i, self.verifier.current_round); - let rnd_poly = self.prover.send_poly(); + if self.verbose { + println!("Round {}", i + 1); + println!("P ----> V: {}", format_polynomial(&rnd_poly)); + } + let challenge = self.verifier.verify_internal_rounds(rnd_poly); + if self.verbose { + println!("V ----> P: r_{} = {:?}", i + 1, challenge); + } self.prover.reduce_poly(challenge); } + if self.verbose { + println!("Final verification:"); + println!("Challenges: {:?}", self.verifier.challenges_sent); + println!("Claimed value at this point: {:?}", self.verifier.claim); + } let oracle = |r: &Vec, claim: F| self.evaluation_oracle(r, claim); self.verifier.verify_final_result(oracle); + if self.verbose { + println!("Protocol completed successfully"); + } + } +} + +fn format_polynomial(coeffs: &[F]) -> String { + let mut terms: Vec = Vec::new(); + for (i, coeff) in coeffs.iter().enumerate() { + if *coeff != F::ZERO { + match i { + 0 => terms.push(format!("{:?}", coeff)), + 1 => terms.push(format!("{:?} X", coeff)), + _ => terms.push(format!("{:?} X^{}", coeff, i)), + } + } + } + if terms.is_empty() { + "0".to_string() + } else { + terms.join(" + ") } } diff --git a/src/sumcheck/tests.rs b/src/sumcheck/tests.rs index 5ef7dc8..0ecfdfc 100644 --- a/src/sumcheck/tests.rs +++ b/src/sumcheck/tests.rs @@ -30,7 +30,22 @@ fn test_sumcheck_protocol() { // While summing over binary values for the variables remember a term is non-zero only if all // the variables making it are 1. This way you can calculate the following: let expected_sum = F::from(57); - let mut sumcheck = SumCheck::new(poly); + let mut sumcheck = SumCheck::new(poly, true); sumcheck.run_interactive_protocol(); assert_eq!(sumcheck.verifier.result, expected_sum); } + +#[test] +#[should_panic] +fn test_sumcheck_protocol_incorrect() { + let poly = create_test_polynomial(); + let incorrect_sum = F::from(58); // Intentionally incorrect sum + let mut sumcheck = SumCheck::new(poly, true); + + // Override the verifier's initial claim with the incorrect sum + sumcheck.verifier.claim = incorrect_sum; + sumcheck.verifier.result = incorrect_sum; + + // This should panic due to the incorrect sum + sumcheck.run_interactive_protocol(); +} From b89fc2b7b63615076666c41eb6a3341d9e13c8e6 Mon Sep 17 00:00:00 2001 From: goforashutosh Date: Sun, 11 Aug 2024 11:07:34 -0400 Subject: [PATCH 6/9] add comments to mult_var_poly --- src/multi_var_poly/arithmetic.rs | 25 ++++++++- src/multi_var_poly/mod.rs | 92 +++++++++++++++++++++++++++----- 2 files changed, 104 insertions(+), 13 deletions(-) diff --git a/src/multi_var_poly/arithmetic.rs b/src/multi_var_poly/arithmetic.rs index 25a5576..fb7e48a 100644 --- a/src/multi_var_poly/arithmetic.rs +++ b/src/multi_var_poly/arithmetic.rs @@ -1,3 +1,18 @@ +//! Arithmetic operations for multivariate polynomials. +//! The operations are implemented for [`MultiVarPolynomial`] in the monomial basis. +//! +//! Note: Operations are restricted to polynomials with the same degree structure. +//! +//! ## Implementations +//! - [`Add`] for adding two multivariate polynomials. +//! - [`AddAssign`] for adding two multivariate polynomials in place. +//! - [`Sum`] for summing a collection of multivariate polynomials. +//! - [`Sub`] for subtracting two multivariate polynomials. +//! - [`SubAssign`] for subtracting two multivariate polynomials in place. +//! - [`Neg`] for negating a multivariate polynomial. +//! - [`Mul`] for scalar multiplication of a multivariate polynomial. +//! - [`MulAssign`] for scalar multiplication of a multivariate polynomial in place. + use std::{ iter::Sum, ops::{Add, AddAssign, Neg, Sub, SubAssign}, @@ -8,6 +23,7 @@ use super::*; impl Add for MultiVarPolynomial { type Output = Self; + /// Implements addition of two multivariate polynomials by adding their coefficients. fn add(self, rhs: Self) -> Self::Output { assert_eq!(self.degree, rhs.degree, "Polynomials must have the same degree structure"); @@ -19,6 +35,7 @@ impl Add for MultiVarPolynomial { } impl AddAssign for MultiVarPolynomial { + /// Implements in-place addition of two multivariate polynomials by adding their coefficients. fn add_assign(&mut self, rhs: Self) { assert_eq!(self.degree, rhs.degree, "Polynomials must have the same degree structure"); @@ -29,6 +46,7 @@ impl AddAssign for MultiVarPolynomial { } impl Sum for MultiVarPolynomial { + /// Implements summing a collection of multivariate polynomials. fn sum>(iter: I) -> Self { iter.reduce(|x, y| x + y).expect("Cannot sum an empty iterator of MultiVarPolynomials") } @@ -37,6 +55,7 @@ impl Sum for MultiVarPolynomial { impl Sub for MultiVarPolynomial { type Output = Self; + /// Implements subtraction of two multivariate polynomials by subtracting their coefficients. fn sub(self, rhs: Self) -> Self::Output { assert_eq!(self.degree, rhs.degree, "Polynomials must have the same degree structure"); @@ -48,6 +67,8 @@ impl Sub for MultiVarPolynomial { } impl SubAssign for MultiVarPolynomial { + /// Implements in-place subtraction of two multivariate polynomials by subtracting their + /// coefficients. fn sub_assign(&mut self, rhs: Self) { assert_eq!(self.degree, rhs.degree, "Polynomials must have the same degree structure"); @@ -60,6 +81,7 @@ impl SubAssign for MultiVarPolynomial { impl Neg for MultiVarPolynomial { type Output = Self; + /// Implements negation of a multivariate polynomial by negating its coefficients. fn neg(self) -> Self::Output { Self { degree: self.degree, @@ -71,11 +93,12 @@ impl Neg for MultiVarPolynomial { impl Mul for MultiVarPolynomial { type Output = Self; + /// Implements scalar multiplication of a multivariate polynomial. fn mul(self, rhs: F) -> Self::Output { self.scalar_mul(rhs) } } -// Implement MulAssign to support *= impl MulAssign for MultiVarPolynomial { + /// Implements in-place scalar multiplication of a multivariate polynomial. fn mul_assign(&mut self, rhs: F) { for coeff in &mut self.coefficients { *coeff *= rhs; diff --git a/src/multi_var_poly/mod.rs b/src/multi_var_poly/mod.rs index c3abcda..c9c84d5 100644 --- a/src/multi_var_poly/mod.rs +++ b/src/multi_var_poly/mod.rs @@ -1,14 +1,56 @@ +//! This module contains the implementation of multivariate polynomials in the monomial basis. +//! +//! ## Overview +//! A multivariate polynomial is a mathematical expression that consists of multiple variables and +//! coefficients. The variables are raised to non-negative integer powers and multiplied by the +//! coefficients. For example, the polynomial $f(x,y,z) = 1 + 2x + 3y + 4z + 5xy + 6yz + 7xz + 8xyz$ +//! is a multivariate polynomial with three variables. +//! +//! - [`MultiVarPolynomial`] struct represents a multivariate polynomial in the monomial basis. +//! - Includes arithmetic operations such as addition, subtraction, and scalar multiplication in the +//! [`arithmetic`] module. +//! - Provides methods for evaluation and summing over boolean hypercube. + use super::*; use crate::algebra::field::FiniteField; -// L is the number of variables +/// A multivariate polynomial of arbitrary degree. +/// The coefficients are stored in a specific order based on the degree vector. +/// The ordering follows a lexicographic pattern on the exponents of the variables, +/// but in reverse order of the variables (from last to first in the degree vector). +/// For a polynomial with variables x, y, z and degrees dx, dy, dz respectively: +/// +/// - The first dz+1 coefficients correspond to increasing powers of z (z^0 to z^dz). +/// - The next dz+1 coefficients correspond to y * (increasing powers of z). +/// - This pattern repeats for each power of y up to dy. +/// - Once all combinations of y and z are exhausted, the power of x increases. +/// +/// For example, with degrees [1, 1, 1] (for x, y, z respectively), the coefficient order is: +/// [1, z, y, yz, x, xz, xy, xyz] +/// +/// The total number of coefficients is (dx+1) * (dy+1) * (dz+1). +/// +/// This ordering allows for efficient indexing and evaluation of the polynomial, +/// particularly when iterating over the boolean hypercube or performing other operations. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MultiVarPolynomial { + /// Vector representing the maximum degree for each variable. pub degree: Vec, + /// Coefficients of the polynomial in the monomial basis. + // The ordering follows a lexicographic pattern on the exponents of the variables, + /// but in reverse order of the variables (from last to first in the degree vector). + /// + /// See comment for [`MultiVarPolynomial`] for more details pub coefficients: Vec, } -// generates the cartesian product (0..l[0]) X (0..l[1]) X ... X (0..l[n-1]) +/// Generates the cartesian product `(0..l[0]) X (0..l[1]) X ... X (0..l[n-1])` +/// +/// Ordering: the first coordinate is the "most significant" one. +/// +/// Example: for input `l = [2,3]`, this outputs `[[0,0], [0,1], [0,2], [1,0], [1,1], [1,2]]` +/// +/// This is used internally for polynomial operations. fn generate_cartesian_product(l: Vec) -> Vec> { let mut result = vec![vec![]]; @@ -28,6 +70,17 @@ fn generate_cartesian_product(l: Vec) -> Vec> { } impl MultiVarPolynomial { + /// Create a new multivariate polynomial. + /// + /// ## Arguments: + /// - `degree`: A vector of usize representing the maximum degree for each variable. + /// - `coefficients`: A vector of field elements representing the coefficients of the polynomial. + /// The ordering should be the same as the one for the coefficients in the struct. See comments + /// for [`MultiVarPolynomial`]. + /// + /// ## Returns: + /// - A Result containing the new MultiVarPolynomial or an error message if the number of + /// coefficients doesn't match the expected count based on the degree vector. pub fn new(degree: Vec, coefficients: Vec) -> Result { // Calculate the expected number of coefficients let expected_coeff_count = degree.iter().map(|&d| d + 1).fold(1, Mul::mul); @@ -46,15 +99,15 @@ impl MultiVarPolynomial { /// Constructs a multivariate polynomial from coordinates and coefficients. /// - /// # Arguments - /// * `coordinates` - A vector of vectors, where each inner vector represents the degrees of a + /// ## Arguments: + /// - `coordinates`: A vector of vectors, where each inner vector represents the degrees of a /// term. - /// * `coefficients` - A vector of coefficients corresponding to each term. + /// - `coefficients`: A vector of coefficients corresponding to the `coordinates` above. /// - /// # Returns - /// * `Result` - The constructed polynomial or an error message. + /// ## Returns: + /// - A Result containing the constructed polynomial or an error message. /// - /// Doesn't take into account cases where you try to fill the same coefficient twice + /// Note: This method doesn't handle cases where the same coefficient is filled twice. pub fn from_coordinates( coordinates: Vec>, coefficients: Vec, @@ -78,20 +131,23 @@ impl MultiVarPolynomial { let total_terms: usize = degree.iter().map(|&d| d + 1).product(); let mut full_coefficients = vec![F::ZERO; total_terms]; - // println!("degree = {:?}", degree); - for (coord, &coeff) in coordinates.iter().zip(coefficients.iter()) { let index = coord.iter().enumerate().fold(0, |acc, (i, &d)| { acc + d * degree.get(i + 1..).unwrap_or(&[]).iter().map(|&d| d + 1).product::() }); - // println!("Accessing index={}", index); - // println!("for coord={:?}", coord); full_coefficients[index] = coeff; } Self::new(degree, full_coefficients) } + /// Evaluates the polynomial at a given point. + /// + /// ## Arguments: + /// - `r`: A vector of field elements representing the point at which to evaluate the polynomial. + /// + /// ## Returns: + /// - The result of evaluating the polynomial at the given point. pub fn evaluation(&self, r: &Vec) -> F { assert_eq!(r.len(), self.num_var()); let degree_plus_1 = self.degree.iter().map(|x| x + 1).collect(); @@ -110,8 +166,13 @@ impl MultiVarPolynomial { return result; } + /// Returns the number of variables in the polynomial. pub fn num_var(&self) -> usize { self.degree.len() } + /// Computes the sum of the polynomial over the boolean hypercube. + /// + /// ## Returns: + /// - The sum of the polynomial evaluated at all points in the boolean hypercube. pub fn sum_over_bool_hypercube(&self) -> F { // let vec_of_2 = (0..self.num_var()).map(|_| 2 as usize).collect(); let vec_of_2 = vec![2; self.num_var()]; @@ -124,6 +185,13 @@ impl MultiVarPolynomial { return sum; } + /// Multiplies the polynomial by a scalar. + /// + /// ## Arguments: + /// - `scalar`: The field element to multiply the polynomial by. + /// + /// ## Returns: + /// - A new MultiVarPolynomial that is the result of the scalar multiplication. pub fn scalar_mul(&self, scalar: F) -> Self { Self { degree: self.degree.clone(), From 4b59fdafb437aa98a5a97cbd59c7ce329433c103 Mon Sep 17 00:00:00 2001 From: goforashutosh Date: Sun, 11 Aug 2024 11:35:58 -0400 Subject: [PATCH 7/9] added comments to sum-check --- src/sumcheck/mod.rs | 101 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 4 deletions(-) diff --git a/src/sumcheck/mod.rs b/src/sumcheck/mod.rs index f527adf..28f7264 100644 --- a/src/sumcheck/mod.rs +++ b/src/sumcheck/mod.rs @@ -1,22 +1,53 @@ +//! This module implements the sum-check protocol for multivariate polynomials over finite fields. +//! +//! ## Overview +//! The sum-check protocol is an interactive proof system where a prover convinces a verifier +//! of the sum of a multivariate polynomial over a boolean hypercube. The protocol proceeds +//! in rounds, reducing the number of variables in each round. +//! +//! - [`SumCheckProver`] represents the prover in the protocol. +//! - [`SumCheckVerifier`] represents the verifier in the protocol. +//! - [`SumCheck`] encapsulates both prover and verifier, managing the entire protocol. + use rand::thread_rng; use super::*; use crate::{algebra::field::FiniteField, multi_var_poly::MultiVarPolynomial}; +/// Represents the prover in the sum-check protocol. pub struct SumCheckProver { + /// The multivariate polynomial being summed over. pub multi_var_poly: MultiVarPolynomial, + /// Tracks the current round of the protocol. pub current_round: usize, + /// The total number of rounds in the protocol. pub total_rounds: usize, } impl SumCheckProver { + /// Creates a new SumCheckProver instance. + /// + /// ## Arguments: + /// - `poly`: The multivariate polynomial to be used in the protocol. + /// + /// ## Returns: + /// - A new `SumCheckProver` instance. pub fn new(poly: MultiVarPolynomial) -> Self { let tot_rnds = poly.num_var(); SumCheckProver { multi_var_poly: poly, current_round: 0, total_rounds: tot_rnds } } + /// Computes the sum of the polynomial over the boolean hypercube. + /// + /// ## Returns: + /// - The sum of the polynomial over the boolean hypercube. pub fn sum_poly(&self) -> F { return self.multi_var_poly.sum_over_bool_hypercube(); } + /// Generates the univariate polynomial to be sent to the Verifier in the current round of the + /// protocol. + /// + /// ## Returns: + /// - A vector of field elements representing the coefficients of the univariate polynomial. pub fn send_poly(&self) -> Vec { if self.multi_var_poly.num_var() > 1 { let tot_deg_ex_first: usize = @@ -49,6 +80,12 @@ impl SumCheckProver { } } + /// Reduces the multivariate polynomial based on the verifier's challenge, that is, sets the + /// variable in the first position equal to the challenge. Computes coefficients for the rest of + /// the variables based on this and changes the `multi_var_poly` stored accordingly. + /// + /// ## Arguments: + /// - `r`: The challenge field element from the verifier. pub fn reduce_poly(&mut self, r: F) { if self.multi_var_poly.num_var() > 1 { let tot_deg_ex_first: usize = @@ -82,16 +119,31 @@ impl SumCheckProver { } } +/// Represents the verifier in the sum-check protocol. pub struct SumCheckVerifier { + /// Tracks the current round of the sum-check protocol pub current_round: usize, + /// The total number of rounds in the protocol. pub total_rounds: usize, + /// Stores the degrees of the variables in the multivariate polynomial being summed over. pub degree: Vec, + /// Stores the result claimed by the Prover for the sum over the multivariate polynomial. pub result: F, + /// Tracks the value of polynomial sum claimed by the Prover in the present round. It is public + /// only for debugging purposes. pub claim: F, + /// Vector storing challenges sent by the Verifier so far. pub challenges_sent: Vec, - pub _poly_received: Vec>, } impl SumCheckVerifier { + /// Creates a new `SumCheckVerifier` instance. + /// + /// ## Arguments: + /// - `c`: The claimed sum of the polynomial. + /// - `deg`: A vector representing the degrees of each variable in the polynomial. + /// + /// ## Returns: + /// - A new `SumCheckVerifier` instance. fn new(c: F, deg: Vec) -> Self { Self { current_round: 0, @@ -100,10 +152,16 @@ impl SumCheckVerifier { result: c, claim: c, challenges_sent: vec![], - _poly_received: vec![], } } + /// Verifies the prover's polynomial for the current round and generates a challenge. + /// + /// ## Arguments: + /// - `h_poly`: The univariate polynomial sent by the prover for this round. + /// + /// ## Returns: + /// - The challenge field element for the next round. fn verify_internal_rounds(&mut self, h_poly: Vec) -> F { assert_eq!( h_poly.len(), @@ -135,11 +193,17 @@ impl SumCheckVerifier { self.claim = new_claim; self.current_round += 1; self.challenges_sent.push(challenge); - self._poly_received.push(h_poly); return challenge; } + /// Verifies the final result of the protocol using the provided oracle. + /// + /// ## Arguments: + /// - `oracle`: A function that checks if the claimed value of the evaluation of the multivariate + /// polynomial at the point given by `&challenges_sent:&Vec` is correct or not. Using a + /// oracle like this should allow us to use both simple evaluation by the Verifier as well as a + /// commitment proof. fn verify_final_result(&self, oracle: impl Fn(&Vec, F) -> bool) { assert!( oracle(&self.challenges_sent, self.claim), @@ -148,13 +212,26 @@ impl SumCheckVerifier { } } +/// Represents the entire sum-check protocol, including both prover and verifier. pub struct SumCheck { + // The sum-check Prover object pub prover: SumCheckProver, + // The sum-check Verifier object pub verifier: SumCheckVerifier, + // The multivariate polynomial being summed over pub multi_var_poly: MultiVarPolynomial, + // A flag which allows which prints the entire protocol if set to `true` pub verbose: bool, } impl SumCheck { + /// Creates a new SumCheck instance. + /// + /// ## Arguments: + /// - `poly`: The multivariate polynomial to be used in the protocol. + /// - `verbose`: A boolean flag indicating whether to output detailed protocol steps. + /// + /// ## Returns: + /// - A new `SumCheck` instance. fn new(poly: MultiVarPolynomial, verbose: bool) -> Self { let prover = SumCheckProver::new(poly.clone()); let claimed_sum = prover.sum_poly(); @@ -162,14 +239,23 @@ impl SumCheck { Self { prover, verifier, multi_var_poly: poly, verbose } } + /// Evaluates the multivariate polynomial at a given point. + /// + /// ## Arguments: + /// - `r`: A vector of field elements representing the point of evaluation. + /// - `claim`: The claimed value of the polynomial at the given point. + /// + /// ## Returns: + /// - A boolean indicating whether the evaluation matches the claim. pub fn evaluation_oracle(&self, r: &Vec, claim: F) -> bool { return self.multi_var_poly.evaluation(r) == claim; } + /// Runs the interactive sum-check protocol between the prover and verifier. pub fn run_interactive_protocol(&mut self) { if self.verbose { println!("Starting Sum-Check Protocol"); - println!("Initial claim: {:?}", self.verifier.claim); + println!("Initial result claimed: {:?}", self.verifier.result); } for i in 0..self.multi_var_poly.num_var() { let rnd_poly = self.prover.send_poly(); @@ -197,6 +283,13 @@ impl SumCheck { } } +/// Helper function to format a polynomial as a string. +/// +/// ## Arguments: +/// - `coeffs`: A slice of field elements representing the coefficients of the polynomial. +/// +/// ## Returns: +/// - A string representation of the polynomial. fn format_polynomial(coeffs: &[F]) -> String { let mut terms: Vec = Vec::new(); for (i, coeff) in coeffs.iter().enumerate() { From 14d90f64c85972d8f1bf468211bc2b61780fbd1e Mon Sep 17 00:00:00 2001 From: goforashutosh Date: Sun, 11 Aug 2024 11:47:17 -0400 Subject: [PATCH 8/9] added example --- examples/sumcheck_ex.rs | 55 +++++++++++++++++++++++++++++++++++++++++ src/sumcheck/mod.rs | 16 ++++++------ src/sumcheck/tests.rs | 4 +-- 3 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 examples/sumcheck_ex.rs diff --git a/examples/sumcheck_ex.rs b/examples/sumcheck_ex.rs new file mode 100644 index 0000000..3429919 --- /dev/null +++ b/examples/sumcheck_ex.rs @@ -0,0 +1,55 @@ +// File: examples/sum_check_demo.rs + +use ronkathon::{ + algebra::field::prime::PlutoBaseField, multi_var_poly::MultiVarPolynomial, sumcheck::SumCheck, +}; + +type F = PlutoBaseField; + +fn create_demo_polynomial() -> MultiVarPolynomial { + // Create the polynomial: + // 3 x^2 y^2 z^2 + 2x^2 y + 5x^2 z^2 + 4yz + 6x + 1 + let coordinates = vec![ + vec![0, 0, 0], // Constant term + vec![1, 0, 0], // x term + vec![0, 1, 1], // yz term + vec![2, 0, 2], // x^2 z^2 term + vec![2, 1, 0], // x^2 y term + vec![2, 2, 2], // x^2 y^2 z^2 term + ]; + let coefficients = vec![ + F::from(1), // Constant term + F::from(6), // x term + F::from(4), // yz term + F::from(5), // x^2 z^2 term + F::from(2), // x^2 y term + F::from(3), // x^2 y^2 z^2 term + ]; + MultiVarPolynomial::from_coordinates(coordinates, coefficients).unwrap() +} + +fn main() { + println!("Sum-Check Protocol Demonstration"); + println!("================================"); + + let poly = create_demo_polynomial(); + println!("Created multivariate polynomial:"); + println!("3 x^2 y^2 z^2 + 2x^2 y + 5x^2 z^2 + 4yz + 6x + 1"); + + let expected_sum = F::from(57); + println!("\nExpected sum over boolean hypercube: {:?}", expected_sum); + + let mut sumcheck = SumCheck::new(poly, true); + println!("\nRunning interactive sum-check protocol:"); + sumcheck.run_interactive_protocol(); + + println!("\nVerification result:"); + if sumcheck.verifier.result == expected_sum { + println!("Sum-check protocol succeeded! The sum is verified to be {:?}", expected_sum); + } else { + println!( + "Sum-check protocol failed. Expected {:?}, but got {:?}", + expected_sum, sumcheck.verifier.result + ); + } +} diff --git a/src/sumcheck/mod.rs b/src/sumcheck/mod.rs index 28f7264..7839ef3 100644 --- a/src/sumcheck/mod.rs +++ b/src/sumcheck/mod.rs @@ -144,7 +144,7 @@ impl SumCheckVerifier { /// /// ## Returns: /// - A new `SumCheckVerifier` instance. - fn new(c: F, deg: Vec) -> Self { + pub fn new(c: F, deg: Vec) -> Self { Self { current_round: 0, total_rounds: deg.len(), @@ -162,7 +162,7 @@ impl SumCheckVerifier { /// /// ## Returns: /// - The challenge field element for the next round. - fn verify_internal_rounds(&mut self, h_poly: Vec) -> F { + pub fn verify_internal_rounds(&mut self, h_poly: Vec) -> F { assert_eq!( h_poly.len(), self.degree[self.current_round] + 1, @@ -204,7 +204,7 @@ impl SumCheckVerifier { /// polynomial at the point given by `&challenges_sent:&Vec` is correct or not. Using a /// oracle like this should allow us to use both simple evaluation by the Verifier as well as a /// commitment proof. - fn verify_final_result(&self, oracle: impl Fn(&Vec, F) -> bool) { + pub fn verify_final_result(&self, oracle: impl Fn(&Vec, F) -> bool) { assert!( oracle(&self.challenges_sent, self.claim), "Verifier Abort: Final value of polynomial claimed by the Prover is incorrect" @@ -214,13 +214,13 @@ impl SumCheckVerifier { /// Represents the entire sum-check protocol, including both prover and verifier. pub struct SumCheck { - // The sum-check Prover object + /// The sum-check Prover object pub prover: SumCheckProver, - // The sum-check Verifier object + /// The sum-check Verifier object pub verifier: SumCheckVerifier, - // The multivariate polynomial being summed over + /// The multivariate polynomial being summed over pub multi_var_poly: MultiVarPolynomial, - // A flag which allows which prints the entire protocol if set to `true` + /// A flag which allows which prints the entire protocol if set to `true` pub verbose: bool, } impl SumCheck { @@ -232,7 +232,7 @@ impl SumCheck { /// /// ## Returns: /// - A new `SumCheck` instance. - fn new(poly: MultiVarPolynomial, verbose: bool) -> Self { + pub fn new(poly: MultiVarPolynomial, verbose: bool) -> Self { let prover = SumCheckProver::new(poly.clone()); let claimed_sum = prover.sum_poly(); let verifier = SumCheckVerifier::new(claimed_sum, poly.degree.clone()); diff --git a/src/sumcheck/tests.rs b/src/sumcheck/tests.rs index 0ecfdfc..f5040d3 100644 --- a/src/sumcheck/tests.rs +++ b/src/sumcheck/tests.rs @@ -30,7 +30,7 @@ fn test_sumcheck_protocol() { // While summing over binary values for the variables remember a term is non-zero only if all // the variables making it are 1. This way you can calculate the following: let expected_sum = F::from(57); - let mut sumcheck = SumCheck::new(poly, true); + let mut sumcheck = SumCheck::new(poly, false); sumcheck.run_interactive_protocol(); assert_eq!(sumcheck.verifier.result, expected_sum); } @@ -40,7 +40,7 @@ fn test_sumcheck_protocol() { fn test_sumcheck_protocol_incorrect() { let poly = create_test_polynomial(); let incorrect_sum = F::from(58); // Intentionally incorrect sum - let mut sumcheck = SumCheck::new(poly, true); + let mut sumcheck = SumCheck::new(poly, false); // Override the verifier's initial claim with the incorrect sum sumcheck.verifier.claim = incorrect_sum; From 7ac3bcdb0b37e6d6f56941d9b3b0f7f1c62b8adb Mon Sep 17 00:00:00 2001 From: goforashutosh Date: Sun, 11 Aug 2024 12:01:04 -0400 Subject: [PATCH 9/9] added readme --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c7d556..2103831 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,26 @@ ## Overview + Ronkathon is a rust implementation of a collection of cryptographic primitives. It is inspired by the common python plonkathon repository, and plonk-by-hand. We use the same curve and field as plonk-by-hand (not secure), and are working towards building everything from scratch to understand everything from first principles. +## Multivariate polynomials and sum-check + +This project implements the sum-check protocol for multivariate polynomials over finite fields. The sum-check protocol is an interactive proof system where a prover convinces a verifier of the sum of a multivariate polynomial over a boolean hypercube. This implementation includes: + +- A `MultiVarPolynomial` struct which represents a multivariate polynomial +- A `SumCheckProver` for generating proofs +- A `SumCheckVerifier` for verifying proofs +- A `SumCheck` struct that encapsulates the entire protocol. + +Use + +`cargo run --example sumcheck_ex` + +to run example code. + ## Primitives + - [Finite Group](src/field/group.rs) - [Fields and Their Extensions](src/field/README.md) - [Binary Fields](src/field/binary_towers/README.md) @@ -28,19 +45,23 @@ Ronkathon is a rust implementation of a collection of cryptographic primitives. - [DSL](src/compiler/README.md) ### Signatures + - [Tiny ECDSA](src/ecdsa.rs) ### Encryption + - [RSA](src/encryption/asymmetric/rsa/README.md) - [DES](src/encryption/symmetric/des/README.md) - [AES](src/encryption/symmetric/aes/README.md) - [ChaCha](src/encryption/symmetric/chacha/README.md) ### Hash + - [Sha256 Hash](src/hashes/README.md) - [Poseidon Hash](src/hashes/poseidon/README.md) ## In Progress + - [ ] Edwards curve Signatures (EdDSA) ## Resources @@ -48,26 +69,32 @@ Ronkathon is a rust implementation of a collection of cryptographic primitives. We have found the following resources helpful in understanding the foundational mathematics behind this implementation. After going through these, you should be able to understand the codebase ### Theoretic Resources + - [Plonk by Hand P1](https://research.metastate.dev/plonk-by-hand-part-1/) - [Plonk by Hand P2](https://research.metastate.dev/plonk-by-hand-part-2-the-proof/) + ### Code Refrences + - [Plonkathon](https://github.com/0xPARC/plonkathon/blob/main/README.md) - [Plonky3](https://github.com/Plonky3/Plonky3) - [py_pairing](https://github.com/ethereum/py_pairing/tree/master) - [arkworks](https://github.com/arkworks-rs) - ## Math + To see computations used in the background, go to the `math/` directory. From there, you can run the `.sage` files in a SageMath environment. In particular, the `math/field.sage` computes roots of unity in the `PlutoField` which is of size 101. To install sage on your machine, follow the instructions [here](https://doc.sagemath.org/html/en/installation/index.html). If you are on a Mac, you can install it via homebrew with `brew install --cask sage`. ## License + Licensed under your option of either: + - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) ## Contribution + Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.