From 29ca8f1007baf1b890d7f378f0b412bb49742bf9 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Wed, 9 Oct 2024 21:42:26 +0200 Subject: [PATCH 1/5] Arrabiata/main: add more future tasks to remind --- arrabbiata/src/main.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arrabbiata/src/main.rs b/arrabbiata/src/main.rs index c242401495..150d56c4d5 100644 --- a/arrabbiata/src/main.rs +++ b/arrabbiata/src/main.rs @@ -101,6 +101,12 @@ pub fn main() { // FIXME: // Compute the accumulator for the permutation argument + // FIXME: + // Commit to the accumulator and absorb the commitment + + // FIXME: + // Coin challenge α for combining the constraints + // FIXME: // Compute the cross-terms From 56552900c6c16bd832b992cdbf07075be24d6e52 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 15 Oct 2024 10:51:25 +0200 Subject: [PATCH 2/5] Arrabiata/Column: implement column to usize --- arrabbiata/src/columns.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arrabbiata/src/columns.rs b/arrabbiata/src/columns.rs index 3efc6e8616..7afa3e3643 100644 --- a/arrabbiata/src/columns.rs +++ b/arrabbiata/src/columns.rs @@ -36,6 +36,22 @@ pub enum Column { X(usize), } +/// Convert a column to a usize. This is used by the library [mvpoly] when we +/// need to compute the cross-terms. +/// For now, we only support the conversion of the private inputs, i.e. the `X` +/// variant. +/// Also, for now, the [mvpoly] library expects the columns to be mapped to a +/// prime number as the prime representation is used under the hood. This must +/// be changed. +impl From for usize { + fn from(val: Column) -> usize { + match val { + Column::X(i) => i, + _ => unimplemented!("Only the private inputs are supported for now"), + } + } +} + pub struct Challenges { /// Challenge used to aggregate the constraints pub alpha: F, From 0083c30e1407c5d71f9a09a6e743e34643c9f17d Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 15 Oct 2024 16:47:11 +0200 Subject: [PATCH 3/5] Arrabiata/Column: support conversion of public inputs into usize We do suppose evaluations of the public inputs will be given after the witness. --- arrabbiata/src/columns.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/arrabbiata/src/columns.rs b/arrabbiata/src/columns.rs index 7afa3e3643..e9c078c5db 100644 --- a/arrabbiata/src/columns.rs +++ b/arrabbiata/src/columns.rs @@ -8,6 +8,8 @@ use std::{ }; use strum_macros::{EnumCount as EnumCountMacro, EnumIter}; +use crate::NUMBER_OF_COLUMNS; + /// This enum represents the different gadgets that can be used in the circuit. /// The selectors are defined at setup time, can take only the values `0` or /// `1` and are public. @@ -38,16 +40,21 @@ pub enum Column { /// Convert a column to a usize. This is used by the library [mvpoly] when we /// need to compute the cross-terms. -/// For now, we only support the conversion of the private inputs, i.e. the `X` -/// variant. -/// Also, for now, the [mvpoly] library expects the columns to be mapped to a -/// prime number as the prime representation is used under the hood. This must -/// be changed. +/// For now, only the private inputs and the public inputs are converted, +/// because there might not need to treat the selectors in the polynomial while +/// computing the cross-terms (FIXME: check this later, but pretty sure it's the +/// case). +/// +/// Also, the [mvpoly::monomials] implementation of the trait [mvpoly::MVPoly] +/// will be used, and the mapping here is consistent with the one expected by +/// this implementation, i.e. we simply map to an increasing number starting at +/// 0, without any gap. impl From for usize { fn from(val: Column) -> usize { match val { Column::X(i) => i, - _ => unimplemented!("Only the private inputs are supported for now"), + Column::PublicInput(i) => NUMBER_OF_COLUMNS + i, + Column::Selector(_) => unimplemented!("Selectors are not supported. This method is supposed to be called only to compute the cross-term and an optimisation is in progress to avoid the inclusion of the selectors in the multi-variate polynomial."), } } } From 2d2265cad121efff053f160aed226bb17701ba31 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Mon, 16 Dec 2024 19:51:50 +0100 Subject: [PATCH 4/5] mvpoly: compute cross terms of a scaled polynomial --- mvpoly/src/lib.rs | 39 +++++++++++++++++ mvpoly/src/monomials.rs | 46 +++++++++++++++++--- mvpoly/src/prime.rs | 12 +++++ mvpoly/tests/monomials.rs | 92 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 6 deletions(-) diff --git a/mvpoly/src/lib.rs b/mvpoly/src/lib.rs index 6e8d35b7bf..87d0532862 100644 --- a/mvpoly/src/lib.rs +++ b/mvpoly/src/lib.rs @@ -254,6 +254,45 @@ pub trait MVPoly: u2: F, ) -> HashMap; + /// Compute the cross-terms of the given polynomial, scaled by the given + /// scalar. + /// + /// More explicitly, given a polynomial `P(X1, ..., Xn)` and a scalar α, the + /// method computes the the cross-terms of the polynomial `Q(X1, ..., Xn, α) + /// = α * P(X1, ..., Xn)`. For this reason, the method takes as input the + /// two different scalars `scalar1` and `scalar2` as we are considering the + /// scaling factor as a variable. + /// + /// This method is particularly useful when you need to compute a + /// (possibly random) combinaison of polynomials `P1(X1, ..., Xn), ..., + /// Pm(X1, ..., Xn)`, like when computing a quotient polynomial in the PlonK + /// PIOP, as the result is the sum of individual "scaled" polynomials: + /// ```text + /// Q(X_1, ..., X_n, α_1, ..., α_m) = + /// α_1 P1(X_1, ..., X_n) + + /// ... + /// α_m Pm(X_1, ..., X_n) + + /// ``` + /// + /// The polynomial must not necessarily be homogeneous. For this reason, the + /// values `u1` and `u2` represents the extra variable that is used to make + /// the polynomial homogeneous. + /// + /// The homogeneous degree is supposed to be the one defined by the type of + /// the polynomial `P`, i.e. `D`. + /// + /// The output is a map of `D` values that represents the cross-terms + /// for each power of `r`. + fn compute_cross_terms_scaled( + &self, + eval1: &[F; N], + eval2: &[F; N], + u1: F, + u2: F, + scalar1: F, + scalar2: F, + ) -> HashMap; + /// Modify the monomial in the polynomial to the new value `coeff`. fn modify_monomial(&mut self, exponents: [usize; N], coeff: F); diff --git a/mvpoly/src/monomials.rs b/mvpoly/src/monomials.rs index 275d322a5a..67d3127403 100644 --- a/mvpoly/src/monomials.rs +++ b/mvpoly/src/monomials.rs @@ -1,3 +1,8 @@ +use crate::{ + prime, + utils::{compute_indices_nested_loop, naive_prime_factors, PrimeNumberGenerator}, + MVPoly, +}; use ark_ff::{One, PrimeField, Zero}; use kimchi::circuits::{expr::Variable, gate::CurrOrNext}; use num_integer::binomial; @@ -8,12 +13,6 @@ use std::{ ops::{Add, Mul, Neg, Sub}, }; -use crate::{ - prime, - utils::{compute_indices_nested_loop, naive_prime_factors, PrimeNumberGenerator}, - MVPoly, -}; - /// Represents a multivariate polynomial in `N` variables with coefficients in /// `F`. The polynomial is represented as a sparse polynomial, where each /// monomial is represented by a vector of `N` exponents. @@ -472,6 +471,41 @@ impl MVPoly for Sparse HashMap { + assert!( + D >= 2, + "The degree of the polynomial must be greater than 2" + ); + let cross_terms = self.compute_cross_terms(eval1, eval2, u1, u2); + + let mut res: HashMap = HashMap::new(); + cross_terms.iter().for_each(|(power_r, coeff)| { + res.insert(*power_r, *coeff * scalar1); + }); + cross_terms.iter().for_each(|(power_r, coeff)| { + res.entry(*power_r + 1) + .and_modify(|e| *e += *coeff * scalar2) + .or_insert(*coeff * scalar2); + }); + let eval1_hom = self.homogeneous_eval(eval1, u1); + res.entry(1) + .and_modify(|e| *e += eval1_hom * scalar2) + .or_insert(eval1_hom * scalar2); + let eval2_hom = self.homogeneous_eval(eval2, u2); + res.entry(D) + .and_modify(|e| *e += eval2_hom * scalar1) + .or_insert(eval2_hom * scalar1); + res + } + fn modify_monomial(&mut self, exponents: [usize; N], coeff: F) { self.monomials .entry(exponents) diff --git a/mvpoly/src/prime.rs b/mvpoly/src/prime.rs index 498cc1c7ee..41398663f8 100644 --- a/mvpoly/src/prime.rs +++ b/mvpoly/src/prime.rs @@ -432,6 +432,18 @@ impl MVPoly for Dense HashMap { + unimplemented!() + } + fn modify_monomial(&mut self, exponents: [usize; N], coeff: F) { let mut prime_gen = PrimeNumberGenerator::new(); let primes = prime_gen.get_first_nth_primes(N); diff --git a/mvpoly/tests/monomials.rs b/mvpoly/tests/monomials.rs index 514ab7cc7b..b169f8bc01 100644 --- a/mvpoly/tests/monomials.rs +++ b/mvpoly/tests/monomials.rs @@ -712,3 +712,95 @@ fn test_from_expr_ec_addition() { assert_eq!(eval, exp_eval); } } + +#[test] +fn test_cross_terms_fixed_polynomial_and_eval_homogeneous_degree_3() { + // X + let x = { + // We say it is of degree 2 for the cross-term computation + let mut x = Sparse::::zero(); + x.add_monomial([1], Fp::one()); + x + }; + // X * Y + let scaled_x = { + let scaling_var = { + let mut v = Sparse::::zero(); + v.add_monomial([0, 1], Fp::one()); + v + }; + let x: Sparse = { + let x: Result, String> = x.clone().into(); + x.unwrap() + }; + x.clone() * scaling_var + }; + // x1 = 42, α1 = 1 + // x2 = 42, α2 = 2 + let eval1: [Fp; 2] = [Fp::from(42), Fp::one()]; + let eval2: [Fp; 2] = [Fp::from(42), Fp::one() + Fp::one()]; + let u1 = Fp::one(); + let u2 = Fp::one() + Fp::one(); + let scalar1 = eval1[1]; + let scalar2 = eval2[1]; + + let cross_terms_scaled_p1 = { + // When computing the cross-terms, the method supposes that the polynomial + // is of degree D - 1. + // We do suppose we homogenize to degree 3. + let scaled_x: Sparse = { + let p: Result, String> = scaled_x.clone().into(); + p.unwrap() + }; + scaled_x.compute_cross_terms(&eval1, &eval2, u1, u2) + }; + let cross_terms = { + let x: Sparse = { + let x: Result, String> = x.clone().into(); + x.unwrap() + }; + x.compute_cross_terms_scaled( + &eval1[0..1].try_into().unwrap(), + &eval2[0..1].try_into().unwrap(), + u1, + u2, + scalar1, + scalar2, + ) + }; + assert_eq!(cross_terms, cross_terms_scaled_p1); +} + +#[test] +fn test_cross_terms_scaled() { + let mut rng = o1_utils::tests::make_test_rng(None); + let p1 = unsafe { Sparse::::random(&mut rng, None) }; + let scaled_p1 = { + // Scaling variable is U. We do this by adding a new variable. + let scaling_variable: Sparse = { + let mut p: Sparse = Sparse::::zero(); + p.add_monomial([0, 0, 0, 0, 1], Fp::one()); + p + }; + // Simply transforming p1 in the expected degree and with the right + // number of variables + let p1 = { + let p1: Result, String> = p1.clone().into(); + p1.unwrap() + }; + scaling_variable.clone() * p1.clone() + }; + let random_eval1: [Fp; 5] = std::array::from_fn(|_| Fp::rand(&mut rng)); + let random_eval2: [Fp; 5] = std::array::from_fn(|_| Fp::rand(&mut rng)); + let scalar1 = random_eval1[4]; + let scalar2 = random_eval2[4]; + let u1 = Fp::rand(&mut rng); + let u2 = Fp::rand(&mut rng); + let cross_terms = { + let eval1: [Fp; 4] = random_eval1[0..4].try_into().unwrap(); + let eval2: [Fp; 4] = random_eval2[0..4].try_into().unwrap(); + p1.compute_cross_terms_scaled(&eval1, &eval2, u1, u2, scalar1, scalar2) + }; + let scaled_cross_terms = scaled_p1.compute_cross_terms(&random_eval1, &random_eval2, u1, u2); + assert_eq!(cross_terms, scaled_cross_terms); +} From 00a6119c8071450016ca3e8cd0afe2823d6853cc Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 17 Dec 2024 16:44:35 +0100 Subject: [PATCH 5/5] mvpoly/monomials: allow to artificially increase the degree --- mvpoly/src/monomials.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mvpoly/src/monomials.rs b/mvpoly/src/monomials.rs index 67d3127403..e08be206fa 100644 --- a/mvpoly/src/monomials.rs +++ b/mvpoly/src/monomials.rs @@ -554,12 +554,19 @@ impl From for Sparse } } -impl From> - for Result, String> +impl + From> for Result, String> { - fn from(poly: Sparse) -> Result, String> { + fn from(poly: Sparse) -> Result, String> { if M < N { - return Err("The number of variables must be greater than N".to_string()); + return Err(format!( + "The final number of variables {M} must be greater than {N}" + )); + } + if D_PRIME < D { + return Err(format!( + "The final degree {D_PRIME} must be greater than initial degree {D}" + )); } let mut monomials = HashMap::new(); poly.monomials.iter().for_each(|(exponents, coeff)| {