Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Circle starks #924

Open
wants to merge 75 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 74 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
852f856
optimize add
Sep 18, 2024
0d68798
save changes. Add, sub and mul checked
Sep 18, 2024
5ebc30a
fix tests
ColoCarletti Sep 19, 2024
81439ae
add new inv
ColoCarletti Sep 19, 2024
47cf7da
add mult by powers of two
Sep 19, 2024
2acfdd7
Merge branch 'optimize_mersenne31' of github.com:lambdaclass/lambdawo…
Sep 19, 2024
cfba8bc
replace inverse
Sep 19, 2024
01eba0d
test new inv
ColoCarletti Sep 20, 2024
60fd981
modify old algorithm for inv
ColoCarletti Sep 20, 2024
996e224
fix tests extension
Sep 20, 2024
c41b977
Merge branch 'optimize_mersenne31' of github.com:lambdaclass/lambdawo…
Sep 20, 2024
28d8b0e
add mul for degree 4 extension
Sep 20, 2024
5e1f533
add fp4 isField and isSubField operations and benchmarks
Sep 23, 2024
fde7faa
new version for fp4 mul based on the paper
Sep 23, 2024
60c8197
add mul of a fp2e by non-residue
Sep 23, 2024
25b8869
change inv using mul_fp2_by_non_resiude
Sep 23, 2024
1187be8
save work
Sep 24, 2024
ecff11e
wip fp2 test
ColoCarletti Sep 24, 2024
a6169ea
Merge branch 'optimize_mersenne31' of github.com:lambdaclass/lambdawo…
ColoCarletti Sep 24, 2024
ae1446a
add fp2 tests
ColoCarletti Sep 24, 2024
fa12fc5
add 2 * a^2 - 1 function
Sep 24, 2024
461485b
use karatsuba in fp4 mul version 1
Sep 25, 2024
d4e3f40
clean up
ColoCarletti Sep 25, 2024
f0437c7
fix Fp as subfield of Fp2. Tests Fp plus Fp4 is now correct
Sep 25, 2024
2c5a301
fix inv
ColoCarletti Sep 26, 2024
4a42dbc
fix comments
ColoCarletti Sep 26, 2024
21d09c6
create crate
ColoCarletti Sep 26, 2024
f7efd90
Revert "create crate"
ColoCarletti Sep 26, 2024
db3ab30
Merge branch 'optimize_mersenne31' into circle-starks
ColoCarletti Sep 26, 2024
eddd9be
add circle point implementation
ColoCarletti Sep 26, 2024
ba738c7
add group order
ColoCarletti Sep 26, 2024
85d80c1
rm dependencie
ColoCarletti Sep 26, 2024
14a7beb
add cosets
ColoCarletti Sep 30, 2024
b56d212
add twiddle
ColoCarletti Oct 1, 2024
58aeb0c
init cfft
ColoCarletti Oct 1, 2024
a85eb0f
test cfft
ColoCarletti Oct 2, 2024
6bda70d
test 16 not working
Oct 2, 2024
7cbd173
fix n16 test
ColoCarletti Oct 2, 2024
4f60a48
refactor
ColoCarletti Oct 3, 2024
35a9d5a
Merge branch 'main' into circle-starks
ColoCarletti Oct 4, 2024
95e87d0
clippy
ColoCarletti Oct 4, 2024
10636f9
fmt
ColoCarletti Oct 4, 2024
c76793f
Merge branch 'main' into circle-starks
diegokingston Oct 4, 2024
876459d
clippy
ColoCarletti Oct 4, 2024
dcdd18a
Merge branch 'circle-starks' of github.com:lambdaclass/lambdaworks in…
ColoCarletti Oct 4, 2024
076b683
rm std
ColoCarletti Oct 4, 2024
0d7d89f
add alloc
ColoCarletti Oct 4, 2024
9685a74
fix
ColoCarletti Oct 4, 2024
ac842bf
wip
ColoCarletti Oct 7, 2024
52655b1
Merge branch 'main' into circle-starks
ColoCarletti Oct 8, 2024
5f97990
add hand iterpolation for 4 and 8
ColoCarletti Oct 14, 2024
dc4124e
wip
ColoCarletti Oct 15, 2024
56dae17
evaluation and interpolation working
Oct 16, 2024
2546ca2
add tests and comments
Oct 16, 2024
57735cf
Merge branch 'main' into circle-starks
ColoCarletti Oct 17, 2024
da5fae7
clippy
ColoCarletti Oct 17, 2024
1166a42
Merge branch 'circle-starks' of github.com:lambdaclass/lambdaworks in…
ColoCarletti Oct 17, 2024
28ab71f
fmt
ColoCarletti Oct 17, 2024
e0c666d
remove unused functions
ColoCarletti Oct 17, 2024
3073470
add comment
ColoCarletti Oct 17, 2024
37aae9e
Update math/src/circle/polynomial.rs
nicole-graus Oct 23, 2024
4436e28
change generator and order CirclePoint functions as constants
Oct 23, 2024
835a565
impl eq as PartialEq
Oct 23, 2024
2ea3171
implement scalar_mul as Mul
Oct 23, 2024
c9acb74
Change error name to a more descriptive one and move it to point.rs
Oct 23, 2024
93fd515
fix lint
Oct 23, 2024
7ae4990
fix lint
Oct 23, 2024
8801317
fix some comments
Oct 23, 2024
7fe6a7d
Merge branch 'main' into circle-starks
diegokingston Oct 24, 2024
8b2b3d2
add alloc::vec
ColoCarletti Oct 24, 2024
ebc5e9d
fix no-std
ColoCarletti Oct 24, 2024
f3028e5
cargo fmt
ColoCarletti Oct 24, 2024
b645a7b
fix no-std
ColoCarletti Oct 24, 2024
c7cfd8f
remove macro
ColoCarletti Oct 24, 2024
fc68bea
add comment
ColoCarletti Oct 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 220 additions & 0 deletions math/src/circle/cfft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
extern crate alloc;
use crate::field::{element::FieldElement, fields::mersenne31::field::Mersenne31Field};
use alloc::vec::Vec;

