diff --git a/triton-vm/src/arithmetic_domain.rs b/triton-vm/src/arithmetic_domain.rs index 80d61765..9c42a5ec 100644 --- a/triton-vm/src/arithmetic_domain.rs +++ b/triton-vm/src/arithmetic_domain.rs @@ -1,7 +1,9 @@ use std::ops::Mul; use std::ops::MulAssign; +use num_traits::ConstOne; use num_traits::One; +use num_traits::Zero; use rayon::prelude::*; use twenty_first::math::traits::FiniteField; use twenty_first::math::traits::PrimitiveRootOfUnity; @@ -27,7 +29,7 @@ impl ArithmeticDomain { /// Errors if the domain length is not a power of 2. pub fn of_length(length: usize) -> Result { let domain = Self { - offset: bfe!(1), + offset: BFieldElement::ONE, generator: Self::generator_for_length(length as u64)?, length, }; @@ -126,6 +128,29 @@ impl ArithmeticDomain { domain_values } + /// A polynomial that evaluates to 0 on (and only on) + /// a [domain value][Self::domain_values]. + pub fn zerofier(&self) -> Polynomial { + if self.offset.is_zero() { + return Polynomial::x_to_the(1); + } + + Polynomial::x_to_the(self.length) + - Polynomial::from_constant(self.offset.mod_pow(self.length as u64)) + } + + /// [`Self::zerofier`] times the argument. + /// More performant than polynomial multiplication. + /// See [`Self::zerofier`] for details. + pub fn mul_zerofier_with(&self, polynomial: Polynomial) -> Polynomial + where + FF: FiniteField + Mul, + { + // use knowledge of zerofier's shape for faster multiplication + polynomial.shift_coefficients(self.length) + - polynomial.scalar_mul(self.offset.mod_pow(self.length as u64)) + } + pub(crate) fn halve(&self) -> Result { if self.length < 2 { return Err(ArithmeticDomainError::TooSmallForHalving(self.length)); @@ -316,4 +341,20 @@ mod tests { let values1 = polynomial.batch_evaluate(&domain.domain_values()); assert_eq!(values0, values1); } + + #[proptest] + fn zerofier_is_actually_zerofier(#[strategy(arbitrary_domain())] domain: ArithmeticDomain) { + let actual_zerofier = Polynomial::zerofier(&domain.domain_values()); + prop_assert_eq!(actual_zerofier, domain.zerofier()); + } + + #[proptest] + fn multiplication_with_zerofier_is_identical_to_method_mul_with_zerofier( + #[strategy(arbitrary_domain())] domain: ArithmeticDomain, + #[strategy(arbitrary_polynomial())] polynomial: Polynomial, + ) { + let mul = domain.zerofier() * polynomial.clone(); + let mul_with = domain.mul_zerofier_with(polynomial); + prop_assert_eq!(mul, mul_with); + } } diff --git a/triton-vm/src/table/master_table.rs b/triton-vm/src/table/master_table.rs index 98026764..990bb63b 100644 --- a/triton-vm/src/table/master_table.rs +++ b/triton-vm/src/table/master_table.rs @@ -327,7 +327,7 @@ where .inverse(); let ood_trace_domain_zerofier: XFieldElement = - self.trace_domain_zerofier().evaluate(indeterminate); + self.trace_domain().zerofier().evaluate(indeterminate); let trace_table = self.trace_table(); (0..Self::NUM_COLUMNS) @@ -357,42 +357,27 @@ where .trace_domain() .interpolate(column_codeword.as_slice().unwrap()); - let randomizer = self.mul_trace_domain_zerofier_with(self.trace_randomizer_for_column(idx)); + let randomizer = self + .trace_domain() + .mul_zerofier_with(self.trace_randomizer_for_column(idx)); column_interpolant + randomizer } - /// The zerofier for the [trace domain][Self::trace_domain]. When used in - /// combination with a [trace randomizer][Self::trace_randomizer_for_column], - /// this zerofier makes sure that the trace is not influenced anywhere on the - /// trace domain. In particular, use the following formula: + /// When added to a column in the correct way (see below), allows revealing up + /// to `num_trace_randomizers` entries of the column without breaking + /// zero-knowledge. /// - /// `column + zerofier·randomizer` + /// In order for the trace randomizer to not influence the trace on the + /// [trace domain][Self::trace_domain], it must be multiplied with a polynomial + /// that evaluates to zero on that domain. The polynomial of lowest degree with + /// this property is the corresponding [zerofier][ArithmeticDomain::zerofier]. + /// The randomized trace column interpolant can then be obtained through: /// - /// If _only_ the randomized column is needed, see - /// [`Self::randomized_column_interpolant`]. + /// `column + zerofier·randomizer` /// - /// If you _only_ need this zerofier for multiplication, see - /// [`Self::mul_trace_domain_zerofier_with`], which is faster for - /// this particular operation. - fn trace_domain_zerofier(&self) -> Polynomial { - Polynomial::x_to_the(self.trace_domain().length) - Polynomial::one() - } - - /// [`Self::trace_domain_zerofier`] times the argument. - /// See [`Self::trace_domain_zerofier`] for more details. - fn mul_trace_domain_zerofier_with( - &self, - poly: Polynomial, - ) -> Polynomial { - // use knowledge of zerofier's shape for faster multiplication - poly.shift_coefficients(self.trace_domain().length) - poly - } - - /// When added to a column in the correct way (see - /// [`Self::trace_domain_zerofier`]), allows revealing up to - /// `num_trace_randomizers` entries of the column without breaking - /// zero-knowledge. + /// If you want to multiply the trace randomizer with the zerofier, the most + /// performant approach is [`ArithmeticDomain::mul_zerofier_with`]. /// /// # Panics /// @@ -510,8 +495,9 @@ where .enumerate() .map(|(i, &w)| self.trace_randomizer_for_column(i).scalar_mul(w)) .reduce(Polynomial::zero, |sum, x| sum + x); - let randomizer_contribution = - self.mul_trace_domain_zerofier_with(weighted_sum_of_trace_randomizer_polynomials); + let randomizer_contribution = self + .trace_domain() + .mul_zerofier_with(weighted_sum_of_trace_randomizer_polynomials); weighted_sum_of_trace_columns + randomizer_contribution } @@ -546,10 +532,9 @@ where // add trace randomizers to their columns // todo: this could be done using `Polynomial::batch_evaluate` if that function // had more general trait bounds 🤷 - let zerofier = self.trace_domain_zerofier(); let zerofier_evals = indeterminates .par_iter() - .map(|&i| zerofier.evaluate::<_, Self::Field>(i)) + .map(|&i| self.trace_domain().zerofier().evaluate::<_, Self::Field>(i)) .collect::>(); let trace_randomizers = (0..Self::NUM_COLUMNS)