From c97404191f250620950b500827640cf324ba3351 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Wed, 12 Feb 2025 16:22:21 -0300 Subject: [PATCH 01/37] branch up to date with main --- labrador/src/main.rs | 15 +++++++++ labrador/src/poly_ring.rs | 70 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 labrador/src/poly_ring.rs diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 349b564..a85efa5 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -2,4 +2,19 @@ use labrador::say_hello; fn main() { say_hello(); + // Example poly_ring + const D: usize = 3; // Define the constant d in (x^d + 1) + let p1 = Poly::::create_poly(vec![1, 2]); + let p2 = Poly::::create_poly(vec![1]); + + // Multiply polynomials + let product = p1.mul(&p2); + // Add polynomials + let sum = p1.add(&p2); + // Dot product between coefficients + let dot = p1.inner_product(&p2); + println!("Product: {:?}", product); + println!("sum: {:?}", sum); + println!("dot: {:?}", dot); } + diff --git a/labrador/src/poly_ring.rs b/labrador/src/poly_ring.rs new file mode 100644 index 0000000..bdb8d1b --- /dev/null +++ b/labrador/src/poly_ring.rs @@ -0,0 +1,70 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Poly { + coeffs: [u32; D], +} + +impl Poly { + // Constructor for the polynomial ring + pub fn new(coeffs: [u32; D]) -> Self { + Poly { coeffs } + } + + pub fn add(&self, other: &Self) -> Self { + let mut result = [0u32; D]; + for (r, (a, b)) in result + .iter_mut() + .zip(self.coeffs.iter().zip(other.coeffs.iter())) + { + *r = a.wrapping_add(*b); + } + Poly::new(result) + } + + // Create a Polynomial from a vector + pub fn create_poly(coeffs: Vec) -> Poly { + let mut arr = [0u32; D]; + let u32_coeffs: Vec = coeffs.iter().map(|&coeff| coeff as u32).collect(); + // First D elements are assigned directly + for (i, &u32_coeffs) in u32_coeffs.iter().take(D).enumerate() { + arr[i] = u32_coeffs; + } + + // Handle additional elements by subtracting them at (index % D) + for (i, &u32_coeffs) in u32_coeffs.iter().skip(D).enumerate() { + let mod_index = i % D; + arr[mod_index] = arr[mod_index].wrapping_sub(u32_coeffs); + } + + Poly::new(arr) + } + + // Polynomial multiplication modulo x^D + 1 + pub fn mul(&self, other: &Self) -> Self { + let mut result = [0u32; D]; + + for i in 0..D { + for j in 0..D { + let degree = (i + j) % D; + if (i + j) > D { + result[degree] = + result[degree].wrapping_sub(self.coeffs[i].wrapping_mul(other.coeffs[j])); + } else { + // normal multiplication + result[degree] = + result[degree].wrapping_add(self.coeffs[i].wrapping_mul(other.coeffs[j])); + } + } + } + + Poly::new(result) + } + + // Dot product between coefficients + pub fn inner_product(&self, other: &Self) -> u32 { + self.coeffs + .iter() + .zip(other.coeffs.iter()) + .map(|(&a, &b)| a.wrapping_mul(b)) + .fold(0u32, u32::wrapping_add) + } +} From 0c15b62014f65e4d6bede217be0dd3642ab3d667 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Wed, 12 Feb 2025 16:31:04 -0300 Subject: [PATCH 02/37] up to date with main --- labrador/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index a85efa5..93e79a9 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -16,5 +16,4 @@ fn main() { println!("Product: {:?}", product); println!("sum: {:?}", sum); println!("dot: {:?}", dot); -} - +} \ No newline at end of file From 9c0fafcbb1cd3d44b081d5273b8794bd643c2eb3 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Wed, 12 Feb 2025 16:32:19 -0300 Subject: [PATCH 03/37] up to date with main | fmt solved --- labrador/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 93e79a9..5d1acac 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -16,4 +16,4 @@ fn main() { println!("Product: {:?}", product); println!("sum: {:?}", sum); println!("dot: {:?}", dot); -} \ No newline at end of file +} From db44611d551de44f4de42d628fa6639435b5d2aa Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Wed, 12 Feb 2025 16:34:44 -0300 Subject: [PATCH 04/37] up to date with main --- labrador/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 5d1acac..85f18ee 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -1,5 +1,8 @@ use labrador::say_hello; +mod poly_ring; +use poly_ring::Poly; + fn main() { say_hello(); // Example poly_ring From 52ff5a5b8afd821728499aaef1a100c8347ba78f Mon Sep 17 00:00:00 2001 From: pycckuu Date: Thu, 13 Feb 2025 10:13:35 +0800 Subject: [PATCH 05/37] feat(zq): add modular arithmetic primitive using wrapping operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement Zq struct representing elements in ℤ/(2³²)ℤ ring arithmetic with native u32 operations. Key features: - Fully derived Debug, Clone, Copy, PartialEq, Eq, and Default traits - Implements Add/Sub/Mul operator traits with implicit modulo reduction - Provides Assign variants for in-place operations (AddAssign, etc) - Macro-generated trait impls ensuring DRY principle adherence - Display trait for formatted output in user interfaces - Extensive test coverage including edge cases: - Additive/multiplicative identity properties - Wrapping overflow/underflow behavior - Assignment operator correctness - u32 conversion invariants - Display formatting checks Enables safe, zero-cost abstraction for cryptographic primitives requiring modular arithmetic (e.g., FHE schemes, lattice-based crypto). Leverages: - Rust's wrapping arithmetic for constant-time operations - Type-safe API preventing raw integer misuse Performance characteristics: - All operations compile to single CPU instructions - No heap allocations Issue-URL: https://github.com/NethermindEth/condor-rs/issues/17 --- labrador/src/lib.rs | 2 + labrador/src/main.rs | 4 + labrador/src/zq.rs | 180 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 labrador/src/zq.rs diff --git a/labrador/src/lib.rs b/labrador/src/lib.rs index 63dba27..e1b3b31 100644 --- a/labrador/src/lib.rs +++ b/labrador/src/lib.rs @@ -15,6 +15,8 @@ // Amortization #![doc = include_str!("../../doc/amortization.md")] +pub mod zq; + /// Prints a "Hello, world!" message pub fn say_hello() { println!("Hello, world!"); diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 349b564..fe50000 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -1,5 +1,9 @@ use labrador::say_hello; +use labrador::zq::Zq; fn main() { say_hello(); + let a = Zq::new(5); + let b = Zq::new(3); + println!("a + b = {}", a + b); } diff --git a/labrador/src/zq.rs b/labrador/src/zq.rs new file mode 100644 index 0000000..97d8de3 --- /dev/null +++ b/labrador/src/zq.rs @@ -0,0 +1,180 @@ +use std::fmt; +use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; +/// Represents an element in the ring Z/qZ where q = 2^32. +/// Uses native u32 operations with automatic modulo reduction through wrapping arithmetic. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Zq { + /// Stored value is always in [0, q-1] due to u32's wrapping behavior + value: u32, +} + +impl Zq { + /// Modulus q = 2^32 (stored as 0 in u32 due to wrapping behavior) + pub const Q: u32 = u32::MAX.wrapping_add(1); + + /// Creates a new Zq element from a raw u32 value. + /// No explicit modulo needed as u32 automatically wraps + pub fn new(value: u32) -> Self { + Self { value } + } + + /// Zero element (additive identity) + pub fn zero() -> Self { + Self { value: 0 } + } + + /// Multiplicative identity + pub fn one() -> Self { + Self { value: 1 } + } + + /// Returns the raw u32 value. Use with caution as it's modulo q. + pub fn value(&self) -> u32 { + self.value + } +} + +// Macro to generate arithmetic trait implementations +macro_rules! impl_arithmetic { + ($trait:ident, $assign_trait:ident, $method:ident, $assign_method:ident, $op:ident) => { + impl $trait for Zq { + type Output = Self; + + fn $method(self, rhs: Self) -> Self::Output { + Self::new(self.value.$op(rhs.value)) + } + } + + impl $assign_trait for Zq { + fn $assign_method(&mut self, rhs: Self) { + self.value = self.value.$op(rhs.value); + } + } + }; +} + +impl_arithmetic!(Add, AddAssign, add, add_assign, wrapping_add); +impl_arithmetic!(Sub, SubAssign, sub, sub_assign, wrapping_sub); +impl_arithmetic!(Mul, MulAssign, mul, mul_assign, wrapping_mul); + +impl From for Zq { + fn from(value: u32) -> Self { + Self::new(value) + } +} + +impl fmt::Display for Zq { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Shows value with modulus for clarity + write!(f, "{} (mod 2^32)", self.value()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_arithmetic() { + let a = Zq::new(5); + let b = Zq::new(3); + + // Addition + assert_eq!((a + b).value(), 8, "5 + 3 should be 8"); + // Subtraction + assert_eq!((a - b).value(), 2, "5 - 3 should be 2"); + // Multiplication + assert_eq!((a * b).value(), 15, "5 * 3 should be 15"); + } + + #[test] + fn test_wrapping_arithmetic() { + let a = Zq::new(u32::MAX); + let b = Zq::new(1); + + assert_eq!((a + b).value(), 0, "u32::MAX + 1 should wrap to 0"); + assert_eq!( + (b - a).value(), + 2, + "1 - u32::MAX should wrap to 2 (mod 2^32)" + ); + } + + #[test] + fn test_subtraction_edge_cases() { + let max = Zq::new(u32::MAX); + let one = Zq::new(1); + let two = Zq::new(2); + + assert_eq!((one - max).value(), 2); + assert_eq!((two - max).value(), 3); + assert_eq!((max - max).value(), 0); + } + + #[test] + fn test_multiplication_wrapping() { + let a = Zq::new(1 << 31); + let two = Zq::new(2); + + // Multiplication wraps when exceeding u32 range + assert_eq!((a * two).value(), 0, "2^31 * 2 should wrap to 0"); + } + + #[test] + fn test_assignment_operators() { + let mut a = Zq::new(5); + let b = Zq::new(3); + + a += b; + assert_eq!(a.value(), 8, "5 += 3 should be 8"); + + a -= b; + assert_eq!(a.value(), 5, "8 -= 3 should be 5"); + + a *= b; + assert_eq!(a.value(), 15, "5 *= 3 should be 15"); + } + + #[test] + fn test_conversion_from_u32() { + let a: Zq = 5_u32.into(); + assert_eq!(a.value(), 5, "Conversion from u32 should preserve value"); + } + + #[test] + fn test_negative_arithmetic() { + let small = Zq::new(3); + let large = Zq::new(5); + + // Test underflow handling (3 - 5 in u32 terms) + let result = small - large; + assert_eq!( + result.value(), + u32::MAX - 1, + "3 - 5 should wrap to 2^32 - 2" + ); + + // Test compound negative operations + let mut x = Zq::new(10); + x -= Zq::new(15); + assert_eq!(x.value(), u32::MAX - 4, "10 -= 15 should wrap to 2^32 - 5"); + + // Test negative equivalent value in multiplication + let a = Zq::new(u32::MAX); // Represents -1 in mod 2^32 arithmetic + let b = Zq::new(2); + assert_eq!( + (a * b).value(), + u32::MAX - 1, + "(-1) * 2 should be -2 ≡ 2^32 - 2" + ); + } + + #[test] + fn test_display_implementation() { + let a = Zq::new(5); + let max = Zq::new(u32::MAX); + + assert_eq!(format!("{}", a), "5 (mod 2^32)"); + assert_eq!(format!("{}", max), "4294967295 (mod 2^32)"); + } +} From cb284a254b8d33af4a0ec9f001f3964e209ce98c Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Thu, 13 Feb 2025 20:02:44 -0300 Subject: [PATCH 06/37] poly_ring is now based on Zq --- labrador/src/main.rs | 44 ++++++++-- labrador/src/poly_ring.rs | 177 +++++++++++++++++++++++--------------- 2 files changed, 145 insertions(+), 76 deletions(-) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 9b57d9b..c044109 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -11,19 +11,49 @@ fn main() { const D: usize = 3; // Define the constant d in (x^d + 1) let p1 = Poly::::create_poly(vec![1, 2]); let p2 = Poly::::create_poly(vec![1]); - - // Multiply polynomials + // Perform polynomial multiplication let product = p1.mul(&p2); - // Add polynomials + + // Perform polynomial addition let sum = p1.add(&p2); - // Dot product between coefficients + + // Perform polynomial subtraction + let sub = p1.sub(&p2); + + // Compute the dot product between the polynomial coefficients let dot = p1.inner_product(&p2); + + // Negate the polynomial + let negation = p1.neg(); + + // Perform scalar multiplication + let scalar_multiplication = p1.scalar_mul(Zq::new(2)); + + // Perform division by a monomial + let monomial_division = p1.div_by_monomial(2); + + // Evaluate the polynomial at x = 2 + let evaluation = p1.eval(Zq::new(2)); + + // Check if the polynomial is the zero polynomial + let zero_check = p1.is_zero(); + + // Check if p1 is equal to p2 + let are_equal = p1.is_equal(&p2); + + // Print the results println!("Product: {:?}", product); - println!("sum: {:?}", sum); - println!("dot: {:?}", dot); + println!("Sum: {:?}", sum); + println!("Subtraction: {:?}", sub); + println!("Dot product: {:?}", dot); + println!("Negation: {:?}", negation); + println!("Scalar multiplication: {:?}", scalar_multiplication); + println!("Monomial division: {:?}", monomial_division); + println!("Evaluation at x=2: {:?}", evaluation); + println!("Is zero polynomial: {:?}", zero_check); + println!("Are polynomials equal: {:?}", are_equal); let a = Zq::new(5); let b = Zq::new(3); println!("a + b = {}", a + b); - } diff --git a/labrador/src/poly_ring.rs b/labrador/src/poly_ring.rs index 0a71d0d..85e2adb 100644 --- a/labrador/src/poly_ring.rs +++ b/labrador/src/poly_ring.rs @@ -7,7 +7,7 @@ // - Polynomial addition: add() // - Polynomial multiplication: mul() // - inner_product/ Dot product: inner_product() -// - Polynomial substraction: sub() +// - Polynomial subtraction: sub() // - Polynomial negation: neg() // - Scalar multiplication: scalar_mul() // - Division by monomials: div_by_monomial() @@ -17,54 +17,57 @@ // // Further operations and optimizations will be added in future versions. +// We use the Zq ring +use labrador::zq::Zq; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Poly { - coeffs: [u32; D], + coeffs: [Zq; D], } impl Poly { // Constructor for the polynomial ring - pub fn new(coeffs: [u32; D]) -> Self { + pub fn new(coeffs: [Zq; D]) -> Self { Poly { coeffs } } // Polynomial addition pub fn add(&self, other: &Self) -> Self { - let mut result = [0u32; D]; + let mut result = [Zq::zero(); D]; for (r, (a, b)) in result .iter_mut() .zip(self.coeffs.iter().zip(other.coeffs.iter())) { - *r = a.wrapping_add(*b); + *r = *a + *b; } Poly::new(result) } - // Polynomial substraction + // Polynomial subtraction pub fn sub(&self, other: &Self) -> Self { - let mut result = [0u32; D]; + let mut result = [Zq::zero(); D]; for (r, (a, b)) in result .iter_mut() .zip(self.coeffs.iter().zip(other.coeffs.iter())) { - *r = a.wrapping_sub(*b); + *r = *a - *b; } Poly::new(result) } // Create a Polynomial from a vector pub fn create_poly(coeffs: Vec) -> Poly { - let mut arr = [0u32; D]; + let mut arr = [Zq::zero(); D]; let u32_coeffs: Vec = coeffs.iter().map(|&coeff| coeff as u32).collect(); // First D elements are assigned directly for (i, &u32_coeffs) in u32_coeffs.iter().take(D).enumerate() { - arr[i] = u32_coeffs; + arr[i] = Zq::new(u32_coeffs); } // Handle additional elements by subtracting them at (index % D) for (i, &u32_coeffs) in u32_coeffs.iter().skip(D).enumerate() { let mod_index = i % D; - arr[mod_index] = arr[mod_index].wrapping_sub(u32_coeffs); + arr[mod_index] = arr[mod_index] - (Zq::new(u32_coeffs)); } Poly::new(arr) @@ -72,18 +75,16 @@ impl Poly { // Polynomial multiplication modulo x^D + 1 pub fn mul(&self, other: &Self) -> Self { - let mut result = [0u32; D]; + let mut result = [Zq::zero(); D]; for i in 0..D { for j in 0..D { let degree = (i + j) % D; if (i + j) > D { - result[degree] = - result[degree].wrapping_sub(self.coeffs[i].wrapping_mul(other.coeffs[j])); + result[degree] = result[degree] - (self.coeffs[i] * (other.coeffs[j])); } else { // normal multiplication - result[degree] = - result[degree].wrapping_add(self.coeffs[i].wrapping_mul(other.coeffs[j])); + result[degree] = result[degree] - (self.coeffs[i] * (other.coeffs[j])); } } } @@ -92,37 +93,37 @@ impl Poly { } // Dot product between coefficients - pub fn inner_product(&self, other: &Self) -> u32 { + pub fn inner_product(&self, other: &Self) -> Zq { self.coeffs .iter() .zip(other.coeffs.iter()) - .map(|(&a, &b)| a.wrapping_mul(b)) - .fold(0u32, u32::wrapping_add) + .map(|(&a, &b)| a * (b)) + .fold(Zq::zero(), |acc, x| acc + x) } // Polynomial negation pub fn neg(&self) -> Self { - let mut result = [0u32; D]; + let mut result = [Zq::zero(); D]; for (i, &coeff) in self.coeffs.iter().enumerate() { - result[i] = coeff.wrapping_neg(); + result[i] = Zq::zero() - coeff; } Poly::new(result) } // Scalar multiplication - pub fn scalar_mul(&self, s: u32) -> Self { - let mut result = [0u32; D]; + pub fn scalar_mul(&self, s: Zq) -> Self { + let mut result = [Zq::zero(); D]; for (i, &coeff) in self.coeffs.iter().enumerate() { - result[i] = s.wrapping_mul(coeff); + result[i] = s * (coeff); } Poly::new(result) } - // (Division by monomials) X^k | performing a cyclic right shift + // (Division by monomial) X^k | performing a cyclic right shift pub fn div_by_monomial(&self, k: usize) -> Self { - let mut result = [0u32; D]; + let mut result = [Zq::zero(); D]; for (i, &coeff) in self.coeffs.iter().enumerate() { - // Calculate the new index with wrap-around (k-shift) + // (k-shift) let new_index = (i + k) % D; result[new_index] = coeff; } @@ -130,12 +131,12 @@ impl Poly { } // Evaluate the polynomial at a specific point - pub fn eval(&self, x: u32) -> u32 { - let mut result = 0u32; - let mut power = 1u32; + pub fn eval(&self, x: Zq) -> Zq { + let mut result = Zq::zero(); + let mut power = Zq::one(); for &coeff in &self.coeffs { - result = result.wrapping_add(coeff.wrapping_mul(power)); - power = power.wrapping_mul(x); + result = result + (coeff * (power)); + power = power * (x); } result @@ -143,7 +144,7 @@ impl Poly { // Check if Polynomial == 0 pub fn is_zero(&self) -> bool { - self.coeffs.iter().all(|&coeff| coeff == 0) + self.coeffs.iter().all(|&coeff| coeff == Zq::zero()) } // Check if two polynomials are equal @@ -156,22 +157,33 @@ impl Poly { mod tests { use super::*; - // Test constructor and polynomial creation + // Test new() and polynomial creation #[test] fn test_new_and_create_poly() { - let poly = Poly::new([1, 2, 3, 4]); - assert_eq!(poly.coeffs, [1, 2, 3, 4]); + let poly = Poly::new([Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)]); + assert_eq!( + poly.coeffs, + [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] + ); - // vector positive coefficients + // Positive coefficients let poly_from_vec = Poly::<4>::create_poly(vec![1, 2, 3, 4]); - assert_eq!(poly_from_vec.coeffs, [1, 2, 3, 4]); + assert_eq!( + poly_from_vec.coeffs, + [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] + ); - // negative coefficients + // Negative coefficients let poly_from_vec_negative = Poly::<4>::create_poly(vec![-1, -2, -3, -4]); let u32_max = u32::MAX; assert_eq!( poly_from_vec_negative.coeffs, - [u32_max, u32_max - 1, u32_max - 2, u32_max - 3] + [ + Zq::new(u32_max), + Zq::new(u32_max - 1), + Zq::new(u32_max - 2), + Zq::new(u32_max - 3) + ] ); } @@ -179,76 +191,103 @@ mod tests { #[test] fn test_add() { // within bounds - let poly1 = Poly::new([1, 2, 3, 4]); - let poly2 = Poly::new([4, 3, 2, 1]); + let poly1 = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let poly2 = Poly::<4>::create_poly(vec![4, 3, 2, 1]); let result = poly1.add(&poly2); - assert_eq!(result.coeffs, [5, 5, 5, 5]); + assert_eq!( + result.coeffs, + [Zq::new(5), Zq::new(5), Zq::new(5), Zq::new(5)] + ); // Outside of bounds - let poly3 = Poly::new([1, 2, 3, 4]); - let poly4 = Poly::new([u32::MAX, 3, u32::MAX, 1]); + let poly3 = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let poly4 = Poly::<4>::new([Zq::new(u32::MAX), Zq::new(3), Zq::new(u32::MAX), Zq::new(1)]); let result2 = poly3.add(&poly4); - assert_eq!(result2.coeffs, [0, 5, 2, 5]); + assert_eq!( + result2.coeffs, + [Zq::new(0), Zq::new(5), Zq::new(2), Zq::new(5)] + ); } // Test subtraction of polynomials #[test] fn test_sub() { - let poly1 = Poly::new([5, 10, 15, 20]); - let poly2 = Poly::new([2, 4, 6, 8]); + // within bounds + let poly1 = Poly::<4>::create_poly(vec![5, 10, 15, 20]); + let poly2 = Poly::<4>::create_poly(vec![2, 4, 6, 8]); let result = poly1.sub(&poly2); - assert_eq!(result.coeffs, [3, 6, 9, 12]); + assert_eq!( + result.coeffs, + [Zq::new(3), Zq::new(6), Zq::new(9), Zq::new(12)] + ); - let poly3 = Poly::new([1, 1, 3, 2]); - let poly4 = Poly::new([2, 4, 6, 8]); + // Outside of bounds + let poly3 = Poly::<4>::create_poly(vec![1, 1, 3, 2]); + let poly4 = Poly::<4>::create_poly(vec![2, 4, 6, 8]); let result2 = poly3.sub(&poly4); assert_eq!( result2.coeffs, - [u32::MAX, u32::MAX - 2, u32::MAX - 2, u32::MAX - 5] + [ + Zq::new(u32::MAX), + Zq::new(u32::MAX - 2), + Zq::new(u32::MAX - 2), + Zq::new(u32::MAX - 5) + ] ); } // Test negation of polynomial #[test] fn test_neg() { - let poly = Poly::new([1, 2, 3, 4]); + let poly = Poly::<4>::create_poly(vec![1, 2, 3, 4]); let result = poly.neg(); assert_eq!( result.coeffs, - [u32::MAX, u32::MAX - 1, u32::MAX - 2, u32::MAX - 3] + [ + Zq::new(u32::MAX), + Zq::new(u32::MAX - 1), + Zq::new(u32::MAX - 2), + Zq::new(u32::MAX - 3) + ] ); } // Test scalar multiplication #[test] fn test_scalar_mul() { - let poly = Poly::new([1, 2, 3, 4]); - let result = poly.scalar_mul(2); - assert_eq!(result.coeffs, [2, 4, 6, 8]); + let poly = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let result = poly.scalar_mul(Zq::new(2)); + assert_eq!( + result.coeffs, + [Zq::new(2), Zq::new(4), Zq::new(6), Zq::new(8)] + ); } // Test division by monomials #[test] fn test_div_by_monomial() { - let poly = Poly::new([1, 2, 3, 4]); + let poly = Poly::<4>::create_poly(vec![1, 2, 3, 4]); let result = poly.div_by_monomial(2); - assert_eq!(result.coeffs, [3, 4, 1, 2]); + assert_eq!( + result.coeffs, + [Zq::new(3), Zq::new(4), Zq::new(1), Zq::new(2)] + ); } // Test polynomial evaluation #[test] fn test_eval() { - let poly = Poly::new([1, 2, 3, 4]); - let result = poly.eval(2); - assert_eq!(result, 49); + let poly = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let result = poly.eval(Zq::new(2)); + assert_eq!(result, Zq::new(49)); } // Test equality check #[test] fn test_is_equal() { - let poly1 = Poly::new([1, 2, 3, 4]); - let poly2 = Poly::new([1, 2, 3, 4]); - let poly3 = Poly::new([4, 3, 2, 1]); + let poly1 = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let poly2 = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let poly3 = Poly::<4>::create_poly(vec![4, 3, 2, 1]); assert!(poly1.is_equal(&poly2)); assert!(!poly1.is_equal(&poly3)); } @@ -256,9 +295,9 @@ mod tests { // Test zero polynomial check #[test] fn test_is_zero_poly() { - let zero_poly = Poly::new([0, 0, 0, 0]); - let non_zero_poly = Poly::new([1, 0, 0, 0]); - assert_eq!(zero_poly.is_zero(), true); - assert_eq!(non_zero_poly.is_zero(), false); + let zero_poly = Poly::<4>::create_poly(vec![0, 0, 0, 0]); + let non_zero_poly = Poly::<4>::create_poly(vec![1, 0, 0, 0]); + assert!(zero_poly.is_zero()); + assert!(!non_zero_poly.is_zero()); } } From ded244c7d40bee3b2a504d3f136f8e4f5506cf3b Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Thu, 13 Feb 2025 20:06:07 -0300 Subject: [PATCH 07/37] Clippy error fixed --- labrador/src/poly_ring.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/labrador/src/poly_ring.rs b/labrador/src/poly_ring.rs index 85e2adb..5e67e32 100644 --- a/labrador/src/poly_ring.rs +++ b/labrador/src/poly_ring.rs @@ -67,7 +67,7 @@ impl Poly { // Handle additional elements by subtracting them at (index % D) for (i, &u32_coeffs) in u32_coeffs.iter().skip(D).enumerate() { let mod_index = i % D; - arr[mod_index] = arr[mod_index] - (Zq::new(u32_coeffs)); + arr[mod_index] -= (Zq::new(u32_coeffs)); } Poly::new(arr) @@ -81,10 +81,10 @@ impl Poly { for j in 0..D { let degree = (i + j) % D; if (i + j) > D { - result[degree] = result[degree] - (self.coeffs[i] * (other.coeffs[j])); + result[degree] -= (self.coeffs[i] * (other.coeffs[j])); } else { // normal multiplication - result[degree] = result[degree] - (self.coeffs[i] * (other.coeffs[j])); + result[degree] -= (self.coeffs[i] * (other.coeffs[j])); } } } @@ -135,8 +135,8 @@ impl Poly { let mut result = Zq::zero(); let mut power = Zq::one(); for &coeff in &self.coeffs { - result = result + (coeff * (power)); - power = power * (x); + result += (coeff * (power)); + power *= (x); } result From f49e374136329afaacb081e634fe15445e711906 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Thu, 13 Feb 2025 20:10:09 -0300 Subject: [PATCH 08/37] other clippy error fixed --- labrador/src/poly_ring.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/labrador/src/poly_ring.rs b/labrador/src/poly_ring.rs index 5e67e32..a34d772 100644 --- a/labrador/src/poly_ring.rs +++ b/labrador/src/poly_ring.rs @@ -67,7 +67,7 @@ impl Poly { // Handle additional elements by subtracting them at (index % D) for (i, &u32_coeffs) in u32_coeffs.iter().skip(D).enumerate() { let mod_index = i % D; - arr[mod_index] -= (Zq::new(u32_coeffs)); + arr[mod_index] -= Zq::new(u32_coeffs); } Poly::new(arr) @@ -81,10 +81,10 @@ impl Poly { for j in 0..D { let degree = (i + j) % D; if (i + j) > D { - result[degree] -= (self.coeffs[i] * (other.coeffs[j])); + result[degree] -= self.coeffs[i] * other.coeffs[j]; } else { // normal multiplication - result[degree] -= (self.coeffs[i] * (other.coeffs[j])); + result[degree] -= self.coeffs[i] * other.coeffs[j]; } } } @@ -135,8 +135,8 @@ impl Poly { let mut result = Zq::zero(); let mut power = Zq::one(); for &coeff in &self.coeffs { - result += (coeff * (power)); - power *= (x); + result += coeff * power; + power *= x; } result From 7c3248b7fcae6472fbcc179bdbf2566458d89938 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Fri, 14 Feb 2025 10:31:19 -0300 Subject: [PATCH 09/37] struct renamed to Rq --- labrador/src/main.rs | 6 ++-- labrador/src/poly_ring.rs | 62 +++++++++++++++++++-------------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index c044109..0112b97 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -2,15 +2,15 @@ use labrador::say_hello; use labrador::zq::Zq; mod poly_ring; -use poly_ring::Poly; +use poly_ring::Rq; fn main() { say_hello(); // Example poly_ring const D: usize = 3; // Define the constant d in (x^d + 1) - let p1 = Poly::::create_poly(vec![1, 2]); - let p2 = Poly::::create_poly(vec![1]); + let p1 = Rq::::create_poly(vec![1, 2]); + let p2 = Rq::::create_poly(vec![1]); // Perform polynomial multiplication let product = p1.mul(&p2); diff --git a/labrador/src/poly_ring.rs b/labrador/src/poly_ring.rs index a34d772..6e23de7 100644 --- a/labrador/src/poly_ring.rs +++ b/labrador/src/poly_ring.rs @@ -21,14 +21,14 @@ use labrador::zq::Zq; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Poly { +pub struct Rq { coeffs: [Zq; D], } -impl Poly { +impl Rq { // Constructor for the polynomial ring pub fn new(coeffs: [Zq; D]) -> Self { - Poly { coeffs } + Rq { coeffs } } // Polynomial addition @@ -40,7 +40,7 @@ impl Poly { { *r = *a + *b; } - Poly::new(result) + Rq::new(result) } // Polynomial subtraction @@ -52,11 +52,11 @@ impl Poly { { *r = *a - *b; } - Poly::new(result) + Rq::new(result) } // Create a Polynomial from a vector - pub fn create_poly(coeffs: Vec) -> Poly { + pub fn create_poly(coeffs: Vec) -> Rq { let mut arr = [Zq::zero(); D]; let u32_coeffs: Vec = coeffs.iter().map(|&coeff| coeff as u32).collect(); // First D elements are assigned directly @@ -70,7 +70,7 @@ impl Poly { arr[mod_index] -= Zq::new(u32_coeffs); } - Poly::new(arr) + Rq::new(arr) } // Polynomial multiplication modulo x^D + 1 @@ -89,7 +89,7 @@ impl Poly { } } - Poly::new(result) + Rq::new(result) } // Dot product between coefficients @@ -107,7 +107,7 @@ impl Poly { for (i, &coeff) in self.coeffs.iter().enumerate() { result[i] = Zq::zero() - coeff; } - Poly::new(result) + Rq::new(result) } // Scalar multiplication @@ -116,7 +116,7 @@ impl Poly { for (i, &coeff) in self.coeffs.iter().enumerate() { result[i] = s * (coeff); } - Poly::new(result) + Rq::new(result) } // (Division by monomial) X^k | performing a cyclic right shift @@ -127,7 +127,7 @@ impl Poly { let new_index = (i + k) % D; result[new_index] = coeff; } - Poly::new(result) + Rq::new(result) } // Evaluate the polynomial at a specific point @@ -160,21 +160,21 @@ mod tests { // Test new() and polynomial creation #[test] fn test_new_and_create_poly() { - let poly = Poly::new([Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)]); + let poly = Rq::new([Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)]); assert_eq!( poly.coeffs, [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] ); // Positive coefficients - let poly_from_vec = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let poly_from_vec = Rq::<4>::create_poly(vec![1, 2, 3, 4]); assert_eq!( poly_from_vec.coeffs, [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] ); // Negative coefficients - let poly_from_vec_negative = Poly::<4>::create_poly(vec![-1, -2, -3, -4]); + let poly_from_vec_negative = Rq::<4>::create_poly(vec![-1, -2, -3, -4]); let u32_max = u32::MAX; assert_eq!( poly_from_vec_negative.coeffs, @@ -191,8 +191,8 @@ mod tests { #[test] fn test_add() { // within bounds - let poly1 = Poly::<4>::create_poly(vec![1, 2, 3, 4]); - let poly2 = Poly::<4>::create_poly(vec![4, 3, 2, 1]); + let poly1 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly2 = Rq::<4>::create_poly(vec![4, 3, 2, 1]); let result = poly1.add(&poly2); assert_eq!( result.coeffs, @@ -200,8 +200,8 @@ mod tests { ); // Outside of bounds - let poly3 = Poly::<4>::create_poly(vec![1, 2, 3, 4]); - let poly4 = Poly::<4>::new([Zq::new(u32::MAX), Zq::new(3), Zq::new(u32::MAX), Zq::new(1)]); + let poly3 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly4 = Rq::<4>::new([Zq::new(u32::MAX), Zq::new(3), Zq::new(u32::MAX), Zq::new(1)]); let result2 = poly3.add(&poly4); assert_eq!( result2.coeffs, @@ -213,8 +213,8 @@ mod tests { #[test] fn test_sub() { // within bounds - let poly1 = Poly::<4>::create_poly(vec![5, 10, 15, 20]); - let poly2 = Poly::<4>::create_poly(vec![2, 4, 6, 8]); + let poly1 = Rq::<4>::create_poly(vec![5, 10, 15, 20]); + let poly2 = Rq::<4>::create_poly(vec![2, 4, 6, 8]); let result = poly1.sub(&poly2); assert_eq!( result.coeffs, @@ -222,8 +222,8 @@ mod tests { ); // Outside of bounds - let poly3 = Poly::<4>::create_poly(vec![1, 1, 3, 2]); - let poly4 = Poly::<4>::create_poly(vec![2, 4, 6, 8]); + let poly3 = Rq::<4>::create_poly(vec![1, 1, 3, 2]); + let poly4 = Rq::<4>::create_poly(vec![2, 4, 6, 8]); let result2 = poly3.sub(&poly4); assert_eq!( result2.coeffs, @@ -239,7 +239,7 @@ mod tests { // Test negation of polynomial #[test] fn test_neg() { - let poly = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); let result = poly.neg(); assert_eq!( result.coeffs, @@ -255,7 +255,7 @@ mod tests { // Test scalar multiplication #[test] fn test_scalar_mul() { - let poly = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); let result = poly.scalar_mul(Zq::new(2)); assert_eq!( result.coeffs, @@ -266,7 +266,7 @@ mod tests { // Test division by monomials #[test] fn test_div_by_monomial() { - let poly = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); let result = poly.div_by_monomial(2); assert_eq!( result.coeffs, @@ -277,7 +277,7 @@ mod tests { // Test polynomial evaluation #[test] fn test_eval() { - let poly = Poly::<4>::create_poly(vec![1, 2, 3, 4]); + let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); let result = poly.eval(Zq::new(2)); assert_eq!(result, Zq::new(49)); } @@ -285,9 +285,9 @@ mod tests { // Test equality check #[test] fn test_is_equal() { - let poly1 = Poly::<4>::create_poly(vec![1, 2, 3, 4]); - let poly2 = Poly::<4>::create_poly(vec![1, 2, 3, 4]); - let poly3 = Poly::<4>::create_poly(vec![4, 3, 2, 1]); + let poly1 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly2 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly3 = Rq::<4>::create_poly(vec![4, 3, 2, 1]); assert!(poly1.is_equal(&poly2)); assert!(!poly1.is_equal(&poly3)); } @@ -295,8 +295,8 @@ mod tests { // Test zero polynomial check #[test] fn test_is_zero_poly() { - let zero_poly = Poly::<4>::create_poly(vec![0, 0, 0, 0]); - let non_zero_poly = Poly::<4>::create_poly(vec![1, 0, 0, 0]); + let zero_poly = Rq::<4>::create_poly(vec![0, 0, 0, 0]); + let non_zero_poly = Rq::<4>::create_poly(vec![1, 0, 0, 0]); assert!(zero_poly.is_zero()); assert!(!non_zero_poly.is_zero()); } From 426ea9f349f60dbc4053525487b951906aa0518c Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Sun, 16 Feb 2025 23:52:25 -0300 Subject: [PATCH 10/37] macro for direct arithmetic and From/into function --- labrador/src/main.rs | 10 +- labrador/src/rq.rs | 347 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 352 insertions(+), 5 deletions(-) create mode 100644 labrador/src/rq.rs diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 0112b97..529d93e 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -1,8 +1,8 @@ use labrador::say_hello; use labrador::zq::Zq; -mod poly_ring; -use poly_ring::Rq; +mod rq; +use rq::Rq; fn main() { say_hello(); @@ -12,13 +12,13 @@ fn main() { let p1 = Rq::::create_poly(vec![1, 2]); let p2 = Rq::::create_poly(vec![1]); // Perform polynomial multiplication - let product = p1.mul(&p2); + let product = p1 * p2; // Perform polynomial addition - let sum = p1.add(&p2); + let sum = p1 + p2; // Perform polynomial subtraction - let sub = p1.sub(&p2); + let sub = p1 - p2; // Compute the dot product between the polynomial coefficients let dot = p1.inner_product(&p2); diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs new file mode 100644 index 0000000..a13ea30 --- /dev/null +++ b/labrador/src/rq.rs @@ -0,0 +1,347 @@ +// This file is part of the polynomial ring operations module. +// +// This module provides implementations for various operations +// in the polynomial ring R = Z_q[X] / (X^d + 1). +// +// Currently implemented functions include: +// - Polynomial addition: + +// - Polynomial multiplication: * +// - inner_product/ Dot product: inner_product() +// - Polynomial subtraction: - +// - Polynomial negation: neg() +// - Scalar multiplication: scalar_mul() +// - Division by monomials: div_by_monomial() +// - Polynomial evaluation: eval() +// - Zero check: is_zero() +// - Polynomial equality check: is_equal() +// +// Further operations and optimizations will be added in future versions. + +// We use the Zq ring +use labrador::zq::Zq; +use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Rq { + coeffs: [Zq; D], +} + +impl Rq { + // Constructor for the polynomial ring + pub fn new(coeffs: [Zq; D]) -> Self { + Rq { coeffs } + } + + // Polynomial addition + pub fn _add(&self, other: &Self) -> Self { + let mut result = [Zq::zero(); D]; + for (r, (a, b)) in result + .iter_mut() + .zip(self.coeffs.iter().zip(other.coeffs.iter())) + { + *r = *a + *b; + } + Rq::new(result) + } + + // Polynomial subtraction + pub fn _sub(&self, other: &Self) -> Self { + let mut result = [Zq::zero(); D]; + for (r, (a, b)) in result + .iter_mut() + .zip(self.coeffs.iter().zip(other.coeffs.iter())) + { + *r = *a - *b; + } + Rq::new(result) + } + + // Create a Polynomial from a vector + pub fn create_poly(coeffs: Vec) -> Rq { + let mut arr = [Zq::zero(); D]; + let u32_coeffs: Vec = coeffs.iter().map(|&coeff| coeff as u32).collect(); + // First D elements are assigned directly + for (i, &u32_coeffs) in u32_coeffs.iter().take(D).enumerate() { + arr[i] = Zq::new(u32_coeffs); + } + + // Handle additional elements by subtracting them at (index % D) + for (i, &u32_coeffs) in u32_coeffs.iter().skip(D).enumerate() { + let mod_index = i % D; + arr[mod_index] -= Zq::new(u32_coeffs); + } + + Rq::new(arr) + } + + // Polynomial multiplication modulo x^D + 1 + pub fn _mul(&self, other: &Self) -> Self { + let mut result = [Zq::zero(); D]; + + for i in 0..D { + for j in 0..D { + let degree = (i + j) % D; + if (i + j) > D { + result[degree] -= self.coeffs[i] * other.coeffs[j]; + } else { + // normal multiplication + result[degree] -= self.coeffs[i] * other.coeffs[j]; + } + } + } + + Rq::new(result) + } + + // Dot product between coefficients + pub fn inner_product(&self, other: &Self) -> Zq { + self.coeffs + .iter() + .zip(other.coeffs.iter()) + .map(|(&a, &b)| a * (b)) + .fold(Zq::zero(), |acc, x| acc + x) + } + + // Polynomial negation + pub fn neg(&self) -> Self { + let mut result = [Zq::zero(); D]; + for (i, &coeff) in self.coeffs.iter().enumerate() { + result[i] = Zq::zero() - coeff; + } + Rq::new(result) + } + + // Scalar multiplication + pub fn scalar_mul(&self, s: Zq) -> Self { + let mut result = [Zq::zero(); D]; + for (i, &coeff) in self.coeffs.iter().enumerate() { + result[i] = s * (coeff); + } + Rq::new(result) + } + + // (Division by monomial) X^k | performing a cyclic right shift + pub fn div_by_monomial(&self, k: usize) -> Self { + let mut result = [Zq::zero(); D]; + for (i, &coeff) in self.coeffs.iter().enumerate() { + // (k-shift) + let new_index = (i + k) % D; + result[new_index] = coeff; + } + Rq::new(result) + } + + // Evaluate the polynomial at a specific point + pub fn eval(&self, x: Zq) -> Zq { + let mut result = Zq::zero(); + let mut power = Zq::one(); + for &coeff in &self.coeffs { + result += coeff * power; + power *= x; + } + + result + } + + // Check if Polynomial == 0 + pub fn is_zero(&self) -> bool { + self.coeffs.iter().all(|&coeff| coeff == Zq::zero()) + } + + // Check if two polynomials are equal + pub fn is_equal(&self, other: &Self) -> bool { + self.coeffs == other.coeffs + } +} + +macro_rules! impl_arithmetic { + ($trait:ident, $assign_trait:ident, $method:ident, $assign_method:ident, $op_method:ident) => { + impl $trait for Rq<{ D }> { + type Output = Self; + + fn $method(self, rhs: Self) -> Self::Output { + self.$op_method(&rhs) // Pass rhs by value + } + } + + impl $assign_trait for Rq<{ D }> { + fn $assign_method(&mut self, rhs: Self) { + let result = self.$op_method(&rhs); // Use your custom method + self.coeffs = result.coeffs; // Assign the resulting coefficients + } + } + }; +} + +// Implement the traits using your custom methods +impl_arithmetic!(Add, AddAssign, add, add_assign, _add); +impl_arithmetic!(Sub, SubAssign, sub, sub_assign, _sub); +impl_arithmetic!(Mul, MulAssign, mul, mul_assign, _mul); + +impl From> for Rq { + fn from(vec: Vec) -> Self { + let mut temp = [Zq::zero(); D]; + // Process excess terms with sign adjustment + for i in (D..vec.len()).rev() { + let m = i / D; + let r = i % D; + let sign = if m % 2 == 0 { 1 } else { -1 }; + if sign == 1 { + temp[r] += vec[i]; + } else { + temp[r] -= vec[i]; + } + } + let coeffs = temp[0..D].try_into().unwrap(); + Rq::new(coeffs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Test new() and polynomial creation + #[test] + fn test_new_and_create_poly() { + let poly = Rq::new([Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)]); + assert_eq!( + poly.coeffs, + [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] + ); + + // Positive coefficients + let poly_from_vec = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + assert_eq!( + poly_from_vec.coeffs, + [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] + ); + + // Negative coefficients + let poly_from_vec_negative = Rq::<4>::create_poly(vec![-1, -2, -3, -4]); + let u32_max = u32::MAX; + assert_eq!( + poly_from_vec_negative.coeffs, + [ + Zq::new(u32_max), + Zq::new(u32_max - 1), + Zq::new(u32_max - 2), + Zq::new(u32_max - 3) + ] + ); + } + + // Test addition of polynomials + #[test] + fn test_add() { + // within bounds + let poly1 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly2 = Rq::<4>::create_poly(vec![4, 3, 2, 1]); + let result = poly1 + poly2; + assert_eq!( + result.coeffs, + [Zq::new(5), Zq::new(5), Zq::new(5), Zq::new(5)] + ); + + // Outside of bounds + let poly3 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly4 = Rq::<4>::new([Zq::new(u32::MAX), Zq::new(3), Zq::new(u32::MAX), Zq::new(1)]); + let result2 = poly3 + poly4; + assert_eq!( + result2.coeffs, + [Zq::new(0), Zq::new(5), Zq::new(2), Zq::new(5)] + ); + } + + // Test subtraction of polynomials + #[test] + fn test_sub() { + // within bounds + let poly1 = Rq::<4>::create_poly(vec![5, 10, 15, 20]); + let poly2 = Rq::<4>::create_poly(vec![2, 4, 6, 8]); + let result = poly1 - poly2; + assert_eq!( + result.coeffs, + [Zq::new(3), Zq::new(6), Zq::new(9), Zq::new(12)] + ); + + // Outside of bounds + let poly3 = Rq::<4>::create_poly(vec![1, 1, 3, 2]); + let poly4 = Rq::<4>::create_poly(vec![2, 4, 6, 8]); + let result2 = poly3 - poly4; + assert_eq!( + result2.coeffs, + [ + Zq::new(u32::MAX), + Zq::new(u32::MAX - 2), + Zq::new(u32::MAX - 2), + Zq::new(u32::MAX - 5) + ] + ); + } + + // Test negation of polynomial + #[test] + fn test_neg() { + let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let result = poly.neg(); + assert_eq!( + result.coeffs, + [ + Zq::new(u32::MAX), + Zq::new(u32::MAX - 1), + Zq::new(u32::MAX - 2), + Zq::new(u32::MAX - 3) + ] + ); + } + + // Test scalar multiplication + #[test] + fn test_scalar_mul() { + let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let result = poly.scalar_mul(Zq::new(2)); + assert_eq!( + result.coeffs, + [Zq::new(2), Zq::new(4), Zq::new(6), Zq::new(8)] + ); + } + + // Test division by monomials + #[test] + fn test_div_by_monomial() { + let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let result = poly.div_by_monomial(2); + assert_eq!( + result.coeffs, + [Zq::new(3), Zq::new(4), Zq::new(1), Zq::new(2)] + ); + } + + // Test polynomial evaluation + #[test] + fn test_eval() { + let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let result = poly.eval(Zq::new(2)); + assert_eq!(result, Zq::new(49)); + } + + // Test equality check + #[test] + fn test_is_equal() { + let poly1 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly2 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly3 = Rq::<4>::create_poly(vec![4, 3, 2, 1]); + assert!(poly1.is_equal(&poly2)); + assert!(!poly1.is_equal(&poly3)); + } + + // Test zero polynomial check + #[test] + fn test_is_zero_poly() { + let zero_poly = Rq::<4>::create_poly(vec![0, 0, 0, 0]); + let non_zero_poly = Rq::<4>::create_poly(vec![1, 0, 0, 0]); + assert!(zero_poly.is_zero()); + assert!(!non_zero_poly.is_zero()); + } +} From f1fcf19201c4e12ca970e626ee1004d783558178 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Mon, 17 Feb 2025 13:33:17 -0300 Subject: [PATCH 11/37] trait From implemented and polynomial multiplication fixed --- labrador/src/main.rs | 5 +- labrador/src/poly_ring.rs | 303 -------------------------------------- labrador/src/rq.rs | 100 ++++--------- 3 files changed, 31 insertions(+), 377 deletions(-) delete mode 100644 labrador/src/poly_ring.rs diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 529d93e..e63c141 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -8,9 +8,8 @@ fn main() { say_hello(); // Example poly_ring - const D: usize = 3; // Define the constant d in (x^d + 1) - let p1 = Rq::::create_poly(vec![1, 2]); - let p2 = Rq::::create_poly(vec![1]); + let p1: Rq<2> = vec![Zq::new(1), Zq::new(2)].into(); + let p2: Rq<2> = vec![Zq::new(1), Zq::new(1)].into(); // Perform polynomial multiplication let product = p1 * p2; diff --git a/labrador/src/poly_ring.rs b/labrador/src/poly_ring.rs deleted file mode 100644 index 6e23de7..0000000 --- a/labrador/src/poly_ring.rs +++ /dev/null @@ -1,303 +0,0 @@ -// This file is part of the polynomial ring operations module. -// -// This module provides implementations for various operations -// in the polynomial ring R = Z_q[X] / (X^d + 1). -// -// Currently implemented functions include: -// - Polynomial addition: add() -// - Polynomial multiplication: mul() -// - inner_product/ Dot product: inner_product() -// - Polynomial subtraction: sub() -// - Polynomial negation: neg() -// - Scalar multiplication: scalar_mul() -// - Division by monomials: div_by_monomial() -// - Polynomial evaluation: eval() -// - Zero check: is_zero() -// - Polynomial equality check: is_equal() -// -// Further operations and optimizations will be added in future versions. - -// We use the Zq ring -use labrador::zq::Zq; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Rq { - coeffs: [Zq; D], -} - -impl Rq { - // Constructor for the polynomial ring - pub fn new(coeffs: [Zq; D]) -> Self { - Rq { coeffs } - } - - // Polynomial addition - pub fn add(&self, other: &Self) -> Self { - let mut result = [Zq::zero(); D]; - for (r, (a, b)) in result - .iter_mut() - .zip(self.coeffs.iter().zip(other.coeffs.iter())) - { - *r = *a + *b; - } - Rq::new(result) - } - - // Polynomial subtraction - pub fn sub(&self, other: &Self) -> Self { - let mut result = [Zq::zero(); D]; - for (r, (a, b)) in result - .iter_mut() - .zip(self.coeffs.iter().zip(other.coeffs.iter())) - { - *r = *a - *b; - } - Rq::new(result) - } - - // Create a Polynomial from a vector - pub fn create_poly(coeffs: Vec) -> Rq { - let mut arr = [Zq::zero(); D]; - let u32_coeffs: Vec = coeffs.iter().map(|&coeff| coeff as u32).collect(); - // First D elements are assigned directly - for (i, &u32_coeffs) in u32_coeffs.iter().take(D).enumerate() { - arr[i] = Zq::new(u32_coeffs); - } - - // Handle additional elements by subtracting them at (index % D) - for (i, &u32_coeffs) in u32_coeffs.iter().skip(D).enumerate() { - let mod_index = i % D; - arr[mod_index] -= Zq::new(u32_coeffs); - } - - Rq::new(arr) - } - - // Polynomial multiplication modulo x^D + 1 - pub fn mul(&self, other: &Self) -> Self { - let mut result = [Zq::zero(); D]; - - for i in 0..D { - for j in 0..D { - let degree = (i + j) % D; - if (i + j) > D { - result[degree] -= self.coeffs[i] * other.coeffs[j]; - } else { - // normal multiplication - result[degree] -= self.coeffs[i] * other.coeffs[j]; - } - } - } - - Rq::new(result) - } - - // Dot product between coefficients - pub fn inner_product(&self, other: &Self) -> Zq { - self.coeffs - .iter() - .zip(other.coeffs.iter()) - .map(|(&a, &b)| a * (b)) - .fold(Zq::zero(), |acc, x| acc + x) - } - - // Polynomial negation - pub fn neg(&self) -> Self { - let mut result = [Zq::zero(); D]; - for (i, &coeff) in self.coeffs.iter().enumerate() { - result[i] = Zq::zero() - coeff; - } - Rq::new(result) - } - - // Scalar multiplication - pub fn scalar_mul(&self, s: Zq) -> Self { - let mut result = [Zq::zero(); D]; - for (i, &coeff) in self.coeffs.iter().enumerate() { - result[i] = s * (coeff); - } - Rq::new(result) - } - - // (Division by monomial) X^k | performing a cyclic right shift - pub fn div_by_monomial(&self, k: usize) -> Self { - let mut result = [Zq::zero(); D]; - for (i, &coeff) in self.coeffs.iter().enumerate() { - // (k-shift) - let new_index = (i + k) % D; - result[new_index] = coeff; - } - Rq::new(result) - } - - // Evaluate the polynomial at a specific point - pub fn eval(&self, x: Zq) -> Zq { - let mut result = Zq::zero(); - let mut power = Zq::one(); - for &coeff in &self.coeffs { - result += coeff * power; - power *= x; - } - - result - } - - // Check if Polynomial == 0 - pub fn is_zero(&self) -> bool { - self.coeffs.iter().all(|&coeff| coeff == Zq::zero()) - } - - // Check if two polynomials are equal - pub fn is_equal(&self, other: &Self) -> bool { - self.coeffs == other.coeffs - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // Test new() and polynomial creation - #[test] - fn test_new_and_create_poly() { - let poly = Rq::new([Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)]); - assert_eq!( - poly.coeffs, - [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] - ); - - // Positive coefficients - let poly_from_vec = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - assert_eq!( - poly_from_vec.coeffs, - [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] - ); - - // Negative coefficients - let poly_from_vec_negative = Rq::<4>::create_poly(vec![-1, -2, -3, -4]); - let u32_max = u32::MAX; - assert_eq!( - poly_from_vec_negative.coeffs, - [ - Zq::new(u32_max), - Zq::new(u32_max - 1), - Zq::new(u32_max - 2), - Zq::new(u32_max - 3) - ] - ); - } - - // Test addition of polynomials - #[test] - fn test_add() { - // within bounds - let poly1 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let poly2 = Rq::<4>::create_poly(vec![4, 3, 2, 1]); - let result = poly1.add(&poly2); - assert_eq!( - result.coeffs, - [Zq::new(5), Zq::new(5), Zq::new(5), Zq::new(5)] - ); - - // Outside of bounds - let poly3 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let poly4 = Rq::<4>::new([Zq::new(u32::MAX), Zq::new(3), Zq::new(u32::MAX), Zq::new(1)]); - let result2 = poly3.add(&poly4); - assert_eq!( - result2.coeffs, - [Zq::new(0), Zq::new(5), Zq::new(2), Zq::new(5)] - ); - } - - // Test subtraction of polynomials - #[test] - fn test_sub() { - // within bounds - let poly1 = Rq::<4>::create_poly(vec![5, 10, 15, 20]); - let poly2 = Rq::<4>::create_poly(vec![2, 4, 6, 8]); - let result = poly1.sub(&poly2); - assert_eq!( - result.coeffs, - [Zq::new(3), Zq::new(6), Zq::new(9), Zq::new(12)] - ); - - // Outside of bounds - let poly3 = Rq::<4>::create_poly(vec![1, 1, 3, 2]); - let poly4 = Rq::<4>::create_poly(vec![2, 4, 6, 8]); - let result2 = poly3.sub(&poly4); - assert_eq!( - result2.coeffs, - [ - Zq::new(u32::MAX), - Zq::new(u32::MAX - 2), - Zq::new(u32::MAX - 2), - Zq::new(u32::MAX - 5) - ] - ); - } - - // Test negation of polynomial - #[test] - fn test_neg() { - let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let result = poly.neg(); - assert_eq!( - result.coeffs, - [ - Zq::new(u32::MAX), - Zq::new(u32::MAX - 1), - Zq::new(u32::MAX - 2), - Zq::new(u32::MAX - 3) - ] - ); - } - - // Test scalar multiplication - #[test] - fn test_scalar_mul() { - let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let result = poly.scalar_mul(Zq::new(2)); - assert_eq!( - result.coeffs, - [Zq::new(2), Zq::new(4), Zq::new(6), Zq::new(8)] - ); - } - - // Test division by monomials - #[test] - fn test_div_by_monomial() { - let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let result = poly.div_by_monomial(2); - assert_eq!( - result.coeffs, - [Zq::new(3), Zq::new(4), Zq::new(1), Zq::new(2)] - ); - } - - // Test polynomial evaluation - #[test] - fn test_eval() { - let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let result = poly.eval(Zq::new(2)); - assert_eq!(result, Zq::new(49)); - } - - // Test equality check - #[test] - fn test_is_equal() { - let poly1 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let poly2 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let poly3 = Rq::<4>::create_poly(vec![4, 3, 2, 1]); - assert!(poly1.is_equal(&poly2)); - assert!(!poly1.is_equal(&poly3)); - } - - // Test zero polynomial check - #[test] - fn test_is_zero_poly() { - let zero_poly = Rq::<4>::create_poly(vec![0, 0, 0, 0]); - let non_zero_poly = Rq::<4>::create_poly(vec![1, 0, 0, 0]); - assert!(zero_poly.is_zero()); - assert!(!non_zero_poly.is_zero()); - } -} diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index a13ea30..f05219b 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -56,41 +56,20 @@ impl Rq { Rq::new(result) } - // Create a Polynomial from a vector - pub fn create_poly(coeffs: Vec) -> Rq { - let mut arr = [Zq::zero(); D]; - let u32_coeffs: Vec = coeffs.iter().map(|&coeff| coeff as u32).collect(); - // First D elements are assigned directly - for (i, &u32_coeffs) in u32_coeffs.iter().take(D).enumerate() { - arr[i] = Zq::new(u32_coeffs); - } - // Handle additional elements by subtracting them at (index % D) - for (i, &u32_coeffs) in u32_coeffs.iter().skip(D).enumerate() { - let mod_index = i % D; - arr[mod_index] -= Zq::new(u32_coeffs); - } - - Rq::new(arr) - } // Polynomial multiplication modulo x^D + 1 pub fn _mul(&self, other: &Self) -> Self { - let mut result = [Zq::zero(); D]; - + // Replace conditional subtraction with full reduction: + let mut temp = vec![Zq::zero(); 2 * D - 1]; for i in 0..D { for j in 0..D { - let degree = (i + j) % D; - if (i + j) > D { - result[degree] -= self.coeffs[i] * other.coeffs[j]; - } else { - // normal multiplication - result[degree] -= self.coeffs[i] * other.coeffs[j]; - } + temp[i + j] += self.coeffs[i] * other.coeffs[j]; } } + let result: Rq = temp.into(); - Rq::new(result) + result } // Dot product between coefficients @@ -120,16 +99,7 @@ impl Rq { Rq::new(result) } - // (Division by monomial) X^k | performing a cyclic right shift - pub fn div_by_monomial(&self, k: usize) -> Self { - let mut result = [Zq::zero(); D]; - for (i, &coeff) in self.coeffs.iter().enumerate() { - // (k-shift) - let new_index = (i + k) % D; - result[new_index] = coeff; - } - Rq::new(result) - } + // Evaluate the polynomial at a specific point pub fn eval(&self, x: Zq) -> Zq { @@ -160,14 +130,14 @@ macro_rules! impl_arithmetic { type Output = Self; fn $method(self, rhs: Self) -> Self::Output { - self.$op_method(&rhs) // Pass rhs by value + self.$op_method(&rhs) } } impl $assign_trait for Rq<{ D }> { fn $assign_method(&mut self, rhs: Self) { - let result = self.$op_method(&rhs); // Use your custom method - self.coeffs = result.coeffs; // Assign the resulting coefficients + let result = self.$op_method(&rhs); + self.coeffs = result.coeffs; } } }; @@ -182,7 +152,7 @@ impl From> for Rq { fn from(vec: Vec) -> Self { let mut temp = [Zq::zero(); D]; // Process excess terms with sign adjustment - for i in (D..vec.len()).rev() { + for i in (0..vec.len()).rev() { let m = i / D; let r = i % D; let sign = if m % 2 == 0 { 1 } else { -1 }; @@ -192,8 +162,7 @@ impl From> for Rq { temp[r] -= vec[i]; } } - let coeffs = temp[0..D].try_into().unwrap(); - Rq::new(coeffs) + Rq::new(temp) } } @@ -211,32 +180,21 @@ mod tests { ); // Positive coefficients - let poly_from_vec = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly_from_vec: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); assert_eq!( poly_from_vec.coeffs, [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] ); - // Negative coefficients - let poly_from_vec_negative = Rq::<4>::create_poly(vec![-1, -2, -3, -4]); - let u32_max = u32::MAX; - assert_eq!( - poly_from_vec_negative.coeffs, - [ - Zq::new(u32_max), - Zq::new(u32_max - 1), - Zq::new(u32_max - 2), - Zq::new(u32_max - 3) - ] - ); + } // Test addition of polynomials #[test] fn test_add() { // within bounds - let poly1 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let poly2 = Rq::<4>::create_poly(vec![4, 3, 2, 1]); + let poly1: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); + let poly2: Rq<4> = vec![Zq::new(4), Zq::new(3), Zq::new(2), Zq::new(1)].into(); let result = poly1 + poly2; assert_eq!( result.coeffs, @@ -244,7 +202,7 @@ mod tests { ); // Outside of bounds - let poly3 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly3: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); let poly4 = Rq::<4>::new([Zq::new(u32::MAX), Zq::new(3), Zq::new(u32::MAX), Zq::new(1)]); let result2 = poly3 + poly4; assert_eq!( @@ -257,8 +215,8 @@ mod tests { #[test] fn test_sub() { // within bounds - let poly1 = Rq::<4>::create_poly(vec![5, 10, 15, 20]); - let poly2 = Rq::<4>::create_poly(vec![2, 4, 6, 8]); + let poly1: Rq<4> = vec![Zq::new(5), Zq::new(10), Zq::new(15), Zq::new(20)].into(); + let poly2: Rq<4> = vec![Zq::new(2), Zq::new(4), Zq::new(6), Zq::new(8)].into(); let result = poly1 - poly2; assert_eq!( result.coeffs, @@ -266,8 +224,8 @@ mod tests { ); // Outside of bounds - let poly3 = Rq::<4>::create_poly(vec![1, 1, 3, 2]); - let poly4 = Rq::<4>::create_poly(vec![2, 4, 6, 8]); + let poly3: Rq<4> = vec![Zq::new(1), Zq::new(1), Zq::new(3), Zq::new(2)].into(); + let poly4: Rq<4> = vec![Zq::new(2), Zq::new(4), Zq::new(6), Zq::new(8)].into(); let result2 = poly3 - poly4; assert_eq!( result2.coeffs, @@ -283,7 +241,7 @@ mod tests { // Test negation of polynomial #[test] fn test_neg() { - let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); let result = poly.neg(); assert_eq!( result.coeffs, @@ -299,7 +257,7 @@ mod tests { // Test scalar multiplication #[test] fn test_scalar_mul() { - let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); let result = poly.scalar_mul(Zq::new(2)); assert_eq!( result.coeffs, @@ -310,7 +268,7 @@ mod tests { // Test division by monomials #[test] fn test_div_by_monomial() { - let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); let result = poly.div_by_monomial(2); assert_eq!( result.coeffs, @@ -321,7 +279,7 @@ mod tests { // Test polynomial evaluation #[test] fn test_eval() { - let poly = Rq::<4>::create_poly(vec![1, 2, 3, 4]); + let poly: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); let result = poly.eval(Zq::new(2)); assert_eq!(result, Zq::new(49)); } @@ -329,9 +287,9 @@ mod tests { // Test equality check #[test] fn test_is_equal() { - let poly1 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let poly2 = Rq::<4>::create_poly(vec![1, 2, 3, 4]); - let poly3 = Rq::<4>::create_poly(vec![4, 3, 2, 1]); + let poly1: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); + let poly2: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); + let poly3: Rq<4> = vec![Zq::new(4), Zq::new(3), Zq::new(2), Zq::new(1)].into(); assert!(poly1.is_equal(&poly2)); assert!(!poly1.is_equal(&poly3)); } @@ -339,8 +297,8 @@ mod tests { // Test zero polynomial check #[test] fn test_is_zero_poly() { - let zero_poly = Rq::<4>::create_poly(vec![0, 0, 0, 0]); - let non_zero_poly = Rq::<4>::create_poly(vec![1, 0, 0, 0]); + let zero_poly: Rq<4> = vec![Zq::zero();4].into(); + let non_zero_poly: Rq<4> = vec![Zq::new(1), Zq::zero(), Zq::zero(), Zq::zero()].into(); assert!(zero_poly.is_zero()); assert!(!non_zero_poly.is_zero()); } From 9f82df511e7d1f12653a301d34ba3a72cdcde5d9 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Mon, 17 Feb 2025 13:34:45 -0300 Subject: [PATCH 12/37] fmt fixed --- labrador/src/rq.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index f05219b..d6fe19f 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -56,8 +56,6 @@ impl Rq { Rq::new(result) } - - // Polynomial multiplication modulo x^D + 1 pub fn _mul(&self, other: &Self) -> Self { // Replace conditional subtraction with full reduction: @@ -99,8 +97,6 @@ impl Rq { Rq::new(result) } - - // Evaluate the polynomial at a specific point pub fn eval(&self, x: Zq) -> Zq { let mut result = Zq::zero(); @@ -136,7 +132,7 @@ macro_rules! impl_arithmetic { impl $assign_trait for Rq<{ D }> { fn $assign_method(&mut self, rhs: Self) { - let result = self.$op_method(&rhs); + let result = self.$op_method(&rhs); self.coeffs = result.coeffs; } } @@ -185,8 +181,6 @@ mod tests { poly_from_vec.coeffs, [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] ); - - } // Test addition of polynomials @@ -297,7 +291,7 @@ mod tests { // Test zero polynomial check #[test] fn test_is_zero_poly() { - let zero_poly: Rq<4> = vec![Zq::zero();4].into(); + let zero_poly: Rq<4> = vec![Zq::zero(); 4].into(); let non_zero_poly: Rq<4> = vec![Zq::new(1), Zq::zero(), Zq::zero(), Zq::zero()].into(); assert!(zero_poly.is_zero()); assert!(!non_zero_poly.is_zero()); From 720a52ee24f727c1947208c20f126ad816b31319 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Mon, 17 Feb 2025 13:37:13 -0300 Subject: [PATCH 13/37] division by monomial removed --- labrador/src/main.rs | 4 ---- labrador/src/rq.rs | 12 ------------ 2 files changed, 16 deletions(-) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index e63c141..0c76fd4 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -28,9 +28,6 @@ fn main() { // Perform scalar multiplication let scalar_multiplication = p1.scalar_mul(Zq::new(2)); - // Perform division by a monomial - let monomial_division = p1.div_by_monomial(2); - // Evaluate the polynomial at x = 2 let evaluation = p1.eval(Zq::new(2)); @@ -47,7 +44,6 @@ fn main() { println!("Dot product: {:?}", dot); println!("Negation: {:?}", negation); println!("Scalar multiplication: {:?}", scalar_multiplication); - println!("Monomial division: {:?}", monomial_division); println!("Evaluation at x=2: {:?}", evaluation); println!("Is zero polynomial: {:?}", zero_check); println!("Are polynomials equal: {:?}", are_equal); diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index d6fe19f..03d1ecf 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -10,7 +10,6 @@ // - Polynomial subtraction: - // - Polynomial negation: neg() // - Scalar multiplication: scalar_mul() -// - Division by monomials: div_by_monomial() // - Polynomial evaluation: eval() // - Zero check: is_zero() // - Polynomial equality check: is_equal() @@ -259,17 +258,6 @@ mod tests { ); } - // Test division by monomials - #[test] - fn test_div_by_monomial() { - let poly: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); - let result = poly.div_by_monomial(2); - assert_eq!( - result.coeffs, - [Zq::new(3), Zq::new(4), Zq::new(1), Zq::new(2)] - ); - } - // Test polynomial evaluation #[test] fn test_eval() { From d201aa434e7f352e029569651d225f75f360333e Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Mon, 17 Feb 2025 14:26:26 -0300 Subject: [PATCH 14/37] merge solved --- labrador/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 3a207e2..0c76fd4 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -1,7 +1,6 @@ use labrador::say_hello; use labrador::zq::Zq; - mod rq; use rq::Rq; @@ -48,7 +47,7 @@ fn main() { println!("Evaluation at x=2: {:?}", evaluation); println!("Is zero polynomial: {:?}", zero_check); println!("Are polynomials equal: {:?}", are_equal); - + let a = Zq::new(5); let b = Zq::new(3); println!("a + b = {}", a + b); From ad5ef13478b709c1743d96f5036ea955114dd61e Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Tue, 18 Feb 2025 14:53:12 -0300 Subject: [PATCH 15/37] new tests added --- labrador/src/main.rs | 4 +- labrador/src/rq.rs | 130 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 108 insertions(+), 26 deletions(-) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 0c76fd4..4f25ccd 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -8,8 +8,8 @@ fn main() { say_hello(); // Example poly_ring - let p1: Rq<2> = vec![Zq::new(1), Zq::new(2)].into(); - let p2: Rq<2> = vec![Zq::new(1), Zq::new(1)].into(); + let p1: Rq<2> = vec![Zq::new(1)].into(); + let p2: Rq<2> = vec![Zq::new(2), Zq::new(1), Zq::new(1)].into(); // Perform polynomial multiplication let product = p1 * p2; diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index 03d1ecf..840bc24 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -26,12 +26,12 @@ pub struct Rq { } impl Rq { - // Constructor for the polynomial ring + /// Constructor for the polynomial ring pub fn new(coeffs: [Zq; D]) -> Self { Rq { coeffs } } - // Polynomial addition + /// Polynomial addition pub fn _add(&self, other: &Self) -> Self { let mut result = [Zq::zero(); D]; for (r, (a, b)) in result @@ -43,7 +43,7 @@ impl Rq { Rq::new(result) } - // Polynomial subtraction + /// Polynomial subtraction pub fn _sub(&self, other: &Self) -> Self { let mut result = [Zq::zero(); D]; for (r, (a, b)) in result @@ -55,21 +55,18 @@ impl Rq { Rq::new(result) } - // Polynomial multiplication modulo x^D + 1 + /// Polynomial multiplication modulo x^D + 1 pub fn _mul(&self, other: &Self) -> Self { - // Replace conditional subtraction with full reduction: - let mut temp = vec![Zq::zero(); 2 * D - 1]; - for i in 0..D { - for j in 0..D { - temp[i + j] += self.coeffs[i] * other.coeffs[j]; + let mut result: Vec = vec![Zq::zero(); 2 * D - 1]; + for (i, &self_coeff) in self.coeffs.iter().enumerate() { + for (j, &other_coeff) in other.coeffs.iter().enumerate() { + result[i + j] += self_coeff * other_coeff; } } - let result: Rq = temp.into(); - - result + result.into() } - // Dot product between coefficients + /// Dot product between coefficients pub fn inner_product(&self, other: &Self) -> Zq { self.coeffs .iter() @@ -78,7 +75,7 @@ impl Rq { .fold(Zq::zero(), |acc, x| acc + x) } - // Polynomial negation + /// Polynomial negation pub fn neg(&self) -> Self { let mut result = [Zq::zero(); D]; for (i, &coeff) in self.coeffs.iter().enumerate() { @@ -87,7 +84,7 @@ impl Rq { Rq::new(result) } - // Scalar multiplication + /// Scalar multiplication pub fn scalar_mul(&self, s: Zq) -> Self { let mut result = [Zq::zero(); D]; for (i, &coeff) in self.coeffs.iter().enumerate() { @@ -96,7 +93,7 @@ impl Rq { Rq::new(result) } - // Evaluate the polynomial at a specific point + /// Evaluate the polynomial at a specific point pub fn eval(&self, x: Zq) -> Zq { let mut result = Zq::zero(); let mut power = Zq::one(); @@ -108,12 +105,12 @@ impl Rq { result } - // Check if Polynomial == 0 + /// Check if Polynomial == 0 pub fn is_zero(&self) -> bool { self.coeffs.iter().all(|&coeff| coeff == Zq::zero()) } - // Check if two polynomials are equal + /// Check if two polynomials are equal pub fn is_equal(&self, other: &Self) -> bool { self.coeffs == other.coeffs } @@ -138,7 +135,6 @@ macro_rules! impl_arithmetic { }; } -// Implement the traits using your custom methods impl_arithmetic!(Add, AddAssign, add, add_assign, _add); impl_arithmetic!(Sub, SubAssign, sub, sub_assign, _sub); impl_arithmetic!(Mul, MulAssign, mul, mul_assign, _mul); @@ -174,18 +170,44 @@ mod tests { [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] ); - // Positive coefficients - let poly_from_vec: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); + // Direct conversion + let poly_from_vec_direct: Rq<4> = + vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); assert_eq!( - poly_from_vec.coeffs, + poly_from_vec_direct.coeffs, [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] ); + // Wrapping around + let poly_from_vec_wrapping: Rq<4> = + vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4), Zq::new(1)].into(); + assert_eq!( + poly_from_vec_wrapping.coeffs, + [Zq::zero(), Zq::new(2), Zq::new(3), Zq::new(4)] + ); + // Filling up with zeros + let poly_from_vec_zeros: Rq<4> = vec![Zq::new(1), Zq::new(2)].into(); + assert_eq!( + poly_from_vec_zeros.coeffs, + [Zq::new(1), Zq::new(2), Zq::zero(), Zq::zero()] + ); + // High-Degree Term Reduction + let poly_high_degree_reduction: Rq<2> = vec![ + Zq::new(1), + Zq::new(2), + Zq::zero(), + Zq::zero(), + Zq::zero(), + Zq::zero(), + Zq::new(1), + ] + .into(); + assert_eq!(poly_high_degree_reduction.coeffs, [Zq::zero(), Zq::new(2)]); } // Test addition of polynomials #[test] fn test_add() { - // within bounds + // Within bounds let poly1: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); let poly2: Rq<4> = vec![Zq::new(4), Zq::new(3), Zq::new(2), Zq::new(1)].into(); let result = poly1 + poly2; @@ -196,12 +218,54 @@ mod tests { // Outside of bounds let poly3: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); - let poly4 = Rq::<4>::new([Zq::new(u32::MAX), Zq::new(3), Zq::new(u32::MAX), Zq::new(1)]); + let poly4: Rq<4> = + vec![Zq::new(u32::MAX), Zq::new(3), Zq::new(u32::MAX), Zq::new(1)].into(); let result2 = poly3 + poly4; assert_eq!( result2.coeffs, [Zq::new(0), Zq::new(5), Zq::new(2), Zq::new(5)] ); + // Addition with zero polynomial + let poly5: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); + let poly6: Rq<4> = vec![Zq::zero()].into(); + let result3 = poly5 + poly6; + assert_eq!( + result3.coeffs, + [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] + ); + // Addition with high coefficeints + let poly7: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(u32::MAX)].into(); + let poly8: Rq<4> = vec![ + Zq::new(u32::MAX), + Zq::new(u32::MAX), + Zq::new(u32::MAX), + Zq::new(u32::MAX), + ] + .into(); + let result3 = poly7 + poly8; + assert_eq!( + result3.coeffs, + [ + Zq::new(0), + Zq::new(1), + Zq::new(2), + Zq::new(u32::MAX.wrapping_add(u32::MAX)) + ] + ); + } + // Test multiplication of polynomials + #[test] + // Multiplication with wrapping + fn test_mul() { + let poly1: Rq<3> = vec![Zq::new(1), Zq::new(1), Zq::new(2)].into(); + let poly2: Rq<3> = vec![Zq::new(1), Zq::new(1)].into(); + let result = poly1 * poly2; + assert_eq!(result.coeffs, [Zq::new(u32::MAX), Zq::new(2), Zq::new(3)]); + // Multiplication with zero polynomial + let poly3: Rq<3> = vec![Zq::new(1), Zq::new(1), Zq::new(2)].into(); + let poly4: Rq<3> = vec![Zq::zero()].into(); + let result2 = poly3 * poly4; + assert_eq!(result2.coeffs, [Zq::zero(), Zq::zero(), Zq::zero()]); } // Test subtraction of polynomials @@ -229,6 +293,24 @@ mod tests { Zq::new(u32::MAX - 5) ] ); + // Subtraction with zero polynomial + let poly5: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); + let poly6: Rq<4> = vec![Zq::zero()].into(); + let result3 = poly6 - poly5; + let result4 = poly5 - poly6; + assert_eq!( + result3.coeffs, + [ + Zq::new(u32::MAX), + Zq::new(u32::MAX - 1), + Zq::new(u32::MAX - 2), + Zq::new(u32::MAX - 3) + ] + ); + assert_eq!( + result4.coeffs, + [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] + ); } // Test negation of polynomial From 8bc411acebda79a46dfbb5d6b2e0fb5809877673 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Tue, 18 Feb 2025 14:54:23 -0300 Subject: [PATCH 16/37] typo fixed --- labrador/src/rq.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index 840bc24..5ec4459 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -233,7 +233,7 @@ mod tests { result3.coeffs, [Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)] ); - // Addition with high coefficeints + // Addition with high coefficients let poly7: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(u32::MAX)].into(); let poly8: Rq<4> = vec![ Zq::new(u32::MAX), From 2499d1a79cb66aead41ea672c41f001d210a6187 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Tue, 18 Feb 2025 16:09:56 -0300 Subject: [PATCH 17/37] docs updated --- labrador/src/lib.rs | 2 ++ labrador/src/main.rs | 4 +--- labrador/src/rq.rs | 7 +++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/labrador/src/lib.rs b/labrador/src/lib.rs index 1213ee3..a2ac1cd 100644 --- a/labrador/src/lib.rs +++ b/labrador/src/lib.rs @@ -16,6 +16,8 @@ // Amortization #![doc = include_str!("../../doc/amortization.md")] +pub mod rq; + pub mod zq; /// Prints a "Hello, world!" message diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 4f25ccd..c357de6 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -1,9 +1,7 @@ +use labrador::rq::Rq; use labrador::say_hello; use labrador::zq::Zq; -mod rq; -use rq::Rq; - fn main() { say_hello(); diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index 5ec4459..1542317 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -1,7 +1,5 @@ // This file is part of the polynomial ring operations module. // -// This module provides implementations for various operations -// in the polynomial ring R = Z_q[X] / (X^d + 1). // // Currently implemented functions include: // - Polynomial addition: + @@ -17,9 +15,10 @@ // Further operations and optimizations will be added in future versions. // We use the Zq ring -use labrador::zq::Zq; +use crate::zq::Zq; use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; - +/// This module provides implementations for various operations +/// in the polynomial ring R = Z_q\[X\] / (X^d + 1). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Rq { coeffs: [Zq; D], From 19435e1ddb795b67108640de1d3c2deca4ecce26 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Wed, 19 Feb 2025 13:20:42 -0300 Subject: [PATCH 18/37] Implementation of Horner method and Matrix multiplication on stack --- labrador/src/main.rs | 2 +- labrador/src/rq.rs | 40 +++++++++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index c357de6..c10a4cf 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -27,7 +27,7 @@ fn main() { let scalar_multiplication = p1.scalar_mul(Zq::new(2)); // Evaluate the polynomial at x = 2 - let evaluation = p1.eval(Zq::new(2)); + let evaluation = p2.eval(Zq::new(2)); // Check if the polynomial is the zero polynomial let zero_check = p1.is_zero(); diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index 1542317..40128f7 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -31,7 +31,7 @@ impl Rq { } /// Polynomial addition - pub fn _add(&self, other: &Self) -> Self { + fn addition(&self, other: &Self) -> Self { let mut result = [Zq::zero(); D]; for (r, (a, b)) in result .iter_mut() @@ -43,7 +43,7 @@ impl Rq { } /// Polynomial subtraction - pub fn _sub(&self, other: &Self) -> Self { + fn subtraction(&self, other: &Self) -> Self { let mut result = [Zq::zero(); D]; for (r, (a, b)) in result .iter_mut() @@ -55,14 +55,30 @@ impl Rq { } /// Polynomial multiplication modulo x^D + 1 - pub fn _mul(&self, other: &Self) -> Self { - let mut result: Vec = vec![Zq::zero(); 2 * D - 1]; + fn multiplication(&self, other: &Self) -> Self { + let mut result = [Zq::zero(); D]; + let mut out_of_field = [Zq::zero(); D]; for (i, &self_coeff) in self.coeffs.iter().enumerate() { for (j, &other_coeff) in other.coeffs.iter().enumerate() { - result[i + j] += self_coeff * other_coeff; + if i + j < D { + result[i + j] += self_coeff * other_coeff; + } else { + out_of_field[(i + j) % D] += self_coeff * other_coeff; + } + } + } + // Process excess terms with sign adjustment + for i in (0..D).rev() { + let m = i + D / D; + let r = i + D % D; + let sign = if m % 2 == 0 { 1 } else { -1 }; + if sign == 1 { + result[r] += out_of_field[i]; + } else { + result[r] -= out_of_field[i]; } } - result.into() + Rq::new(result) } /// Dot product between coefficients @@ -95,10 +111,8 @@ impl Rq { /// Evaluate the polynomial at a specific point pub fn eval(&self, x: Zq) -> Zq { let mut result = Zq::zero(); - let mut power = Zq::one(); - for &coeff in &self.coeffs { - result += coeff * power; - power *= x; + for coeff in self.coeffs.iter().rev() { + result = result * x + *coeff; } result @@ -134,9 +148,9 @@ macro_rules! impl_arithmetic { }; } -impl_arithmetic!(Add, AddAssign, add, add_assign, _add); -impl_arithmetic!(Sub, SubAssign, sub, sub_assign, _sub); -impl_arithmetic!(Mul, MulAssign, mul, mul_assign, _mul); +impl_arithmetic!(Add, AddAssign, add, add_assign, addition); +impl_arithmetic!(Sub, SubAssign, sub, sub_assign, subtraction); +impl_arithmetic!(Mul, MulAssign, mul, mul_assign, multiplication); impl From> for Rq { fn from(vec: Vec) -> Self { From 863343d25ac2344653de61e9b490f5a36adc6187 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Wed, 19 Feb 2025 13:53:20 -0300 Subject: [PATCH 19/37] multiplication fixed --- labrador/src/rq.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index 40128f7..98d118c 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -69,9 +69,9 @@ impl Rq { } // Process excess terms with sign adjustment for i in (0..D).rev() { - let m = i + D / D; - let r = i + D % D; - let sign = if m % 2 == 0 { 1 } else { -1 }; + let m = i / D; + let r = i % D; + let sign = if m + 1 % 2 == 0 { 1 } else { -1 }; if sign == 1 { result[r] += out_of_field[i]; } else { @@ -268,17 +268,28 @@ mod tests { } // Test multiplication of polynomials #[test] - // Multiplication with wrapping + fn test_mul() { + // Multiplication with wrapping let poly1: Rq<3> = vec![Zq::new(1), Zq::new(1), Zq::new(2)].into(); let poly2: Rq<3> = vec![Zq::new(1), Zq::new(1)].into(); let result = poly1 * poly2; assert_eq!(result.coeffs, [Zq::new(u32::MAX), Zq::new(2), Zq::new(3)]); + // Multiplication with zero polynomial let poly3: Rq<3> = vec![Zq::new(1), Zq::new(1), Zq::new(2)].into(); let poly4: Rq<3> = vec![Zq::zero()].into(); let result2 = poly3 * poly4; assert_eq!(result2.coeffs, [Zq::zero(), Zq::zero(), Zq::zero()]); + + // Multiplication with wrapping higher order + let poly5: Rq<3> = vec![Zq::new(1), Zq::new(1), Zq::new(2)].into(); + let poly6: Rq<3> = vec![Zq::new(1), Zq::new(1), Zq::new(7), Zq::new(5)].into(); + let result3 = poly5 * poly6; + assert_eq!( + result3.coeffs, + [Zq::new(u32::MAX - 12), Zq::new(u32::MAX - 16), Zq::zero()] + ); } // Test subtraction of polynomials From 5d816b15ed2dc79dd35e5eccaa8caa97c6044e6f Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Wed, 19 Feb 2025 14:08:48 -0300 Subject: [PATCH 20/37] Clippy issue solved --- labrador/src/rq.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index 98d118c..317542e 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -71,7 +71,7 @@ impl Rq { for i in (0..D).rev() { let m = i / D; let r = i % D; - let sign = if m + 1 % 2 == 0 { 1 } else { -1 }; + let sign = if (m + 1) % 2 == 0 { 1 } else { -1 }; if sign == 1 { result[r] += out_of_field[i]; } else { From 555faa164045ff5e90ea578e3c1224f5c51e8b23 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Wed, 19 Feb 2025 14:20:56 -0300 Subject: [PATCH 21/37] Neg trait implemented --- labrador/src/main.rs | 2 +- labrador/src/rq.rs | 27 ++++++++++++++++----------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index c10a4cf..616ef4f 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -21,7 +21,7 @@ fn main() { let dot = p1.inner_product(&p2); // Negate the polynomial - let negation = p1.neg(); + let negation = -p1; // Perform scalar multiplication let scalar_multiplication = p1.scalar_mul(Zq::new(2)); diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index 317542e..8992bba 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -16,7 +16,7 @@ // We use the Zq ring use crate::zq::Zq; -use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; /// This module provides implementations for various operations /// in the polynomial ring R = Z_q\[X\] / (X^d + 1). #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -90,15 +90,6 @@ impl Rq { .fold(Zq::zero(), |acc, x| acc + x) } - /// Polynomial negation - pub fn neg(&self) -> Self { - let mut result = [Zq::zero(); D]; - for (i, &coeff) in self.coeffs.iter().enumerate() { - result[i] = Zq::zero() - coeff; - } - Rq::new(result) - } - /// Scalar multiplication pub fn scalar_mul(&self, s: Zq) -> Self { let mut result = [Zq::zero(); D]; @@ -170,6 +161,20 @@ impl From> for Rq { } } +// Implementing the Neg trait +impl Neg for Rq { + type Output = Self; + + // Polynomial negation + fn neg(self) -> Self { + let mut result = [Zq::zero(); D]; + for (i, &coeff) in self.coeffs.iter().enumerate() { + result[i] = Zq::zero() - coeff; + } + Rq::new(result) + } +} + #[cfg(test)] mod tests { use super::*; @@ -341,7 +346,7 @@ mod tests { #[test] fn test_neg() { let poly: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); - let result = poly.neg(); + let result = -poly; assert_eq!( result.coeffs, [ From 78b85e14bcac18530ffe4d8762bb0e6955790329 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Wed, 19 Feb 2025 17:43:03 -0300 Subject: [PATCH 22/37] Cloning instead of Copy operation --- labrador/src/main.rs | 10 +++++----- labrador/src/rq.rs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 616ef4f..80b0b4b 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -9,19 +9,19 @@ fn main() { let p1: Rq<2> = vec![Zq::new(1)].into(); let p2: Rq<2> = vec![Zq::new(2), Zq::new(1), Zq::new(1)].into(); // Perform polynomial multiplication - let product = p1 * p2; + let product = p1.clone() * p2.clone(); // Perform polynomial addition - let sum = p1 + p2; + let sum = p1.clone() + p2.clone(); // Perform polynomial subtraction - let sub = p1 - p2; + let sub = p1.clone() - p2.clone(); // Compute the dot product between the polynomial coefficients - let dot = p1.inner_product(&p2); + let dot = p1.clone().inner_product(&p2); // Negate the polynomial - let negation = -p1; + let negation = -p1.clone(); // Perform scalar multiplication let scalar_multiplication = p1.scalar_mul(Zq::new(2)); diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index 8992bba..78e7d7b 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -19,7 +19,7 @@ use crate::zq::Zq; use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; /// This module provides implementations for various operations /// in the polynomial ring R = Z_q\[X\] / (X^d + 1). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Rq { coeffs: [Zq; D], } @@ -325,8 +325,8 @@ mod tests { // Subtraction with zero polynomial let poly5: Rq<4> = vec![Zq::new(1), Zq::new(2), Zq::new(3), Zq::new(4)].into(); let poly6: Rq<4> = vec![Zq::zero()].into(); - let result3 = poly6 - poly5; - let result4 = poly5 - poly6; + let result3 = poly6.clone() - poly5.clone(); + let result4 = poly5.clone() - poly6.clone(); assert_eq!( result3.coeffs, [ From 47bd1b29b28965287c62a222caaa420b29076041 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Thu, 20 Feb 2025 12:21:58 -0300 Subject: [PATCH 23/37] random matrix for projection --- Cargo.lock | 237 ++++++++++++++++++++++++++++++++++++++++++- labrador/Cargo.toml | 1 + labrador/src/jl.rs | 27 +++++ labrador/src/lib.rs | 10 -- labrador/src/main.rs | 13 ++- 5 files changed, 273 insertions(+), 15 deletions(-) create mode 100644 labrador/src/jl.rs diff --git a/Cargo.lock b/Cargo.lock index ae1de82..1233243 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "base64" @@ -8,9 +8,244 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi", + "windows-targets", +] + [[package]] name = "labrador" version = "0.1.0" dependencies = [ "base64", + "rand", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy 0.7.35", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy 0.8.20", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" +dependencies = [ + "getrandom", + "zerocopy 0.8.20", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" +dependencies = [ + "zerocopy-derive 0.8.20", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] diff --git a/labrador/Cargo.toml b/labrador/Cargo.toml index 908200d..a3af712 100644 --- a/labrador/Cargo.toml +++ b/labrador/Cargo.toml @@ -7,3 +7,4 @@ license.workspace = true [dependencies] base64 = "0.22.1" +rand = "0.9" diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs new file mode 100644 index 0000000..db0dca7 --- /dev/null +++ b/labrador/src/jl.rs @@ -0,0 +1,27 @@ +use rand::thread_rng; +//use labrador::rq::Rq; + +use rand::prelude::*; + +pub fn generate_random_projection_matrix(d: usize, m: usize) -> Vec> { + let mut rng = thread_rng(); + + // Initialize a 2D vector with dimensions (d, m) + let mut matrix = vec![vec![0; m]; d]; + + // Fill the matrix with random values from {-1, 0, 1} + for row in matrix.iter_mut() { + for elem in row.iter_mut() { + let rand_val: f64 = rng.gen(); + *elem = if rand_val < 0.25 { + -1 + } else if rand_val < 0.75 { + 0 + } else { + 1 + }; + } + } + + matrix +} diff --git a/labrador/src/lib.rs b/labrador/src/lib.rs index a2ac1cd..c646e99 100644 --- a/labrador/src/lib.rs +++ b/labrador/src/lib.rs @@ -20,20 +20,10 @@ pub mod rq; pub mod zq; -/// Prints a "Hello, world!" message -pub fn say_hello() { - println!("Hello, world!"); -} - #[cfg(test)] mod tests { use super::*; - #[test] - fn test_say_hello() { - say_hello(); - } - #[test] fn test_for_workflow() { use base64::prelude::*; diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 80b0b4b..e776526 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -1,10 +1,7 @@ use labrador::rq::Rq; -use labrador::say_hello; use labrador::zq::Zq; - +mod jl; fn main() { - say_hello(); - // Example poly_ring let p1: Rq<2> = vec![Zq::new(1)].into(); let p2: Rq<2> = vec![Zq::new(2), Zq::new(1), Zq::new(1)].into(); @@ -49,4 +46,12 @@ fn main() { let a = Zq::new(5); let b = Zq::new(3); println!("a + b = {}", a + b); + + // Johnson Linderstrauss Projections + let m = 5; + let d = 5; + let matrix = jl::generate_random_projection_matrix(m, d); + for row in matrix { + println!("{:?}", row); + } } From a2a69e62b5c9e6af2e978a418342f53a725dca15 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Thu, 20 Feb 2025 12:27:32 -0300 Subject: [PATCH 24/37] warnings solved --- labrador/src/jl.rs | 6 +++--- labrador/src/lib.rs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index db0dca7..df2be01 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -1,10 +1,10 @@ -use rand::thread_rng; +use rand::rng; //use labrador::rq::Rq; use rand::prelude::*; pub fn generate_random_projection_matrix(d: usize, m: usize) -> Vec> { - let mut rng = thread_rng(); + let mut rng = rng(); // Initialize a 2D vector with dimensions (d, m) let mut matrix = vec![vec![0; m]; d]; @@ -12,7 +12,7 @@ pub fn generate_random_projection_matrix(d: usize, m: usize) -> Vec> { // Fill the matrix with random values from {-1, 0, 1} for row in matrix.iter_mut() { for elem in row.iter_mut() { - let rand_val: f64 = rng.gen(); + let rand_val: f64 = rng.random(); *elem = if rand_val < 0.25 { -1 } else if rand_val < 0.75 { diff --git a/labrador/src/lib.rs b/labrador/src/lib.rs index c646e99..688d9cf 100644 --- a/labrador/src/lib.rs +++ b/labrador/src/lib.rs @@ -22,7 +22,6 @@ pub mod zq; #[cfg(test)] mod tests { - use super::*; #[test] fn test_for_workflow() { From eb391dc2a776cfb498829161d45b83c24b44ee80 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Thu, 20 Feb 2025 19:22:50 -0300 Subject: [PATCH 25/37] Random matrix and Projection --- labrador/src/jl.rs | 136 +++++++++++++++++++++++++++++++++++++------ labrador/src/lib.rs | 2 +- labrador/src/main.rs | 30 ++++++++-- labrador/src/rq.rs | 15 +++++ labrador/src/zq.rs | 4 ++ 5 files changed, 161 insertions(+), 26 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index df2be01..0948e4c 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -1,27 +1,125 @@ +use crate::rq::Rq; +use crate::zq::Zq; use rand::rng; //use labrador::rq::Rq; - use rand::prelude::*; -pub fn generate_random_projection_matrix(d: usize, m: usize) -> Vec> { - let mut rng = rng(); - - // Initialize a 2D vector with dimensions (d, m) - let mut matrix = vec![vec![0; m]; d]; - - // Fill the matrix with random values from {-1, 0, 1} - for row in matrix.iter_mut() { - for elem in row.iter_mut() { - let rand_val: f64 = rng.random(); - *elem = if rand_val < 0.25 { - -1 - } else if rand_val < 0.75 { - 0 - } else { - 1 - }; +/// Projection matrix with values in {1,0,-1} +pub struct ProjectionMatrix { + matrix: Vec>, // 256 x nd matrix, entries -1 or 1 in Zq +} + +impl ProjectionMatrix { + /// Defines a matrix of size 256xnd + pub fn new(nd: usize) -> Self { + let mut matrix = vec![vec![Zq::zero(); nd]; 256]; + let mut rng = rng(); + // Fill the matrix with random values from {-1, 0, 1} + for row in matrix.iter_mut() { + for elem in row.iter_mut() { + let rand_val: f64 = rng.random(); + *elem = if rand_val < 0.25 { + Zq::new(u32::MAX) // -1 in Zq + } else if rand_val < 0.75 { + Zq::zero() + } else { + Zq::one() + }; + } + } + ProjectionMatrix { matrix } + } + /// Returns the matrix + pub fn get_matrix(&self) -> &Vec> { + &self.matrix + } +} + +/// Calculate projection vector +#[derive(Debug)] +pub struct ProjectionVector { + projection: Vec, // 256-dimensional projection vector +} + +impl ProjectionVector { + // Function to concatenate coefficients from multiple Rq into a Vec + fn concatenate_coefficients(rqs: Vec>) -> Vec { + let mut concatenated_coeffs: Vec = Vec::new(); + + // Iterate over each Rq, extracting the coefficients and concatenating them + for rq in rqs { + let coeffs = rq.get_coefficients(); + concatenated_coeffs.extend_from_slice(&coeffs); // Extend the Vec with the coefficients + } + + concatenated_coeffs + } + + /// Calculates Projection + pub fn new(matrix: &ProjectionMatrix, s_i: &Vec>) -> Self { + let mut projection = vec![Zq::zero(); 256]; + let coefficients = Self::concatenate_coefficients(s_i.clone()); + for i in 0..256 { + projection[i] = matrix.get_matrix()[i] + .iter() + .zip(coefficients.iter()) + .map(|(m, s)| *m * *s) + .sum(); } + ProjectionVector { projection } } + /// Obtain projection + pub fn get_projection(&self) -> &Vec { + &self.projection + } +} + +/// Function to generate a random polynomial +/// Returns a vector of `n` random polynomials, each of degree `d` +pub fn generate_random_polynomials( + n: usize, + rng: &mut R, + beta: f64, +) -> Vec> { + let mut polynomials = Vec::with_capacity(n); + + for _ in 0..n { + loop { + // Generate random coefficients + let coeffs: [Zq; D] = std::array::from_fn(|_| { + let small_value: u32 = rng.random_range(0..100 as u32); // random value between 0 and 100 (as u322) + match small_value.try_into() { + Ok(value) => Zq::new(value), + Err(_) => { + eprintln!("Failed to convert value: {}", small_value); + Zq::new(0) // Default value in case of failure + } + } + }); + + // Create the polynomial + let polynomial = Rq::new(coeffs); + + // Check the norm of the polynomial (assuming norm is some value you can compute) + let norm = compute_norm(&polynomial); // This function should compute the norm of the polynomial + + // If the norm is smaller than beta, add the polynomial to the list + if norm < beta { + polynomials.push(polynomial); + break; // Exit the loop as the polynomial is valid + } + // If the norm is greater than or equal to beta, try again with a new random polynomial + } + } + + polynomials +} - matrix +// Helper function to compute the norm of the polynomial +fn compute_norm(polynomial: &Rq) -> f64 { + polynomial + .get_coefficients() + .iter() + .map(|&coeff| coeff.to_f64()) + .sum() } diff --git a/labrador/src/lib.rs b/labrador/src/lib.rs index 688d9cf..f530792 100644 --- a/labrador/src/lib.rs +++ b/labrador/src/lib.rs @@ -16,8 +16,8 @@ // Amortization #![doc = include_str!("../../doc/amortization.md")] +pub mod jl; pub mod rq; - pub mod zq; #[cfg(test)] diff --git a/labrador/src/main.rs b/labrador/src/main.rs index e776526..24c2e8a 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -1,6 +1,14 @@ +use labrador::jl::generate_random_polynomials; +use labrador::jl::ProjectionMatrix; +use labrador::jl::ProjectionVector; use labrador::rq::Rq; use labrador::zq::Zq; -mod jl; +use rand::rngs::ThreadRng; // Import ThreadRng + +const D: usize = 4; // Degree of polynomials (you can change this to the required degree) +const N: usize = 5; // Number of polynomials (you can adjust this as needed) +const BETA: f64 = 50.0; + fn main() { // Example poly_ring let p1: Rq<2> = vec![Zq::new(1)].into(); @@ -48,10 +56,20 @@ fn main() { println!("a + b = {}", a + b); // Johnson Linderstrauss Projections - let m = 5; - let d = 5; - let matrix = jl::generate_random_projection_matrix(m, d); - for row in matrix { - println!("{:?}", row); + // Example parameters + + let mut rng = rand::rngs::ThreadRng::default(); // Corrected RNG instantiation + + // Generate the random polynomials + let polynomials = generate_random_polynomials::(N, &mut rng, BETA); + let matrix = ProjectionMatrix::new(D * N); + let projection = ProjectionVector::new(&matrix, &polynomials); + // Print the generated polynomials + for (i, poly) in polynomials.iter().enumerate() { + println!("Polynomial {}: {:?}", i + 1, poly); } + println!("Projection: {:?}", projection); + // cqalcualte the norm of a vector of zq elements + // compare norms + // norms verification } diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index 78e7d7b..68cf054 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -29,6 +29,10 @@ impl Rq { pub fn new(coeffs: [Zq; D]) -> Self { Rq { coeffs } } + /// Function to get the coefficients as a slice of Zq + pub fn get_coefficients(&self) -> Vec { + self.coeffs.to_vec() + } /// Polynomial addition fn addition(&self, other: &Self) -> Self { @@ -160,6 +164,17 @@ impl From> for Rq { Rq::new(temp) } } +use std::iter::Sum; + +impl Sum for Zq { + // Accumulate using the addition operator + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.fold(Zq::zero(), |acc, x| acc + x) + } +} // Implementing the Neg trait impl Neg for Rq { diff --git a/labrador/src/zq.rs b/labrador/src/zq.rs index 97d8de3..b7c0041 100644 --- a/labrador/src/zq.rs +++ b/labrador/src/zq.rs @@ -18,6 +18,10 @@ impl Zq { Self { value } } + pub fn to_f64(&self) -> f64 { + self.value as f64 // Correctly access the 'value' field + } + /// Zero element (additive identity) pub fn zero() -> Self { Self { value: 0 } From 7ba6b9b3818fe79976153a71fc1a807856b834ee Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Thu, 20 Feb 2025 19:25:54 -0300 Subject: [PATCH 26/37] typo fixed --- labrador/src/jl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 0948e4c..2c668f6 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -6,7 +6,7 @@ use rand::prelude::*; /// Projection matrix with values in {1,0,-1} pub struct ProjectionMatrix { - matrix: Vec>, // 256 x nd matrix, entries -1 or 1 in Zq + matrix: Vec>, // 256 x (nxd) matrix, entries -1 or 1 in Zq } impl ProjectionMatrix { From a8ac2b673a31978c756d25eb084cc849a81549eb Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Thu, 20 Feb 2025 19:27:29 -0300 Subject: [PATCH 27/37] typo fixed 2 --- labrador/src/jl.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 2c668f6..16b8330 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -11,8 +11,8 @@ pub struct ProjectionMatrix { impl ProjectionMatrix { /// Defines a matrix of size 256xnd - pub fn new(nd: usize) -> Self { - let mut matrix = vec![vec![Zq::zero(); nd]; 256]; + pub fn new(N: usize) -> Self { + let mut matrix = vec![vec![Zq::zero(); N]; 256]; let mut rng = rng(); // Fill the matrix with random values from {-1, 0, 1} for row in matrix.iter_mut() { From 7379aa5b71e41bcf6bc3f6dfc77226c357a8ddeb Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Fri, 21 Feb 2025 18:45:57 -0300 Subject: [PATCH 28/37] Random projection matrix and norms implemented --- labrador/src/jl.rs | 112 +++++++++++++++++++++++++++++++++---------- labrador/src/main.rs | 30 +++++++----- labrador/src/rq.rs | 3 +- labrador/src/zq.rs | 4 +- 4 files changed, 108 insertions(+), 41 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 16b8330..02e09dd 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -1,18 +1,17 @@ use crate::rq::Rq; use crate::zq::Zq; -use rand::rng; -//use labrador::rq::Rq; use rand::prelude::*; +use rand::rng; -/// Projection matrix with values in {1,0,-1} +/// Projection matrix with values in {1,0,-1} mod q pub struct ProjectionMatrix { - matrix: Vec>, // 256 x (nxd) matrix, entries -1 or 1 in Zq + matrix: Vec>, } impl ProjectionMatrix { - /// Defines a matrix of size 256xnd - pub fn new(N: usize) -> Self { - let mut matrix = vec![vec![Zq::zero(); N]; 256]; + /// Defines a matrix of size 256xn + pub fn new(n: usize) -> Self { + let mut matrix = vec![vec![Zq::zero(); n]; 256]; let mut rng = rng(); // Fill the matrix with random values from {-1, 0, 1} for row in matrix.iter_mut() { @@ -49,11 +48,19 @@ impl ProjectionVector { // Iterate over each Rq, extracting the coefficients and concatenating them for rq in rqs { let coeffs = rq.get_coefficients(); - concatenated_coeffs.extend_from_slice(&coeffs); // Extend the Vec with the coefficients + concatenated_coeffs.extend_from_slice(&coeffs); } concatenated_coeffs } + // Euclidean norm + pub fn norm(&self) -> f64 { + self.projection + .iter() + .map(|coeff| coeff.to_f64().powi(2)) + .sum::() + .sqrt() + } /// Calculates Projection pub fn new(matrix: &ProjectionMatrix, s_i: &Vec>) -> Self { @@ -74,12 +81,11 @@ impl ProjectionVector { } } -/// Function to generate a random polynomial -/// Returns a vector of `n` random polynomials, each of degree `d` +/// Returns a vector of `n` random polynomials, each of degree `d`, for testing purposes. pub fn generate_random_polynomials( n: usize, rng: &mut R, - beta: f64, + beta: f64, // vector norm bound ) -> Vec> { let mut polynomials = Vec::with_capacity(n); @@ -87,21 +93,18 @@ pub fn generate_random_polynomials( loop { // Generate random coefficients let coeffs: [Zq; D] = std::array::from_fn(|_| { - let small_value: u32 = rng.random_range(0..100 as u32); // random value between 0 and 100 (as u322) - match small_value.try_into() { - Ok(value) => Zq::new(value), - Err(_) => { - eprintln!("Failed to convert value: {}", small_value); - Zq::new(0) // Default value in case of failure - } - } + // Small values so we get a vector of 'small norm' + let small_value: u32 = rng.random_range(0..100); + Zq::new(small_value) }); // Create the polynomial let polynomial = Rq::new(coeffs); - // Check the norm of the polynomial (assuming norm is some value you can compute) - let norm = compute_norm(&polynomial); // This function should compute the norm of the polynomial + // Compute the norm of all polynomials including the new one + let mut all_polynomials = polynomials.clone(); + all_polynomials.push(polynomial.clone()); + let norm = compute_norm(&all_polynomials); // If the norm is smaller than beta, add the polynomial to the list if norm < beta { @@ -116,10 +119,67 @@ pub fn generate_random_polynomials( } // Helper function to compute the norm of the polynomial -fn compute_norm(polynomial: &Rq) -> f64 { - polynomial - .get_coefficients() +pub fn compute_norm(polynomials: &Vec>) -> f64 { + polynomials .iter() - .map(|&coeff| coeff.to_f64()) - .sum() + .flat_map(|poly| poly.get_coefficients()) // Collect coefficients from all polynomials + .map(|coeff| coeff.to_f64().powi(2)) + .sum::() + .sqrt() +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::rngs::ThreadRng; + use rand::Rng; + + // Norm size error + #[test] + fn norm_distance() { + for _ in 0..5 { + let mut rng = rand::rngs::ThreadRng::default(); + + // Generate random values for n and beta at runtime + const D: usize = 4; + let n: usize = rng.random_range(3..5); // Random vector size between 3 and 10 + let beta: f64 = rng.random_range(200..500) as f64; // 'Small' Random value for beta + + // Generate random polynomials using d and n as runtime values + let polynomials = generate_random_polynomials::(n, &mut rng, beta); + let matrix = ProjectionMatrix::new(D * n); + let projection = ProjectionVector::new(&matrix, &polynomials); + + assert!( + projection.norm() > 128.0_f64.sqrt() * compute_norm(&polynomials), + "This error message implies the Modular Johnson-Lindenstrauss Lemma is working" + ); + } + } + // Average of norms error + #[test] + fn average_norm_distance() { + let mut rng = rand::rngs::ThreadRng::default(); + const D: usize = 4; + let n: usize = 3; + let beta: f64 = 500 as f64; + let polynomials = generate_random_polynomials::(n, &mut rng, beta); + let mut norm_sum = 0.0; + + for _ in 0..50 { + // Generate random projections + let matrix = ProjectionMatrix::new(D * n); + let projection = ProjectionVector::new(&matrix, &polynomials); + // Sum up the norms + norm_sum += projection.norm(); + } + + // Compute the average of the norms + let average_norm = norm_sum / 50.0; + // Assert that the average norm is greater than the expected value + assert!( + average_norm > 128.0_f64.sqrt() * compute_norm(&polynomials), + "This error message implies the Modular Johnson-Lindenstrauss Lemma is working" + ); + } } diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 24c2e8a..a610f00 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -1,3 +1,4 @@ +use labrador::jl::compute_norm; use labrador::jl::generate_random_polynomials; use labrador::jl::ProjectionMatrix; use labrador::jl::ProjectionVector; @@ -6,8 +7,8 @@ use labrador::zq::Zq; use rand::rngs::ThreadRng; // Import ThreadRng const D: usize = 4; // Degree of polynomials (you can change this to the required degree) -const N: usize = 5; // Number of polynomials (you can adjust this as needed) -const BETA: f64 = 50.0; +const N: usize = 3; // Number of polynomials (you can adjust this as needed) +const BETA: f64 = 500.0; fn main() { // Example poly_ring @@ -58,18 +59,23 @@ fn main() { // Johnson Linderstrauss Projections // Example parameters - let mut rng = rand::rngs::ThreadRng::default(); // Corrected RNG instantiation - // Generate the random polynomials + let mut rng = rand::rngs::ThreadRng::default(); let polynomials = generate_random_polynomials::(N, &mut rng, BETA); let matrix = ProjectionMatrix::new(D * N); let projection = ProjectionVector::new(&matrix, &polynomials); - // Print the generated polynomials - for (i, poly) in polynomials.iter().enumerate() { - println!("Polynomial {}: {:?}", i + 1, poly); - } - println!("Projection: {:?}", projection); - // cqalcualte the norm of a vector of zq elements - // compare norms - // norms verification + + // Print the generated polynomial Norms + println!( + "beta = {} | Polynomial Norm = {} | sqrt(128) * norm polynomials = {} | Projection Norm = {}",BETA, + compute_norm(&polynomials),128.0_f64.sqrt() * compute_norm(&polynomials),projection.norm() + ); + + // Show Polynomials S_i + //for (i, poly) in polynomials.iter().enumerate() { + // println!("polynomial {} = {:?}", i, poly); + //} + + // Show projection elements + //println!("elements = {:?}", projection.get_projection()) } diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index 68cf054..e4c8130 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -11,6 +11,7 @@ // - Polynomial evaluation: eval() // - Zero check: is_zero() // - Polynomial equality check: is_equal() +// - Get the Coefficients: get_coefficients() // // Further operations and optimizations will be added in future versions. @@ -29,7 +30,7 @@ impl Rq { pub fn new(coeffs: [Zq; D]) -> Self { Rq { coeffs } } - /// Function to get the coefficients as a slice of Zq + /// Get the coefficients as a vector pub fn get_coefficients(&self) -> Vec { self.coeffs.to_vec() } diff --git a/labrador/src/zq.rs b/labrador/src/zq.rs index b7c0041..ed6449f 100644 --- a/labrador/src/zq.rs +++ b/labrador/src/zq.rs @@ -17,9 +17,9 @@ impl Zq { pub fn new(value: u32) -> Self { Self { value } } - + /// Converts the internal value of Zq to an f64 value pub fn to_f64(&self) -> f64 { - self.value as f64 // Correctly access the 'value' field + self.value as f64 } /// Zero element (additive identity) From 68343b0b8b1677ce742035c587385895d547e4ac Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Fri, 21 Feb 2025 18:57:16 -0300 Subject: [PATCH 29/37] fmt error fixed --- labrador/src/jl.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 02e09dd..05105ba 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -63,15 +63,15 @@ impl ProjectionVector { } /// Calculates Projection - pub fn new(matrix: &ProjectionMatrix, s_i: &Vec>) -> Self { + pub fn new(matrix: &ProjectionMatrix, s_i: &[Rq]) -> Self { let mut projection = vec![Zq::zero(); 256]; - let coefficients = Self::concatenate_coefficients(s_i.clone()); - for i in 0..256 { - projection[i] = matrix.get_matrix()[i] + let coefficients = Self::concatenate_coefficients(s_i.to_vec()); + for (i, item) in projection.iter_mut().enumerate().take(256) { + *item = matrix.get_matrix()[i] .iter() .zip(coefficients.iter()) .map(|(m, s)| *m * *s) - .sum(); + .sum::(); } ProjectionVector { projection } } @@ -119,7 +119,7 @@ pub fn generate_random_polynomials( } // Helper function to compute the norm of the polynomial -pub fn compute_norm(polynomials: &Vec>) -> f64 { +pub fn compute_norm(polynomials: &[Rq]) -> f64 { polynomials .iter() .flat_map(|poly| poly.get_coefficients()) // Collect coefficients from all polynomials @@ -143,7 +143,7 @@ mod tests { // Generate random values for n and beta at runtime const D: usize = 4; let n: usize = rng.random_range(3..5); // Random vector size between 3 and 10 - let beta: f64 = rng.random_range(200..500) as f64; // 'Small' Random value for beta + let beta: f64 = rng.random_range(200.0..500.0); // 'Small' Random value for beta // Generate random polynomials using d and n as runtime values let polynomials = generate_random_polynomials::(n, &mut rng, beta); @@ -162,7 +162,7 @@ mod tests { let mut rng = rand::rngs::ThreadRng::default(); const D: usize = 4; let n: usize = 3; - let beta: f64 = 500 as f64; + let beta: f64 = 500_f64; let polynomials = generate_random_polynomials::(n, &mut rng, beta); let mut norm_sum = 0.0; From 8804369559b6b54675fb499c04d99c3a0fb290e0 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Fri, 21 Feb 2025 19:00:29 -0300 Subject: [PATCH 30/37] Clippy error fixed --- labrador/src/zq.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labrador/src/zq.rs b/labrador/src/zq.rs index ed6449f..2c98d1d 100644 --- a/labrador/src/zq.rs +++ b/labrador/src/zq.rs @@ -19,7 +19,7 @@ impl Zq { } /// Converts the internal value of Zq to an f64 value pub fn to_f64(&self) -> f64 { - self.value as f64 + f64::from(self.value) } /// Zero element (additive identity) From 959baf886eb4aa889bce5db32e7f6740d94c77e8 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Mon, 24 Feb 2025 16:01:07 -0300 Subject: [PATCH 31/37] norm fixed | error fixed | test fixed --- labrador/src/jl.rs | 34 +++++++++++++++++----------------- labrador/src/main.rs | 9 +++++---- labrador/src/zq.rs | 4 ---- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 05105ba..790e102 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -54,12 +54,12 @@ impl ProjectionVector { concatenated_coeffs } // Euclidean norm - pub fn norm(&self) -> f64 { + pub fn norm(&self) -> Zq { self.projection .iter() - .map(|coeff| coeff.to_f64().powi(2)) - .sum::() - .sqrt() + .map(|coeff| *coeff * *coeff) + .sum() + } /// Calculates Projection @@ -85,7 +85,7 @@ impl ProjectionVector { pub fn generate_random_polynomials( n: usize, rng: &mut R, - beta: f64, // vector norm bound + beta: Zq, // vector norm bound ) -> Vec> { let mut polynomials = Vec::with_capacity(n); @@ -107,7 +107,7 @@ pub fn generate_random_polynomials( let norm = compute_norm(&all_polynomials); // If the norm is smaller than beta, add the polynomial to the list - if norm < beta { + if norm.value() < beta.value() { polynomials.push(polynomial); break; // Exit the loop as the polynomial is valid } @@ -119,13 +119,12 @@ pub fn generate_random_polynomials( } // Helper function to compute the norm of the polynomial -pub fn compute_norm(polynomials: &[Rq]) -> f64 { +pub fn compute_norm(polynomials: &[Rq]) -> Zq { polynomials .iter() .flat_map(|poly| poly.get_coefficients()) // Collect coefficients from all polynomials - .map(|coeff| coeff.to_f64().powi(2)) - .sum::() - .sqrt() + .map(|coeff| coeff * coeff) + .sum() } #[cfg(test)] @@ -143,7 +142,7 @@ mod tests { // Generate random values for n and beta at runtime const D: usize = 4; let n: usize = rng.random_range(3..5); // Random vector size between 3 and 10 - let beta: f64 = rng.random_range(200.0..500.0); // 'Small' Random value for beta + let beta: Zq = Zq::new(rng.random_range(200..500)); // 'Small' Random value for beta // Generate random polynomials using d and n as runtime values let polynomials = generate_random_polynomials::(n, &mut rng, beta); @@ -151,7 +150,7 @@ mod tests { let projection = ProjectionVector::new(&matrix, &polynomials); assert!( - projection.norm() > 128.0_f64.sqrt() * compute_norm(&polynomials), + projection.norm().value() < (Zq::new(128) * compute_norm(&polynomials)).value(), "This error message implies the Modular Johnson-Lindenstrauss Lemma is working" ); } @@ -162,23 +161,24 @@ mod tests { let mut rng = rand::rngs::ThreadRng::default(); const D: usize = 4; let n: usize = 3; - let beta: f64 = 500_f64; + let beta: Zq = Zq::new(500); let polynomials = generate_random_polynomials::(n, &mut rng, beta); - let mut norm_sum = 0.0; + let mut norm_sum: u32 = 0; for _ in 0..50 { // Generate random projections let matrix = ProjectionMatrix::new(D * n); let projection = ProjectionVector::new(&matrix, &polynomials); // Sum up the norms - norm_sum += projection.norm(); + norm_sum += projection.norm().value(); } // Compute the average of the norms - let average_norm = norm_sum / 50.0; + let amount: u32 = 50; + let average_norm = norm_sum / amount; // Assert that the average norm is greater than the expected value assert!( - average_norm > 128.0_f64.sqrt() * compute_norm(&polynomials), + average_norm < (Zq::new(128) * compute_norm(&polynomials)).value(), "This error message implies the Modular Johnson-Lindenstrauss Lemma is working" ); } diff --git a/labrador/src/main.rs b/labrador/src/main.rs index a610f00..d94bae6 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -8,7 +8,7 @@ use rand::rngs::ThreadRng; // Import ThreadRng const D: usize = 4; // Degree of polynomials (you can change this to the required degree) const N: usize = 3; // Number of polynomials (you can adjust this as needed) -const BETA: f64 = 500.0; + fn main() { // Example poly_ring @@ -60,15 +60,16 @@ fn main() { // Example parameters // Generate the random polynomials + let beta: Zq = Zq::new(500); let mut rng = rand::rngs::ThreadRng::default(); - let polynomials = generate_random_polynomials::(N, &mut rng, BETA); + let polynomials = generate_random_polynomials::(N, &mut rng, beta); let matrix = ProjectionMatrix::new(D * N); let projection = ProjectionVector::new(&matrix, &polynomials); // Print the generated polynomial Norms println!( - "beta = {} | Polynomial Norm = {} | sqrt(128) * norm polynomials = {} | Projection Norm = {}",BETA, - compute_norm(&polynomials),128.0_f64.sqrt() * compute_norm(&polynomials),projection.norm() + "beta = {} | Polynomial Norm = {} | sqrt(128) * norm polynomials = {} | Projection Norm = {}",beta, + compute_norm(&polynomials),Zq::new(128) * compute_norm(&polynomials),projection.norm() ); // Show Polynomials S_i diff --git a/labrador/src/zq.rs b/labrador/src/zq.rs index 2c98d1d..97d8de3 100644 --- a/labrador/src/zq.rs +++ b/labrador/src/zq.rs @@ -17,10 +17,6 @@ impl Zq { pub fn new(value: u32) -> Self { Self { value } } - /// Converts the internal value of Zq to an f64 value - pub fn to_f64(&self) -> f64 { - f64::from(self.value) - } /// Zero element (additive identity) pub fn zero() -> Self { From 10038f5ee33d23857fc3bc468c15ee1a446f35de Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Mon, 24 Feb 2025 16:02:18 -0300 Subject: [PATCH 32/37] fmt fixed --- labrador/src/jl.rs | 6 +----- labrador/src/main.rs | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 790e102..65a871e 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -55,11 +55,7 @@ impl ProjectionVector { } // Euclidean norm pub fn norm(&self) -> Zq { - self.projection - .iter() - .map(|coeff| *coeff * *coeff) - .sum() - + self.projection.iter().map(|coeff| *coeff * *coeff).sum() } /// Calculates Projection diff --git a/labrador/src/main.rs b/labrador/src/main.rs index d94bae6..219e297 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -9,7 +9,6 @@ use rand::rngs::ThreadRng; // Import ThreadRng const D: usize = 4; // Degree of polynomials (you can change this to the required degree) const N: usize = 3; // Number of polynomials (you can adjust this as needed) - fn main() { // Example poly_ring let p1: Rq<2> = vec![Zq::new(1)].into(); From 058cbb8ef8fd5ebaa9cffc04d98aaeaf08c7e57e Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Mon, 24 Feb 2025 16:09:29 -0300 Subject: [PATCH 33/37] norm test fixed --- labrador/src/jl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 65a871e..0322e1b 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -138,7 +138,7 @@ mod tests { // Generate random values for n and beta at runtime const D: usize = 4; let n: usize = rng.random_range(3..5); // Random vector size between 3 and 10 - let beta: Zq = Zq::new(rng.random_range(200..500)); // 'Small' Random value for beta + let beta: Zq = Zq::new(rng.random_range(500..1000)); // 'Small' Random value for beta // Generate random polynomials using d and n as runtime values let polynomials = generate_random_polynomials::(n, &mut rng, beta); From 3a84533516f5963a55ec0dee095ccb332e219514 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Mon, 24 Feb 2025 16:26:27 -0300 Subject: [PATCH 34/37] tests removed --- labrador/src/jl.rs | 57 ---------------------------------------------- 1 file changed, 57 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 0322e1b..9484b43 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -122,60 +122,3 @@ pub fn compute_norm(polynomials: &[Rq]) -> Zq { .map(|coeff| coeff * coeff) .sum() } - -#[cfg(test)] -mod tests { - use super::*; - use rand::rngs::ThreadRng; - use rand::Rng; - - // Norm size error - #[test] - fn norm_distance() { - for _ in 0..5 { - let mut rng = rand::rngs::ThreadRng::default(); - - // Generate random values for n and beta at runtime - const D: usize = 4; - let n: usize = rng.random_range(3..5); // Random vector size between 3 and 10 - let beta: Zq = Zq::new(rng.random_range(500..1000)); // 'Small' Random value for beta - - // Generate random polynomials using d and n as runtime values - let polynomials = generate_random_polynomials::(n, &mut rng, beta); - let matrix = ProjectionMatrix::new(D * n); - let projection = ProjectionVector::new(&matrix, &polynomials); - - assert!( - projection.norm().value() < (Zq::new(128) * compute_norm(&polynomials)).value(), - "This error message implies the Modular Johnson-Lindenstrauss Lemma is working" - ); - } - } - // Average of norms error - #[test] - fn average_norm_distance() { - let mut rng = rand::rngs::ThreadRng::default(); - const D: usize = 4; - let n: usize = 3; - let beta: Zq = Zq::new(500); - let polynomials = generate_random_polynomials::(n, &mut rng, beta); - let mut norm_sum: u32 = 0; - - for _ in 0..50 { - // Generate random projections - let matrix = ProjectionMatrix::new(D * n); - let projection = ProjectionVector::new(&matrix, &polynomials); - // Sum up the norms - norm_sum += projection.norm().value(); - } - - // Compute the average of the norms - let amount: u32 = 50; - let average_norm = norm_sum / amount; - // Assert that the average norm is greater than the expected value - assert!( - average_norm < (Zq::new(128) * compute_norm(&polynomials)).value(), - "This error message implies the Modular Johnson-Lindenstrauss Lemma is working" - ); - } -} From 519a5c2ef980edbe23f1e6125cd89f7aa1f250d4 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Tue, 25 Feb 2025 21:15:08 -0300 Subject: [PATCH 35/37] Tests and polynomial norm fixed --- labrador/src/jl.rs | 113 +++++++++++++++++++++++++++++++++++-------- labrador/src/main.rs | 40 +++++---------- labrador/src/rq.rs | 105 +++++++++++++++++++++++++++++++++++----- 3 files changed, 200 insertions(+), 58 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 9484b43..4e28afe 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -4,14 +4,15 @@ use rand::prelude::*; use rand::rng; /// Projection matrix with values in {1,0,-1} mod q -pub struct ProjectionMatrix { +pub struct ProjectionMatrix { matrix: Vec>, } -impl ProjectionMatrix { - /// Defines a matrix of size 256xn +impl ProjectionMatrix { + /// Defines a matrix of size 256xnxD + /// n is the size of the vector of polynomials pub fn new(n: usize) -> Self { - let mut matrix = vec![vec![Zq::zero(); n]; 256]; + let mut matrix = vec![vec![Zq::zero(); n * D]; 256]; let mut rng = rng(); // Fill the matrix with random values from {-1, 0, 1} for row in matrix.iter_mut() { @@ -37,11 +38,11 @@ impl ProjectionMatrix { /// Calculate projection vector #[derive(Debug)] pub struct ProjectionVector { - projection: Vec, // 256-dimensional projection vector + projection: [Zq; 256], // 256-dimensional projection vector } impl ProjectionVector { - // Function to concatenate coefficients from multiple Rq into a Vec + /// Function to concatenate coefficients from multiple Rq into a Vec fn concatenate_coefficients(rqs: Vec>) -> Vec { let mut concatenated_coeffs: Vec = Vec::new(); @@ -53,14 +54,14 @@ impl ProjectionVector { concatenated_coeffs } - // Euclidean norm + /// Euclidean norm pub fn norm(&self) -> Zq { self.projection.iter().map(|coeff| *coeff * *coeff).sum() } /// Calculates Projection - pub fn new(matrix: &ProjectionMatrix, s_i: &[Rq]) -> Self { - let mut projection = vec![Zq::zero(); 256]; + pub fn new(matrix: &ProjectionMatrix, s_i: &[Rq]) -> Self { + let mut projection = [Zq::zero(); 256]; let coefficients = Self::concatenate_coefficients(s_i.to_vec()); for (i, item) in projection.iter_mut().enumerate().take(256) { *item = matrix.get_matrix()[i] @@ -72,7 +73,7 @@ impl ProjectionVector { ProjectionVector { projection } } /// Obtain projection - pub fn get_projection(&self) -> &Vec { + pub fn get_projection(&self) -> &[Zq; 256] { &self.projection } } @@ -100,25 +101,97 @@ pub fn generate_random_polynomials( // Compute the norm of all polynomials including the new one let mut all_polynomials = polynomials.clone(); all_polynomials.push(polynomial.clone()); - let norm = compute_norm(&all_polynomials); + let norm = Rq::compute_norm_squared(&all_polynomials); // If the norm is smaller than beta, add the polynomial to the list if norm.value() < beta.value() { polynomials.push(polynomial); - break; // Exit the loop as the polynomial is valid + break; } - // If the norm is greater than or equal to beta, try again with a new random polynomial } } polynomials } -// Helper function to compute the norm of the polynomial -pub fn compute_norm(polynomials: &[Rq]) -> Zq { - polynomials - .iter() - .flat_map(|poly| poly.get_coefficients()) // Collect coefficients from all polynomials - .map(|coeff| coeff * coeff) - .sum() +/// Returns a boolean if the norm of the projection is smaller than 128 for a random polynomial +fn _smaller_than_128b(n: usize) -> bool { + // Generate the random polynomials + let polynomials = Rq::::random_small_vector(n); + // Generate projection matrix + let matrix = ProjectionMatrix::new(n); + // Generate Projection + let projection = ProjectionVector::new(&matrix, &polynomials); + // Check if the norm of the projection is smaller than 128 * (projection of the random polynomial) + let result = + projection.norm().value() < (Zq::new(128) * Rq::compute_norm_squared(&polynomials)).value(); + result } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + // Test that the probability of the inequality being true is close to 1/2 + fn test_probability_is_close_to_half() { + let trials: f64 = 1000.0; + let mut success_count: f64 = 0.0; + for _ in 0..1000 { + if _smaller_than_128b::<64>(5) { + success_count += 1.0; + } + } + + let observed_probability = success_count / trials; + + // we allow some tolerance + let tolerance = 0.05; + assert!( + (observed_probability - 0.5).abs() < tolerance, + "Observed probability {} is not close to 0.5", + observed_probability + ); + } + + #[test] + + // on averge the projected norm squared is the same as 128 * vector norm squared + fn average_value() { + let trials: u32 = 5000; + let n = 5; + let polynomials = Rq::<64>::random_small_vector(n); + let mut matrix = ProjectionMatrix::new(n); + let mut projection = ProjectionVector::new(&matrix, &polynomials); + let mut norm_sum = projection.norm(); + let norm_value = (Zq::new(128) * Rq::compute_norm_squared(&polynomials)).value(); + // Run the test multiple times to simulate the probability + for _ in 0..trials { + matrix = ProjectionMatrix::new(n); + projection = ProjectionVector::new(&matrix, &polynomials); + norm_sum += projection.norm(); + } + + // Calculate the observed probability + let average = norm_sum.value() / trials; + + // we allow some tolerance + let tolerance: u32 = 100; + let abs_difference = if average < norm_value { + norm_value - average + } else { + average - norm_value + }; + + assert!( + abs_difference < tolerance, + "Average norm value {} is not close to {}. Difference: {}", + average, + (Zq::new(128) * Rq::compute_norm_squared(&polynomials)).value(), + abs_difference + ); + } +} + +// define the dot product function +// implement verification of norm bounds for a guiven polynomial vector diff --git a/labrador/src/main.rs b/labrador/src/main.rs index 219e297..b36d487 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -1,18 +1,14 @@ -use labrador::jl::compute_norm; -use labrador::jl::generate_random_polynomials; use labrador::jl::ProjectionMatrix; use labrador::jl::ProjectionVector; use labrador::rq::Rq; use labrador::zq::Zq; -use rand::rngs::ThreadRng; // Import ThreadRng -const D: usize = 4; // Degree of polynomials (you can change this to the required degree) -const N: usize = 3; // Number of polynomials (you can adjust this as needed) +const D: usize = 4; // Degree of polynomials in S_i fn main() { // Example poly_ring - let p1: Rq<2> = vec![Zq::new(1)].into(); - let p2: Rq<2> = vec![Zq::new(2), Zq::new(1), Zq::new(1)].into(); + let p1: Rq = vec![Zq::new(1)].into(); + let p2: Rq = vec![Zq::new(2), Zq::new(1), Zq::new(1)].into(); // Perform polynomial multiplication let product = p1.clone() * p2.clone(); @@ -56,26 +52,16 @@ fn main() { println!("a + b = {}", a + b); // Johnson Linderstrauss Projections - // Example parameters - + // Example // Generate the random polynomials - let beta: Zq = Zq::new(500); - let mut rng = rand::rngs::ThreadRng::default(); - let polynomials = generate_random_polynomials::(N, &mut rng, beta); - let matrix = ProjectionMatrix::new(D * N); + let n = 3; + let polynomials = Rq::::random_small_vector(n); + // Random projection matrix + let matrix = ProjectionMatrix::new(n); + // Calculate projection let projection = ProjectionVector::new(&matrix, &polynomials); - - // Print the generated polynomial Norms - println!( - "beta = {} | Polynomial Norm = {} | sqrt(128) * norm polynomials = {} | Projection Norm = {}",beta, - compute_norm(&polynomials),Zq::new(128) * compute_norm(&polynomials),projection.norm() - ); - - // Show Polynomials S_i - //for (i, poly) in polynomials.iter().enumerate() { - // println!("polynomial {} = {:?}", i, poly); - //} - - // Show projection elements - //println!("elements = {:?}", projection.get_projection()) + // Whithin bounds with probability 1/2 + let result = + projection.norm().value() < (Zq::new(128) * Rq::compute_norm_squared(&polynomials)).value(); + println!("{}", result); } diff --git a/labrador/src/rq.rs b/labrador/src/rq.rs index e4c8130..f174f41 100644 --- a/labrador/src/rq.rs +++ b/labrador/src/rq.rs @@ -2,22 +2,26 @@ // // // Currently implemented functions include: -// - Polynomial addition: + -// - Polynomial multiplication: * -// - inner_product/ Dot product: inner_product() -// - Polynomial subtraction: - -// - Polynomial negation: neg() -// - Scalar multiplication: scalar_mul() -// - Polynomial evaluation: eval() -// - Zero check: is_zero() -// - Polynomial equality check: is_equal() -// - Get the Coefficients: get_coefficients() +// - Polynomial addition: + +// - Polynomial multiplication: * +// - inner_product/ Dot product: inner_product() +// - Polynomial subtraction: - +// - Polynomial negation: neg() +// - Scalar multiplication: scalar_mul() +// - Polynomial evaluation: eval() +// - Zero check: is_zero() +// - Polynomial equality check: is_equal() +// - Get the Coefficients: get_coefficients() +// - Random small norm vector: random_small_vector() +// - Squared norm of coefficients: compute_norm_squared() // // Further operations and optimizations will be added in future versions. // We use the Zq ring use crate::zq::Zq; +use rand::Rng; use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + /// This module provides implementations for various operations /// in the polynomial ring R = Z_q\[X\] / (X^d + 1). #[derive(Debug, Clone, PartialEq, Eq)] @@ -34,7 +38,6 @@ impl Rq { pub fn get_coefficients(&self) -> Vec { self.coeffs.to_vec() } - /// Polynomial addition fn addition(&self, other: &Self) -> Self { let mut result = [Zq::zero(); D]; @@ -123,6 +126,86 @@ impl Rq { pub fn is_equal(&self, other: &Self) -> bool { self.coeffs == other.coeffs } + + /// Generate random small polynomial for commitments + pub fn random_small() -> Self { + let mut rng = rand::rng(); + let mut coeffs = [Zq::zero(); D]; + + for coeff in coeffs.iter_mut() { + // Explicitly sample from {-1, 0, 1} with equal probability + let val = match rng.random_range(0..3) { + 0 => Zq::Q.wrapping_sub(1), // -1 mod q + 1 => 0, // 0 + 2 => 1, // 1 + _ => unreachable!(), + }; + *coeff = Zq::new(val); + } + + Rq::new(coeffs) + } + /// Generate a vector of random small polynomial for commitments of size n + pub fn random_small_vector(n: usize) -> Vec { + let mut v: Vec = Vec::new(); + for _ in 0..n { + v.push(Self::random_small()); + } + v + } + + /// Encode message into polynomial with small coefficients. + /// + /// # Arguments + /// * `message` - A slice of booleans representing a binary message + /// + /// # Returns + /// * `Some(Rq)` - A polynomial where each coefficient is 0 or 1 based on the message bits + /// * `None` - If the message length exceeds the polynomial degree D + /// + /// # Format + /// * Each boolean is encoded as a coefficient: false -> 0, true -> 1 + /// * Message bits are mapped to coefficients in order (index 0 -> constant term) + /// * Remaining coefficients (if message is shorter than D) are set to 0 + pub fn encode_message(message: &[bool]) -> Option { + if message.len() > D { + return None; + } + + let mut coeffs = [Zq::zero(); D]; + for (i, &bit) in message.iter().enumerate() { + coeffs[i] = Zq::new(u32::from(bit)); + } + Some(Rq::new(coeffs)) + } + + /// Iterator over coefficients + pub fn iter(&self) -> std::slice::Iter<'_, Zq> { + self.coeffs.iter() + } + + /// Check if polynomial coefficients are within bounds + pub fn check_bounds(&self, bound: Zq) -> bool { + self.iter().all(|coeff| { + let val = coeff.value(); + // Check if value is within [-bound, bound] + val <= bound.value() || val >= Zq::Q.wrapping_sub(bound.value()) + }) + } + + /// Zero polynomial + pub fn zero() -> Self { + Self::new([Zq::zero(); D]) + } + + // Compute the squared norm of a vector of polynomials + pub fn compute_norm_squared(polynomials: &[Rq]) -> Zq { + polynomials + .iter() + .flat_map(|poly| poly.get_coefficients()) // Collect coefficients from all polynomials + .map(|coeff| coeff * coeff) + .sum() + } } macro_rules! impl_arithmetic { From 09b56aa5a005ee2e89339c8977694ad6e656db95 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Tue, 25 Feb 2025 21:16:23 -0300 Subject: [PATCH 36/37] typo fixed --- labrador/src/jl.rs | 2 +- labrador/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 4e28afe..9378014 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -156,7 +156,7 @@ mod tests { #[test] - // on averge the projected norm squared is the same as 128 * vector norm squared + // on average the projected norm squared is the same as 128 * vector norm squared fn average_value() { let trials: u32 = 5000; let n = 5; diff --git a/labrador/src/main.rs b/labrador/src/main.rs index b36d487..a6620d4 100644 --- a/labrador/src/main.rs +++ b/labrador/src/main.rs @@ -60,7 +60,7 @@ fn main() { let matrix = ProjectionMatrix::new(n); // Calculate projection let projection = ProjectionVector::new(&matrix, &polynomials); - // Whithin bounds with probability 1/2 + // Within bounds with probability 1/2 let result = projection.norm().value() < (Zq::new(128) * Rq::compute_norm_squared(&polynomials)).value(); println!("{}", result); From e6007d34f34241d0f7d35da66a3af444167924e5 Mon Sep 17 00:00:00 2001 From: mattsuffern Date: Tue, 25 Feb 2025 22:23:36 -0300 Subject: [PATCH 37/37] Clippy fixed --- labrador/src/jl.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/labrador/src/jl.rs b/labrador/src/jl.rs index 9378014..bab8858 100644 --- a/labrador/src/jl.rs +++ b/labrador/src/jl.rs @@ -123,9 +123,7 @@ fn _smaller_than_128b(n: usize) -> bool { // Generate Projection let projection = ProjectionVector::new(&matrix, &polynomials); // Check if the norm of the projection is smaller than 128 * (projection of the random polynomial) - let result = - projection.norm().value() < (Zq::new(128) * Rq::compute_norm_squared(&polynomials)).value(); - result + projection.norm().value() < (Zq::new(128) * Rq::compute_norm_squared(&polynomials)).value(); } #[cfg(test)]