#[cfg(feature = "alloc")]
/// fft in place algorithm used to evaluate a polynomial of degree 2^n - 1 in 2^n points.
/// Input must be of size 2^n for some n.
pub fn cfft(
input: &mut [FieldElement<Mersenne31Field>],
twiddles: Vec<Vec<FieldElement<Mersenne31Field>>>,
) {
// If the input size is 2^n, then log_2_size is n.
let log_2_size = input.len().trailing_zeros();

// The cfft has n layers.
(0..log_2_size).for_each(|i| {
// In each layer i we split the current input in chunks of size 2^{i+1}.
let chunk_size = 1 << (i + 1);
let half_chunk_size = 1 << i;
input.chunks_mut(chunk_size).for_each(|chunk| {
// We split each chunk in half, calling the first half hi_part and the second hal low_part.
let (hi_part, low_part) = chunk.split_at_mut(half_chunk_size);

// We apply the corresponding butterfly for every element j of the high and low part.
hi_part
.iter_mut()
.zip(low_part)
.enumerate()
.for_each(|(j, (hi, low))| {
let temp = *low * twiddles[i as usize][j];
*low = *hi - temp;
*hi += temp
});
});
});
}

#[cfg(feature = "alloc")]
/// The inverse fft algorithm used to interpolate 2^n points.
/// Input must be of size 2^n for some n.
pub fn icfft(
input: &mut [FieldElement<Mersenne31Field>],
twiddles: Vec<Vec<FieldElement<Mersenne31Field>>>,
) {
// If the input size is 2^n, then log_2_size is n.
let log_2_size = input.len().trailing_zeros();

// The icfft has n layers.
(0..log_2_size).for_each(|i| {
// In each layer i we split the current input in chunks of size 2^{n - i}.
let chunk_size = 1 << (log_2_size - i);
let half_chunk_size = chunk_size >> 1;
input.chunks_mut(chunk_size).for_each(|chunk| {
// We split each chunk in half, calling the first half hi_part and the second hal low_part.
let (hi_part, low_part) = chunk.split_at_mut(half_chunk_size);

// We apply the corresponding butterfly for every element j of the high and low part.
hi_part
.iter_mut()
.zip(low_part)
.enumerate()
.for_each(|(j, (hi, low))| {
let temp = *hi + *low;
*low = (*hi - *low) * twiddles[i as usize][j];
*hi = temp;
});
});
});
}

