From 7a03c1ba7ef889b33321865ae64ee6d072d2d74c Mon Sep 17 00:00:00 2001 From: Tom Grosso Date: Tue, 1 Oct 2024 17:05:39 -0300 Subject: [PATCH] Add circle evaluatio with fold_circle_into_line --- stwo_cairo_verifier/src/fri.cairo | 3 + stwo_cairo_verifier/src/fri/folding.cairo | 68 ++++++++++ stwo_cairo_verifier/src/fri/verifier.cairo | 2 + stwo_cairo_verifier/src/poly/circle.cairo | 150 ++++++++++++++++++++- 4 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 stwo_cairo_verifier/src/fri/folding.cairo create mode 100644 stwo_cairo_verifier/src/fri/verifier.cairo diff --git a/stwo_cairo_verifier/src/fri.cairo b/stwo_cairo_verifier/src/fri.cairo index 165b3e8c6..67a654ce5 100644 --- a/stwo_cairo_verifier/src/fri.cairo +++ b/stwo_cairo_verifier/src/fri.cairo @@ -1,3 +1,6 @@ +pub mod folding; +pub mod verifier; + use stwo_cairo_verifier::fields::m31::M31Trait; use stwo_cairo_verifier::circle::{Coset, CosetImpl}; use stwo_cairo_verifier::poly::line::{LineDomain, LineDomainImpl}; diff --git a/stwo_cairo_verifier/src/fri/folding.cairo b/stwo_cairo_verifier/src/fri/folding.cairo new file mode 100644 index 000000000..3f7eccf1a --- /dev/null +++ b/stwo_cairo_verifier/src/fri/folding.cairo @@ -0,0 +1,68 @@ +use stwo_cairo_verifier::fields::m31::M31Trait; +use stwo_cairo_verifier::circle::{Coset, CosetImpl}; +use stwo_cairo_verifier::poly::circle::{CircleDomain, CircleDomainImpl}; +use stwo_cairo_verifier::poly::line::{LineDomain, LineDomainImpl}; +use stwo_cairo_verifier::fields::qm31::{QM31, qm31, QM31Trait}; +use stwo_cairo_verifier::fields::m31::M31; +use stwo_cairo_verifier::utils::{bit_reverse_index, pow}; +use stwo_cairo_verifier::fri::verifier::{FOLD_STEP, CIRCLE_TO_LINE_FOLD_STEP}; +use stwo_cairo_verifier::poly::circle::{ + CircleEvaluation, SparseCircleEvaluation, SparseCircleEvaluationImpl +}; +use stwo_cairo_verifier::poly::line::{LineEvaluation, LineEvaluationImpl}; +use stwo_cairo_verifier::fri::ibutterfly; + +/// Folds and accumulates a degree `d` circle polynomial into a degree `d/2` univariate +/// polynomial. +/// +/// Let `src` be the evaluation of a circle polynomial `f` on a +/// [`CircleDomain`] `E`. This function computes evaluations of `f' = f0 +/// + alpha * f1` on the x-coordinates of `E` such that `2f(p) = f0(px) + py * f1(px)`. The +/// evaluations of `f'` are accumulated into `dst` by the formula `dst = dst * alpha^2 + +/// f'`. +pub fn fold_circle_into_line(eval: @CircleEvaluation, alpha: QM31) -> LineEvaluation { + let domain = eval.domain; + let mut values = array![]; + let mut i = 0; + while i < eval.values.len() / 2 { + let p = domain + .at(bit_reverse_index(i * pow(2, CIRCLE_TO_LINE_FOLD_STEP), domain.log_size())); + let f_p = eval.values[2 * i]; + let f_neg_p = eval.values[2 * i + 1]; + let (f0, f1) = ibutterfly(*f_p, *f_neg_p, p.y.inverse()); + values.append(f0 + alpha * f1); + i += 1; + }; + LineEvaluation { values, domain: LineDomainImpl::new(*domain.half_coset) } +} + +#[cfg(test)] +mod test { + use stwo_cairo_verifier::poly::line::{ + LineEvaluation, SparseLineEvaluation, SparseLineEvaluationImpl + }; + use stwo_cairo_verifier::fields::m31::M31Trait; + use stwo_cairo_verifier::circle::{Coset, CosetImpl}; + use stwo_cairo_verifier::poly::circle::{CircleDomain, CircleDomainImpl}; + use stwo_cairo_verifier::poly::line::{LineDomain, LineDomainImpl}; + use stwo_cairo_verifier::fields::qm31::{QM31, qm31}; + use stwo_cairo_verifier::fields::m31::M31; + use stwo_cairo_verifier::utils::{bit_reverse_index, pow}; + use stwo_cairo_verifier::fri::verifier::{FOLD_STEP, CIRCLE_TO_LINE_FOLD_STEP}; + use stwo_cairo_verifier::poly::circle::{ + CircleEvaluation, CircleEvaluationImpl, SparseCircleEvaluation, SparseCircleEvaluationImpl + }; + + #[test] + fn test_fold_circle_into_line_1() { + let domain = CircleDomain { half_coset: CosetImpl::new(553648128, 0) }; + let values = array![qm31(807167738, 0, 0, 0), qm31(1359821401, 0, 0, 0)]; + let sparse_circle_evaluation: SparseCircleEvaluation = SparseCircleEvaluation { + subcircle_evals: array![CircleEvaluationImpl::new(domain, values)] + }; + let alpha = qm31(260773061, 362745443, 1347591543, 1084609991); + let result = sparse_circle_evaluation.fold(alpha); + let expected_result = array![qm31(730692421, 1363821003, 2146256633, 106012305)]; + assert_eq!(expected_result, result); + } +} diff --git a/stwo_cairo_verifier/src/fri/verifier.cairo b/stwo_cairo_verifier/src/fri/verifier.cairo new file mode 100644 index 000000000..d71210817 --- /dev/null +++ b/stwo_cairo_verifier/src/fri/verifier.cairo @@ -0,0 +1,2 @@ +pub const CIRCLE_TO_LINE_FOLD_STEP: u32 = 1; +pub const FOLD_STEP: u32 = 1; diff --git a/stwo_cairo_verifier/src/poly/circle.cairo b/stwo_cairo_verifier/src/poly/circle.cairo index 57ede9cf1..2b7257f38 100644 --- a/stwo_cairo_verifier/src/poly/circle.cairo +++ b/stwo_cairo_verifier/src/poly/circle.cairo @@ -1,11 +1,15 @@ +use stwo_cairo_verifier::circle::CirclePointTrait; use core::option::OptionTrait; use core::clone::Clone; use core::result::ResultTrait; +use stwo_cairo_verifier::fields::qm31::{QM31, qm31}; use stwo_cairo_verifier::fields::m31::{M31, m31}; use stwo_cairo_verifier::utils::pow; use stwo_cairo_verifier::circle::{ Coset, CosetImpl, CirclePoint, CirclePointM31Impl, M31_CIRCLE_GEN, CIRCLE_ORDER }; +use stwo_cairo_verifier::fri::folding::fold_circle_into_line; +use core::num::traits::ops::wrapping::WrappingSub; /// A valid domain for circle polynomial interpolation and evaluation. /// @@ -18,32 +22,118 @@ pub struct CircleDomain { #[generate_trait] pub impl CircleDomainImpl of CircleDomainTrait { + /// Given a coset `C + `, constructs the circle domain `+-C + ` (i.e. + /// this coset and its conjugate). + fn new(half_coset: Coset) -> CircleDomain { + CircleDomain { half_coset } + } + + /// Returns the size of the domain. + fn size(self: @CircleDomain) -> usize { + pow(2, self.log_size()) + } + + /// Returns the log size of the domain. fn log_size(self: @CircleDomain) -> usize { *self.half_coset.log_size + 1 } + /// Returns the circle point index of the `i`th domain element. fn index_at(self: @CircleDomain, index: usize) -> usize { if index < self.half_coset.size() { self.half_coset.index_at(index) } else { - CIRCLE_ORDER - self.half_coset.index_at(index - self.half_coset.size()) + CIRCLE_ORDER.wrapping_sub(self.half_coset.index_at(index - self.half_coset.size())) } } + /// Returns the `i` th domain element. fn at(self: @CircleDomain, index: usize) -> CirclePoint { let mut scalar = self.index_at(index).into(); M31_CIRCLE_GEN.mul(ref scalar) } + + fn new_with_log_size(log_size: u32) -> CircleDomain { + CircleDomain { half_coset: CosetImpl::half_odds(log_size - 1) } + } } +/// An evaluation defined on a [CircleDomain]. +/// +/// The values are ordered according to the [CircleDomain] ordering. +#[derive(Debug, Drop, Clone, PartialEq, Eq)] +pub struct CircleEvaluation { + pub values: Array, + pub domain: CircleDomain, + _eval_order: BitReversedOrder +} + +#[derive(Debug, Drop, Clone, PartialEq)] +pub struct BitReversedOrder {} + +#[generate_trait] +pub impl CircleEvaluationImpl of CircleEvaluationTrait { + fn new(domain: CircleDomain, values: Array) -> CircleEvaluation { + CircleEvaluation { values: values, domain: domain, _eval_order: BitReversedOrder {} } + } +} + +/// Holds a foldable subset of circle polynomial evaluations. +#[derive(Drop, Clone, Debug, PartialEq)] +pub struct SparseCircleEvaluation { + pub subcircle_evals: Array +} + +#[generate_trait] +pub impl SparseCircleEvaluationImpl of SparseCircleEvaluationImplTrait { + fn new(subcircle_evals: Array) -> SparseCircleEvaluation { + SparseCircleEvaluation { subcircle_evals: subcircle_evals } + } + + fn accumulate( + self: @SparseCircleEvaluation, rhs: @SparseCircleEvaluation, alpha: QM31 + ) -> SparseCircleEvaluation { + assert_eq!(self.subcircle_evals.len(), rhs.subcircle_evals.len()); + let mut subcircle_evals = array![]; + let mut i = 0; + while i < self.subcircle_evals.len() { + let lhs = self.subcircle_evals[i]; + let rhs = rhs.subcircle_evals[i]; + let mut values = array![]; + assert_eq!(lhs.values.len(), rhs.values.len()); + let mut j = 0; + while j < lhs.values.len() { + values.append(*lhs.values[j] * alpha + *rhs.values[j]); + j += 1; + }; + subcircle_evals.append(CircleEvaluationImpl::new(*lhs.domain, values)); + i += 1; + }; + + SparseCircleEvaluation { subcircle_evals } + } + + fn fold(self: @SparseCircleEvaluation, alpha: QM31) -> Array { + let mut i = 0; + let mut res: Array = array![]; + while i < self.subcircle_evals.len() { + let circle_evaluation = fold_circle_into_line(self.subcircle_evals[i], alpha); + res.append(*circle_evaluation.values.at(0)); + i += 1; + }; + return res; + } +} #[cfg(test)] mod tests { - use super::{CircleDomain, CircleDomainTrait}; - use stwo_cairo_verifier::circle::{ - Coset, CosetImpl, CirclePoint, CirclePointM31Impl, M31_CIRCLE_GEN, CIRCLE_ORDER + use super::{ + CircleDomain, CircleDomainTrait, CircleEvaluation, CircleEvaluationImpl, SparseCircleEvaluation, + SparseCircleEvaluationImplTrait }; - use stwo_cairo_verifier::fields::m31::{M31, m31}; + use stwo_cairo_verifier::circle::{Coset, CosetImpl, CirclePoint}; + use stwo_cairo_verifier::fields::m31::m31; + use stwo_cairo_verifier::fields::qm31::qm31; #[test] fn test_circle_domain_at_1() { @@ -64,4 +154,54 @@ mod tests { assert_eq!(result, CirclePoint { x: m31(9803698), y: m31(2079025011) }); } + + #[test] + fn test_accumulate_1() { + let alpha = qm31(984186742, 463356387, 533637878, 1417871498); + let lhs = SparseCircleEvaluation { + subcircle_evals: array![ + CircleEvaluationImpl::new( + CircleDomain { half_coset: CosetImpl::new(16777216, 0) }, + array![qm31(28672, 0, 0, 0), qm31(36864, 0, 0, 0)] + ), + CircleEvaluationImpl::new( + CircleDomain { half_coset: CosetImpl::new(2030043136, 0) }, + array![qm31(28672, 0, 0, 0), qm31(36864, 0, 0, 0)] + ), + CircleEvaluationImpl::new( + CircleDomain { half_coset: CosetImpl::new(2097152000, 0) }, + array![qm31(2147454975, 0, 0, 0), qm31(2147446783, 0, 0, 0)] + ), + ] + }; + let rhs = lhs.clone(); + let result = lhs.accumulate(@rhs, alpha); + + assert_eq!(result, SparseCircleEvaluation { + subcircle_evals: array![ + CircleEvaluationImpl::new( + CircleDomain { half_coset: CosetImpl::new(16777216, 0) }, + array![ + qm31(667173716, 1020487722, 1791736788, 1346152946), + qm31(1471361534, 84922130, 1076528072, 810417939) + ] + ), + CircleEvaluationImpl::new( + CircleDomain { half_coset: CosetImpl::new(2030043136, 0) }, + array![ + qm31(667173716, 1020487722, 1791736788, 1346152946), + qm31(1471361534, 84922130, 1076528072, 810417939) + ] + ), + CircleEvaluationImpl::new( + CircleDomain { half_coset: CosetImpl::new(2097152000, 0) }, + array![ + qm31(1480309931, 1126995925, 355746859, 801330701), + qm31(676122113, 2062561517, 1070955575, 1337065708) + ] + ), + ] + }); + + } }