clock_curve_math/extensible/fft.rs
1//! Fast Fourier Transform (FFT) implementation for polynomial multiplication.
2//!
3//! This module provides O(n log n) multiplication of large integers using
4//! the Fast Fourier Transform algorithm. It serves as a high-performance
5//! alternative to Karatsuba and Toom-Cook multiplication for very large operands.
6//!
7//! Note: Full FFT operations require the "std" feature for trigonometric functions
8//! and memory allocation. Without std, it falls back to schoolbook multiplication.
9
10use crate::bigint::BigInt;
11use crate::error::MathError;
12
13/// Complex number representation for FFT
14///
15/// This struct provides basic complex arithmetic operations needed
16/// for Fast Fourier Transform algorithms. It includes addition,
17/// multiplication, and scaling operations optimized for FFT usage.
18#[derive(Clone, Copy, Debug)]
19pub struct Complex {
20 /// The real part of the complex number
21 pub real: f64,
22 /// The imaginary part of the complex number
23 pub imag: f64,
24}
25
26impl Complex {
27 /// Create a new complex number
28 ///
29 /// # Arguments
30 /// * `real` - The real part
31 /// * `imag` - The imaginary part
32 ///
33 /// # Returns
34 /// A new Complex number with the specified real and imaginary parts
35 pub const fn new(real: f64, imag: f64) -> Self {
36 Self { real, imag }
37 }
38
39 /// Create a complex number from a real number
40 ///
41 /// # Arguments
42 /// * `real` - The real value to convert
43 ///
44 /// # Returns
45 /// A complex number with the real value and zero imaginary part
46 pub const fn from_real(real: f64) -> Self {
47 Self::new(real, 0.0)
48 }
49
50 /// Complex addition
51 ///
52 /// # Arguments
53 /// * `other` - The complex number to add
54 ///
55 /// # Returns
56 /// The sum of self and other
57 pub fn add(self, other: Self) -> Self {
58 Self::new(self.real + other.real, self.imag + other.imag)
59 }
60
61 /// Complex subtraction
62 ///
63 /// # Arguments
64 /// * `other` - The complex number to subtract
65 ///
66 /// # Returns
67 /// The difference of self and other
68 pub fn sub(self, other: Self) -> Self {
69 Self::new(self.real - other.real, self.imag - other.imag)
70 }
71
72 /// Complex multiplication
73 ///
74 /// # Arguments
75 /// * `other` - The complex number to multiply
76 ///
77 /// # Returns
78 /// The product of self and other
79 pub fn mul(self, other: Self) -> Self {
80 Self::new(
81 self.real * other.real - self.imag * other.imag,
82 self.real * other.imag + self.imag * other.real,
83 )
84 }
85
86 /// Scalar multiplication
87 ///
88 /// # Arguments
89 /// * `scalar` - The real number to multiply by
90 ///
91 /// # Returns
92 /// The complex number scaled by the scalar
93 pub fn scale(self, scalar: f64) -> Self {
94 Self::new(self.real * scalar, self.imag * scalar)
95 }
96
97 /// Complex exponential (Euler's formula)
98 ///
99 /// Computes e^(i*angle) = cos(angle) + i*sin(angle)
100 ///
101 /// # Arguments
102 /// * `angle` - The angle in radians
103 ///
104 /// # Returns
105 /// The complex exponential of the angle
106 #[cfg(feature = "std")]
107 pub fn exp(angle: f64) -> Self {
108 Self::new(angle.cos(), angle.sin())
109 }
110
111 /// Fallback for no_std - requires std feature
112 ///
113 /// This function is not available in no_std environments and will panic.
114 /// Use the std feature to enable FFT functionality.
115 #[cfg(not(feature = "std"))]
116 pub fn exp(_angle: f64) -> Self {
117 panic!("FFT requires std feature for trigonometric functions")
118 }
119}
120
121/// FFT multiplier for large integer multiplication
122///
123/// This struct provides Fast Fourier Transform-based multiplication
124/// for very large integers. It offers O(n log n) complexity compared
125/// to O(n²) for schoolbook multiplication, making it ideal for
126/// cryptographic applications dealing with large numbers.
127///
128/// The FFT implementation requires the "std" feature for trigonometric
129/// functions and memory allocation. Without std, it falls back to
130/// schoolbook multiplication.
131#[cfg(feature = "std")]
132pub struct FFTMultiplier {
133 // No fields needed for basic implementation
134}
135
136#[cfg(feature = "std")]
137impl FFTMultiplier {
138 /// Create a new FFT multiplier
139 ///
140 /// # Returns
141 /// A new FFTMultiplier instance ready for large integer multiplication
142 pub fn new() -> Self {
143 Self {}
144 }
145
146 /// Check if n is a power of 2
147 ///
148 /// # Arguments
149 /// * `n` - The number to check
150 ///
151 /// # Returns
152 /// true if n is a power of 2, false otherwise
153 fn is_power_of_two(n: usize) -> bool {
154 n > 0 && (n & (n - 1)) == 0
155 }
156
157 /// Find next power of 2 >= n
158 ///
159 /// This is used to pad input arrays to optimal FFT sizes.
160 ///
161 /// # Arguments
162 /// * `n` - The minimum size required
163 ///
164 /// # Returns
165 /// The smallest power of 2 that is >= n
166 fn next_power_of_two(n: usize) -> usize {
167 if n <= 1 {
168 return 1;
169 }
170 // Use bit manipulation to find next power of 2
171 let mut result = 1;
172 while result < n {
173 result <<= 1;
174 }
175 result
176 }
177
178 /// Perform bit-reversal permutation on complex array
179 ///
180 /// This reorders the array elements to match the Cooley-Tukey
181 /// FFT algorithm's requirements, improving cache locality.
182 ///
183 /// # Arguments
184 /// * `coeffs` - The complex coefficient array to permute in-place
185 fn bit_reverse_permutation(coeffs: &mut [Complex]) {
186 let n = coeffs.len();
187 let log_n = (n as f64).log2() as usize;
188
189 for i in 0..n {
190 let mut j = 0;
191 for k in 0..log_n {
192 if (i & (1 << k)) != 0 {
193 j |= 1 << (log_n - 1 - k);
194 }
195 }
196 if j > i {
197 coeffs.swap(i, j);
198 }
199 }
200 }
201
202 /// Cooley-Tukey Fast Fourier Transform
203 ///
204 /// Implements the iterative Cooley-Tukey FFT algorithm for transforming
205 /// a sequence of complex coefficients between time and frequency domains.
206 ///
207 /// # Arguments
208 /// * `coeffs` - The complex coefficient array (modified in-place)
209 /// * `inverse` - If true, compute inverse FFT; if false, forward FFT
210 ///
211 /// # Panics
212 /// Panics if the length of coeffs is not a power of 2
213 pub fn cooley_tukey_fft(&self, coeffs: &mut [Complex], inverse: bool) {
214 let n = coeffs.len();
215
216 // Bit-reversal permutation
217 Self::bit_reverse_permutation(coeffs);
218
219 // Iterative Cooley-Tukey algorithm
220 let mut size = 2;
221 while size <= n {
222 let half_size = size / 2;
223 let angle_step =
224 if inverse { 2.0 } else { -2.0 } * std::f64::consts::PI / (size as f64);
225 let wlen = Complex::exp(angle_step);
226
227 let mut i = 0;
228 while i < n {
229 let mut w = Complex::new(1.0, 0.0);
230 for j in 0..half_size {
231 let u = coeffs[i + j];
232 let v = coeffs[i + j + half_size].mul(w);
233 coeffs[i + j] = u.add(v);
234 coeffs[i + j + half_size] = u.sub(v);
235 w = w.mul(wlen);
236 }
237 i += size;
238 }
239
240 size *= 2;
241 }
242
243 // Scale for inverse transform
244 if inverse {
245 let scale = 1.0 / (n as f64);
246 for coeff in coeffs.iter_mut() {
247 *coeff = coeff.scale(scale);
248 }
249 }
250 }
251
252 /// Convert BigInt coefficients to complex Vec
253 fn bigint_to_complex(coeffs: &[BigInt]) -> Vec<Complex> {
254 coeffs
255 .iter()
256 .map(|c| {
257 // Convert BigInt to f64 (this may lose precision for very large numbers)
258 // For cryptographic use, we limit to reasonable sizes
259 let real_val = c.to_u64().unwrap_or(0) as f64;
260 Complex::from_real(real_val)
261 })
262 .collect()
263 }
264
265 /// Convert complex array back to rounded integer coefficients
266 fn complex_to_coeffs(complex_coeffs: &[Complex]) -> Vec<i64> {
267 complex_coeffs
268 .iter()
269 .map(|c| {
270 // Round to nearest integer
271 c.real.round() as i64
272 })
273 .collect()
274 }
275
276 /// Multiply two polynomials using FFT
277 ///
278 /// This implements the standard FFT-based polynomial multiplication algorithm
279 /// with O(n log n) complexity instead of O(n²) for schoolbook multiplication.
280 ///
281 /// The algorithm:
282 /// 1. Convert coefficients to complex numbers
283 /// 2. Pad to next power of 2 length for optimal FFT performance
284 /// 3. Compute forward FFT of both polynomials
285 /// 4. Multiply pointwise in frequency domain
286 /// 5. Compute inverse FFT to get result coefficients
287 /// 6. Extract and round integer coefficients
288 ///
289 /// # Arguments
290 /// * `a` - Coefficients of the first polynomial
291 /// * `b` - Coefficients of the second polynomial
292 ///
293 /// # Returns
294 /// Result coefficients of the product polynomial, or MathError on failure
295 pub fn polynomial_multiply(
296 &self,
297 a: &[BigInt],
298 b: &[BigInt],
299 ) -> Result<Vec<BigInt>, MathError> {
300 // Determine FFT size
301 let n = Self::next_power_of_two(a.len() + b.len() - 1);
302
303 // Convert to complex numbers and pad
304 let mut a_complex = Self::bigint_to_complex(a);
305 let mut b_complex = Self::bigint_to_complex(b);
306
307 a_complex.resize(n, Complex::from_real(0.0));
308 b_complex.resize(n, Complex::from_real(0.0));
309
310 // Forward FFT
311 self.cooley_tukey_fft(&mut a_complex, false);
312 self.cooley_tukey_fft(&mut b_complex, false);
313
314 // Pointwise multiplication in frequency domain
315 let mut c_complex: Vec<Complex> = a_complex
316 .iter()
317 .zip(b_complex.iter())
318 .map(|(x, y)| x.mul(*y))
319 .collect();
320
321 // Inverse FFT
322 self.cooley_tukey_fft(&mut c_complex, true);
323
324 // Convert back to integer coefficients
325 let coeffs_i64 = Self::complex_to_coeffs(&c_complex);
326
327 // Convert to BigInt and remove trailing zeros
328 let mut result: Vec<BigInt> = coeffs_i64
329 .iter()
330 .map(|&x| {
331 if x >= 0 {
332 BigInt::from_u64(x as u64)
333 } else {
334 // Handle negative coefficients (shouldn't happen in multiplication)
335 BigInt::from_u64(0)
336 }
337 })
338 .collect();
339
340 // Remove trailing zeros
341 while result.len() > 1 {
342 if let Some(last) = result.last() {
343 if last.is_zero() {
344 result.pop();
345 } else {
346 break;
347 }
348 } else {
349 break;
350 }
351 }
352
353 Ok(result)
354 }
355
356 /// Multiply two large integers using FFT
357 ///
358 /// This method provides asymptotically optimal multiplication for very large integers
359 /// by converting them to polynomial coefficients, multiplying using FFT, and converting
360 /// back to integer representation.
361 ///
362 /// # Arguments
363 /// * `a` - First large integer to multiply
364 /// * `b` - Second large integer to multiply
365 ///
366 /// # Returns
367 /// The product a * b as a BigInt, or MathError on failure
368 ///
369 /// # Performance
370 /// Offers O(n log n) complexity for n-bit numbers vs O(n²) for schoolbook multiplication
371 pub fn multiply_big_ints(&self, a: &BigInt, b: &BigInt) -> Result<BigInt, MathError> {
372 // Use base 10^9 for coefficient conversion (good balance of precision and efficiency)
373 const BASE: u64 = 1_000_000_000;
374
375 // Convert to base-10^9 digits
376 let a_coeffs = Self::big_int_to_base_digits(a, BASE);
377 let b_coeffs = Self::big_int_to_base_digits(b, BASE);
378
379 // Multiply polynomials
380 let product_coeffs = self.polynomial_multiply(&a_coeffs, &b_coeffs)?;
381
382 // Convert back to BigInt
383 let result = Self::coeffs_to_big_int(&product_coeffs, BASE);
384
385 Ok(result)
386 }
387
388 /// Convert BigInt to base digits
389 ///
390 /// Decomposes a large integer into digits in the specified base.
391 /// This is used to convert integers to polynomial coefficients for FFT multiplication.
392 ///
393 /// # Arguments
394 /// * `n` - The BigInt to convert
395 /// * `base` - The numerical base for digit decomposition
396 ///
397 /// # Returns
398 /// Vector of digits in the specified base (least significant first)
399 fn big_int_to_base_digits(n: &BigInt, base: u64) -> Vec<BigInt> {
400 if n.is_zero() {
401 return vec![BigInt::from_u64(0)];
402 }
403
404 let mut digits = Vec::new();
405 let mut current = n.clone();
406 let base_big = BigInt::from_u64(base);
407
408 while !current.is_zero() {
409 let (quotient, remainder) = current.div_rem(&base_big);
410 digits.push(remainder);
411 current = quotient;
412 }
413
414 digits
415 }
416
417 /// Convert coefficient array back to BigInt
418 ///
419 /// Reconstructs a BigInt from its base-digit representation.
420 ///
421 /// # Arguments
422 /// * `coeffs` - The digit coefficients (least significant first)
423 /// * `base` - The numerical base used for the digits
424 ///
425 /// # Returns
426 /// The reconstructed BigInt value
427 fn coeffs_to_big_int(coeffs: &[BigInt], base: u64) -> BigInt {
428 let mut result = BigInt::from_u64(0);
429 let base_big = BigInt::from_u64(base);
430
431 for (i, coeff) in coeffs.iter().enumerate() {
432 if i == 0 {
433 result = coeff.clone();
434 } else {
435 // result += coeff * (base^i)
436 // For efficiency, we use iterative multiplication instead of pow
437 let mut power = BigInt::from_u64(1);
438 for _ in 0..i {
439 power = power.mul(&base_big);
440 }
441 let term = coeff.mul(&power);
442 result = result.add(&term);
443 }
444 }
445
446 result
447 }
448}
449
450/// Fallback FFT multiplier for no_std environments
451///
452/// This implementation provides API compatibility when the std feature is not available.
453/// It falls back to basic schoolbook multiplication since FFT requires trigonometric
454/// functions and complex arithmetic which are not available in no_std.
455#[cfg(not(feature = "std"))]
456pub struct FFTMultiplier;
457
458#[cfg(not(feature = "std"))]
459impl FFTMultiplier {
460 /// Create a new FFT multiplier (no_std fallback)
461 ///
462 /// # Returns
463 /// A new FFTMultiplier instance that uses schoolbook multiplication
464 pub const fn new() -> Self {
465 Self
466 }
467
468 /// Multiply using schoolbook multiplication in no_std
469 ///
470 /// Since FFT requires std features for trigonometric functions,
471 /// this falls back to basic BigInt multiplication.
472 ///
473 /// # Arguments
474 /// * `a` - First integer to multiply
475 /// * `b` - Second integer to multiply
476 ///
477 /// # Returns
478 /// The product a * b
479 pub fn multiply_big_ints(&self, a: &BigInt, b: &BigInt) -> Result<BigInt, MathError> {
480 // In no_std, fall back to schoolbook multiplication
481 // FFT requires std feature for trigonometric functions
482 Ok(mul_schoolbook(a, b))
483 }
484}
485
486/// Schoolbook multiplication for fallback
487///
488/// Basic polynomial multiplication using BigInt's built-in multiplication.
489/// This serves as the fallback when FFT is not available.
490fn mul_schoolbook(a: &BigInt, b: &BigInt) -> BigInt {
491 a.mul(b)
492}
493
494/// Standalone FFT multiplication function
495///
496/// This is the main entry point for FFT-based multiplication in the library.
497/// It automatically chooses between FFT and fallback algorithms based on input size
498/// and available features.
499///
500/// # Algorithm Selection:
501/// - Uses FFT for very large numbers (>10,000 bits) when std feature is enabled
502/// - Falls back to Toom-Cook multiplication for medium-sized numbers
503/// - Uses schoolbook multiplication for small numbers or when std is unavailable
504///
505/// # Arguments
506/// * `a` - First large integer to multiply
507/// * `b` - Second large integer to multiply
508///
509/// # Returns
510/// The product a * b as a BigInt
511///
512/// # Performance
513/// Provides O(n log n) complexity for large integers where FFT is beneficial
514pub fn mul_fft_standalone(a: &BigInt, b: &BigInt) -> BigInt {
515 // FFT multiplication for extremely large operands
516 // FFT provides O(n log n) complexity vs O(n^2) for schoolbook multiplication
517
518 let a_bits = a.bit_length();
519 let b_bits = b.bit_length();
520 let max_bits = core::cmp::max(a_bits, b_bits);
521
522 // Use FFT threshold - in practice this would be much lower with full implementation
523 const FFT_THRESHOLD_BITS: u32 = 10000; // Use FFT for numbers > 10,000 bits
524
525 if max_bits < FFT_THRESHOLD_BITS {
526 return super::multiplication::mul_toom_cook_standalone(a, b);
527 }
528
529 // Use FFT for very large numbers (requires std feature)
530 #[cfg(feature = "std")]
531 {
532 let multiplier = FFTMultiplier::new();
533 match multiplier.multiply_big_ints(a, b) {
534 Ok(result) => result,
535 Err(_) => {
536 // Fall back to Toom-Cook if FFT fails
537 super::multiplication::mul_toom_cook_standalone(a, b)
538 }
539 }
540 }
541
542 #[cfg(not(feature = "std"))]
543 {
544 // Fall back to Toom-Cook in no_std environments
545 super::multiplication::mul_toom_cook_standalone(a, b)
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 /// Test basic complex number arithmetic operations
554 #[test]
555 fn test_complex_arithmetic() {
556 let z1 = Complex::new(1.0, 2.0);
557 let z2 = Complex::new(3.0, 4.0);
558
559 let sum = z1.add(z2);
560 assert!((sum.real - 4.0).abs() < 1e-10);
561 assert!((sum.imag - 6.0).abs() < 1e-10);
562
563 let product = z1.mul(z2);
564 assert!((product.real - -5.0).abs() < 1e-10);
565 assert!((product.imag - 10.0).abs() < 1e-10);
566 }
567
568 /// Test power-of-two detection utility function
569 #[cfg(feature = "std")]
570 #[test]
571 fn test_power_of_two() {
572 assert!(FFTMultiplier::is_power_of_two(1));
573 assert!(FFTMultiplier::is_power_of_two(2));
574 assert!(FFTMultiplier::is_power_of_two(4));
575 assert!(FFTMultiplier::is_power_of_two(8));
576 assert!(!FFTMultiplier::is_power_of_two(3));
577 assert!(!FFTMultiplier::is_power_of_two(6));
578 }
579
580 /// Test next power-of-two calculation
581 #[cfg(feature = "std")]
582 #[test]
583 fn test_next_power_of_two() {
584 assert_eq!(FFTMultiplier::next_power_of_two(1), 1);
585 assert_eq!(FFTMultiplier::next_power_of_two(2), 2);
586 assert_eq!(FFTMultiplier::next_power_of_two(3), 4);
587 assert_eq!(FFTMultiplier::next_power_of_two(5), 8);
588 assert_eq!(FFTMultiplier::next_power_of_two(9), 16);
589 }
590
591 /// Test polynomial multiplication using FFT
592 #[cfg(feature = "std")]
593 #[test]
594 fn test_fft_polynomial_multiply() {
595 let multiplier = FFTMultiplier::new();
596
597 // (1 + 2x + 3x²) * (4 + 5x) = 4 + 13x + 22x² + 15x³
598 let a = vec![
599 BigInt::from_u64(1),
600 BigInt::from_u64(2),
601 BigInt::from_u64(3),
602 ];
603 let b = vec![BigInt::from_u64(4), BigInt::from_u64(5)];
604
605 let result = multiplier.polynomial_multiply(&a, &b).unwrap();
606
607 assert_eq!(result.len(), 4);
608 assert_eq!(result[0].to_u64().unwrap(), 4);
609 assert_eq!(result[1].to_u64().unwrap(), 13);
610 assert_eq!(result[2].to_u64().unwrap(), 22);
611 assert_eq!(result[3].to_u64().unwrap(), 15);
612 }
613
614 /// Test big integer multiplication using FFT
615 #[test]
616 fn test_big_int_multiply() {
617 #[cfg(feature = "std")]
618 let multiplier = FFTMultiplier::new();
619
620 #[cfg(not(feature = "std"))]
621 let multiplier = FFTMultiplier;
622
623 let a = BigInt::from_u64(123);
624 let b = BigInt::from_u64(456);
625 let expected = BigInt::from_u64(56088);
626
627 let result = multiplier.multiply_big_ints(&a, &b).unwrap();
628 assert_eq!(result, expected);
629 }
630
631 /// Test standalone FFT multiplication function
632 #[test]
633 fn test_fft_standalone_fallback() {
634 let a = BigInt::from_u64(123);
635 let b = BigInt::from_u64(456);
636 let expected = BigInt::from_u64(56088);
637
638 let result = mul_fft_standalone(&a, &b);
639 assert_eq!(result, expected);
640 }
641}