/// This function permutes a slice of field elements to order the result of the cfft in the natural way.
/// We call the natural order to [P(x0, y0), P(x1, y1), P(x2, y2), ...],
/// where (x0, y0) is the first point of the corresponding coset.
/// The cfft doesn't return the evaluations in the natural order.
/// For example, if we apply the cfft to 8 coefficients of a polynomial of degree 7 we'll get the evaluations in this order:
/// [P(x0, y0), P(x2, y2), P(x4, y4), P(x6, y6), P(x7, y7), P(x5, y5), P(x3, y3), P(x1, y1)],
/// where the even indices are found first in ascending order and then the odd indices in descending order.
/// This function permutes the slice [0, 2, 4, 6, 7, 5, 3, 1] into [0, 1, 2, 3, 4, 5, 6, 7].
/// TODO: This can be optimized by performing in-place value swapping (WIP).
pub fn order_cfft_result_naive(
input: &mut [FieldElement<Mersenne31Field>],
) -> Vec<FieldElement<Mersenne31Field>> {
let mut result = Vec::new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can use interleave

let length = input.len();
for i in 0..length / 2 {
result.push(input[i]); // We push the left index.
result.push(input[length - i - 1]); // We push the right index.
}
result
}

/// This function permutes a slice of field elements to order the input of the icfft in a specific way.
/// For example, if we want to interpolate 8 points we should input them in the icfft in this order:
/// [(x0, y0), (x2, y2), (x4, y4), (x6, y6), (x7, y7), (x5, y5), (x3, y3), (x1, y1)],
/// where the even indices are found first in ascending order and then the odd indices in descending order.
/// This function permutes the slice [0, 1, 2, 3, 4, 5, 6, 7] into [0, 2, 4, 6, 7, 5, 3, 1].
/// TODO: This can be optimized by performing in-place value swapping (WIP).
pub fn order_icfft_input_naive(
input: &mut [FieldElement<Mersenne31Field>],
) -> Vec<FieldElement<Mersenne31Field>> {
let mut result = Vec::new();

// We push the even indices.
(0..input.len()).step_by(2).for_each(|i| {
result.push(input[i]);
});

// We push the odd indices.
(1..input.len()).step_by(2).rev().for_each(|i| {
result.push(input[i]);
});
result
}

#[cfg(test)]
mod tests {
use super::*;
type FE = FieldElement<Mersenne31Field>;

#[test]
fn ordering_cfft_result_works_for_4_points() {
let expected_slice = [FE::from(0), FE::from(1), FE::from(2), FE::from(3)];

let mut slice = [FE::from(0), FE::from(2), FE::from(3), FE::from(1)];

let res = order_cfft_result_naive(&mut slice);

assert_eq!(res, expected_slice)
}

#[test]
fn ordering_cfft_result_works_for_16_points() {
let expected_slice = [
FE::from(0),
FE::from(1),
FE::from(2),
FE::from(3),
FE::from(4),
FE::from(5),
FE::from(6),
FE::from(7),
FE::from(8),
FE::from(9),
FE::from(10),
FE::from(11),
FE::from(12),
FE::from(13),
FE::from(14),
FE::from(15),
];

let mut slice = [
FE::from(0),
FE::from(2),
FE::from(4),
FE::from(6),
FE::from(8),
FE::from(10),
FE::from(12),
FE::from(14),
FE::from(15),
FE::from(13),
FE::from(11),
FE::from(9),
FE::from(7),
FE::from(5),
FE::from(3),
FE::from(1),
];

let res = order_cfft_result_naive(&mut slice);

assert_eq!(res, expected_slice)
}

#[test]
fn from_natural_to_icfft_input_order_works() {
let mut slice = [
FE::from(0),
FE::from(1),
FE::from(2),
FE::from(3),
FE::from(4),
FE::from(5),
FE::from(6),
FE::from(7),
FE::from(8),
FE::from(9),
FE::from(10),
FE::from(11),
FE::from(12),
FE::from(13),
FE::from(14),
FE::from(15),
];

let expected_slice = [
FE::from(0),
FE::from(2),
FE::from(4),
FE::from(6),
FE::from(8),
FE::from(10),
FE::from(12),
FE::from(14),
FE::from(15),
FE::from(13),
FE::from(11),
FE::from(9),
FE::from(7),
FE::from(5),
FE::from(3),
FE::from(1),
];

let res = order_icfft_input_naive(&mut slice);

assert_eq!(res, expected_slice)
}
}
95 changes: 95 additions & 0 deletions math/src/circle/cosets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
extern crate alloc;
use crate::circle::point::CirclePoint;
use crate::field::fields::mersenne31::field::Mersenne31Field;
use alloc::vec::Vec;

