From 0d0765f65506fc5129d6827f2f5c67d9fd6bfd53 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 | 1 + stwo_cairo_verifier/src/fri/folding.cairo | 60 ++++++- stwo_cairo_verifier/src/fri/verifier.cairo | 2 + stwo_cairo_verifier/src/poly/circle.cairo | 176 ++++++++++++++++++--- 4 files changed, 210 insertions(+), 29 deletions(-) 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 df23e97e2..dad6f043a 100644 --- a/stwo_cairo_verifier/src/fri.cairo +++ b/stwo_cairo_verifier/src/fri.cairo @@ -1 +1,2 @@ pub mod folding; +pub mod verifier; diff --git a/stwo_cairo_verifier/src/fri/folding.cairo b/stwo_cairo_verifier/src/fri/folding.cairo index b76bf78e2..1ec71da4d 100644 --- a/stwo_cairo_verifier/src/fri/folding.cairo +++ b/stwo_cairo_verifier/src/fri/folding.cairo @@ -1,12 +1,16 @@ 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, SparseCircleEvaluation, SparseCircleEvaluationImpl +}; use stwo_cairo_verifier::poly::line::{LineEvaluation, LineEvaluationImpl}; -pub const CIRCLE_TO_LINE_FOLD_STEP: u32 = 1; -pub const FOLD_STEP: u32 = 1; + /// Folds a degree `d` polynomial into a degree `d/2` polynomial. pub fn fold_line(eval: @LineEvaluation, alpha: QM31) -> LineEvaluation { @@ -21,22 +25,57 @@ pub fn fold_line(eval: @LineEvaluation, alpha: QM31) -> LineEvaluation { values.append(f0 + alpha * f1); i += 1; }; + LineEvaluationImpl::new(domain.double(), values) } + + +/// 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) } +} + pub fn ibutterfly(v0: QM31, v1: QM31, itwid: M31) -> (QM31, QM31) { (v0 + v1, (v0 - v1) * itwid.into()) } + + 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::folding::{FOLD_STEP, CIRCLE_TO_LINE_FOLD_STEP}; + use stwo_cairo_verifier::fri::verifier::{FOLD_STEP, CIRCLE_TO_LINE_FOLD_STEP}; + use stwo_cairo_verifier::poly::circle::{ + CircleEvaluation, SparseCircleEvaluation, SparseCircleEvaluationImpl + }; + + #[test] fn test_fold_line_1() { let domain = LineDomainImpl::new(CosetImpl::new(67108864, 1)); @@ -52,6 +91,7 @@ mod test { let expected_result = array![qm31(515899232, 1030391528, 1006544539, 11142505)]; assert_eq!(expected_result, result); } + #[test] fn test_fold_line_2() { let domain = LineDomainImpl::new(CosetImpl::new(553648128, 1)); @@ -67,4 +107,18 @@ mod test { let expected_result = array![qm31(1379727866, 1083096056, 1409020369, 1977903500)]; assert_eq!(expected_result, result); } + + + #[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![CircleEvaluation { values, domain }] + }; + 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 270313bc4..35f08b388 100644 --- a/stwo_cairo_verifier/src/poly/circle.cairo +++ b/stwo_cairo_verifier/src/poly/circle.cairo @@ -1,14 +1,16 @@ +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; /// A valid domain for circle polynomial interpolation and evaluation. -/// /// Valid domains are a disjoint union of two conjugate cosets: `+-C + `. /// The ordering defined on this domain is `C + iG_n`, and then `-C - iG_n`. #[derive(Debug, Copy, Drop, PartialEq, Eq)] @@ -18,10 +20,23 @@ 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) @@ -30,37 +45,146 @@ pub impl CircleDomainImpl of CircleDomainTrait { } } - fn at(self: @CircleDomain, index: usize) -> CirclePoint:: { + /// Returns the `i` th domain element. + fn at(self: @CircleDomain, index: usize) -> CirclePoint { M31_CIRCLE_GEN.mul(self.index_at(index).into()) } + + 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, +} -#[cfg(test)] -mod tests { - use super::{CircleDomain, CircleDomainTrait}; - use stwo_cairo_verifier::circle::{ - Coset, CosetImpl, CirclePoint, CirclePointM31Impl, M31_CIRCLE_GEN, CIRCLE_ORDER - }; - use stwo_cairo_verifier::fields::m31::{M31, m31}; - - #[test] - fn test_circle_domain_at_1() { - let half_coset = Coset { initial_index: 16777216, step_size: 67108864, log_size: 5 }; - let domain = CircleDomain { half_coset }; - let index = 17; - let result = domain.at(index); - let expected_result = CirclePoint:: { x: m31(7144319), y: m31(1742797653) }; - assert_eq!(expected_result, result); +#[generate_trait] +pub impl CircleEvaluationImpl of CircleEvaluationTrait { + fn new(domain: CircleDomain, values: Array) -> CircleEvaluation { + CircleEvaluation { values: values, domain: domain } } +} + +/// Holds a foldable subset of circle polynomial evaluations. +#[derive(Drop, Clone, Debug, PartialEq)] +pub struct SparseCircleEvaluation { + pub subcircle_evals: Array +} - #[test] - fn test_circle_domain_at_2() { - let half_coset = Coset { initial_index: 16777216, step_size: 67108864, log_size: 5 }; - let domain = CircleDomain { half_coset }; - let index = 37; - let result = domain.at(index); - let expected_result = CirclePoint:: { x: m31(9803698), y: m31(2079025011) }; - assert_eq!(expected_result, result); +#[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(CircleEvaluation { domain: *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; + } +} + +#[test] +fn test_circle_domain_at_1() { + let half_coset = Coset { initial_index: 16777216, step_size: 67108864, log_size: 5 }; + let domain = CircleDomain { half_coset }; + let index = 17; + let result = domain.at(index); + let expected_result = CirclePoint:: { x: m31(7144319), y: m31(1742797653) }; + assert_eq!(expected_result, result); +} + +#[test] +fn test_circle_domain_at_2() { + let half_coset = Coset { initial_index: 16777216, step_size: 67108864, log_size: 5 }; + let domain = CircleDomain { half_coset }; + let index = 37; + let result = domain.at(index); + let expected_result = CirclePoint:: { x: m31(9803698), y: m31(2079025011) }; + assert_eq!(expected_result, result); } + +#[test] +fn test_accumulate_1() { + let alpha = qm31(984186742, 463356387, 533637878, 1417871498); + let lhs = SparseCircleEvaluation { + subcircle_evals: array![ + CircleEvaluation { + values: array![qm31(28672, 0, 0, 0), qm31(36864, 0, 0, 0)], + domain: CircleDomain { half_coset: CosetImpl::new(16777216, 0) } + }, + CircleEvaluation { + values: array![qm31(28672, 0, 0, 0), qm31(36864, 0, 0, 0)], + domain: CircleDomain { half_coset: CosetImpl::new(2030043136, 0) } + }, + CircleEvaluation { + values: array![qm31(2147454975, 0, 0, 0), qm31(2147446783, 0, 0, 0)], + domain: CircleDomain { half_coset: CosetImpl::new(2097152000, 0) } + }, + ] + }; + let rhs = lhs.clone(); + let result = lhs.accumulate(@rhs, alpha); + + let expected_result = SparseCircleEvaluation { + subcircle_evals: array![ + CircleEvaluation { + values: array![ + qm31(667173716, 1020487722, 1791736788, 1346152946), + qm31(1471361534, 84922130, 1076528072, 810417939) + ], + domain: CircleDomain { half_coset: CosetImpl::new(16777216, 0) } + }, + CircleEvaluation { + values: array![ + qm31(667173716, 1020487722, 1791736788, 1346152946), + qm31(1471361534, 84922130, 1076528072, 810417939) + ], + domain: CircleDomain { half_coset: CosetImpl::new(2030043136, 0) } + }, + CircleEvaluation { + values: array![ + qm31(1480309931, 1126995925, 355746859, 801330701), + qm31(676122113, 2062561517, 1070955575, 1337065708) + ], + domain: CircleDomain { half_coset: CosetImpl::new(2097152000, 0) } + }, + ] + }; + + assert_eq!(expected_result, result); +} +