/// Given g_n, a generator of the subgroup <g_n> of the circle of size n,
/// and given a shift, that is a another point of the cirvle,
/// we define the coset shift + <g_n> which is the set of all the points in
/// <g_n> plus the shift.
/// For example, if <g_4> = {p1, p2, p3, p4}, then g_8 + <g_4> = {g_8 + p1, g_8 + p2, g_8 + p3, g_8 + p4}.

#[derive(Debug, Clone)]
pub struct Coset {
// Coset: shift + <g_n> where n = 2^{log_2_size}.
// Example: g_16 + <g_8>, n = 8, log_2_size = 3, shift = g_16.
pub log_2_size: u32, //TODO: Change log_2_size to u8 because log_2_size < 31.
pub shift: CirclePoint<Mersenne31Field>,
}

impl Coset {
pub fn new(log_2_size: u32, shift: CirclePoint<Mersenne31Field>) -> Self {
Coset { log_2_size, shift }
}

/// Returns the coset g_2n + <g_n>
pub fn new_standard(log_2_size: u32) -> Self {
// shift is a generator of the subgroup of order 2n = 2^{log_2_size + 1}.
let shift = CirclePoint::get_generator_of_subgroup(log_2_size + 1);
Coset { log_2_size, shift }
}

/// Returns g_n, the generator of the subgroup of order n = 2^log_2_size.
pub fn get_generator(&self) -> CirclePoint<Mersenne31Field> {
CirclePoint::GENERATOR.repeated_double(31 - self.log_2_size)
}

/// Given a standard coset g_2n + <g_n>, returns the subcoset with half size g_2n + <g_{n/2}>
pub fn half_coset(coset: Self) -> Self {
Coset {
log_2_size: coset.log_2_size - 1,
shift: coset.shift,
}
}

/// Given a coset shift + G returns the coset -shift + G.
/// Note that (g_2n + <g_{n/2}>) U (-g_2n + <g_{n/2}>) = g_2n + <g_n>.
pub fn conjugate(coset: Self) -> Self {
Coset {
log_2_size: coset.log_2_size,
shift: coset.shift.conjugate(),
}
}

/// Returns the vector of shift + g for every g in <g_n>.
/// where g = i * g_n for i = 0, ..., n-1.
#[cfg(feature = "alloc")]
pub fn get_coset_points(coset: &Self) -> Vec<CirclePoint<Mersenne31Field>> {
// g_n the generator of the subgroup of order n.
let generator_n = CirclePoint::get_generator_of_subgroup(coset.log_2_size);
let size: u8 = 1 << coset.log_2_size;
core::iter::successors(Some(coset.shift.clone()), move |prev| {
Some(prev + &generator_n)
})
.take(size.into())
.collect()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn coset_points_vector_has_right_size() {
let coset = Coset::new_standard(3);
let points = Coset::get_coset_points(&coset);
assert_eq!(1 << coset.log_2_size, points.len())
}

#[test]
fn antipode_of_coset_point_is_in_coset() {
let coset = Coset::new_standard(3);
let points = Coset::get_coset_points(&coset);
let point = points[2].clone();
let anitpode_point = points[6].clone();
assert_eq!(anitpode_point, point.antipode())
}

#[test]
fn coset_generator_has_right_order() {
let coset = Coset::new(2, CirclePoint::GENERATOR * 3);
let generator_n = coset.get_generator();
assert_eq!(generator_n.repeated_double(2), CirclePoint::zero());
}
}
5 changes: 5 additions & 0 deletions math/src/circle/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod cfft;
pub mod cosets;
pub mod point;
pub mod polynomial;
pub mod twiddles;
Loading
Loading