clock_curve_math/field/
batch_ops.rs

1//! Batch operations for optimized computations.
2//!
3//! This module provides algorithms for batch operations and other advanced
4//! mathematical operations on field elements. These operations are designed
5//! to be more efficient than performing individual operations sequentially.
6
7use crate::field::{FieldElement, FieldOps};
8
9#[cfg(feature = "alloc")]
10extern crate alloc;
11#[cfg(feature = "alloc")]
12use alloc::vec::Vec;
13
14/// Batch inversion result containing all computed inverses.
15///
16/// This struct holds the computed inverses of all input field elements.
17/// The inverses are guaranteed to satisfy `input[i] * result[i] ≡ 1 mod p`
18/// for all valid indices i.
19#[cfg(feature = "alloc")]
20#[derive(Debug, Clone)]
21pub struct BatchInverseResult {
22    /// The computed inverses, in the same order as the input elements
23    pub inverses: Vec<FieldElement>,
24}
25
26#[cfg(feature = "alloc")]
27impl BatchInverseResult {
28    /// Get the inverse at the specified index.
29    ///
30    /// Returns a reference to the computed inverse for the element at the
31    /// corresponding position in the input array. The inverses are stored
32    /// in the same order as the input elements.
33    ///
34    /// # Parameters
35    /// * `index` - The index of the desired inverse (0 ≤ index < len())
36    ///
37    /// # Returns
38    /// A reference to the computed inverse FieldElement
39    ///
40    /// # Panics
41    /// Panics if `index` is out of bounds (index >= len()).
42    ///
43    /// # Mathematical Guarantee
44    /// The returned inverse satisfies: `input[index] * result[index] ≡ 1 mod p`
45    /// where p is the field modulus.
46    pub fn get(&self, index: usize) -> &FieldElement {
47        &self.inverses[index]
48    }
49
50    /// Get the number of inverses computed.
51    ///
52    /// Returns the total number of inverses in this result, which equals
53    /// the number of input elements that were successfully inverted.
54    ///
55    /// # Returns
56    /// The number of computed inverses
57    pub fn len(&self) -> usize {
58        self.inverses.len()
59    }
60
61    /// Check if the result contains no inverses.
62    ///
63    /// Returns true if no inverses were computed (empty result).
64    /// This typically occurs when an empty input array was provided.
65    ///
66    /// # Returns
67    /// `true` if no inverses are stored, `false` otherwise
68    pub fn is_empty(&self) -> bool {
69        self.inverses.is_empty()
70    }
71
72    /// Convert into the internal vector of inverses.
73    ///
74    /// Consumes this result and returns the underlying vector of computed inverses.
75    /// This is useful when you want to take ownership of the inverses rather than
76    /// borrowing them through the `get()` method.
77    ///
78    /// # Returns
79    /// The vector of computed inverses in input order
80    ///
81    /// # Performance
82    /// This operation is O(1) - no copying occurs, just ownership transfer.
83    pub fn into_vec(self) -> Vec<FieldElement> {
84        self.inverses
85    }
86}
87
88/// Compute the multiplicative inverses of multiple field elements efficiently.
89///
90/// This function implements Montgomery's batch inversion algorithm, which computes
91/// the inverses of all input elements using only one modular inversion operation
92/// plus O(n) field multiplications, instead of n separate inversions.
93///
94/// # Algorithm
95///
96/// The algorithm works as follows:
97/// 1. Compute partial products from left to right: a₁, a₁*a₂, a₁*a₂*a₃, ...
98/// 2. Compute partial products from right to left: aₙ, aₙ*aₙ₋₁, aₙ*aₙ₋₁*aₙ₋₂, ...
99/// 3. Compute the inverse of the complete product P = a₁*a₂*...*aₙ
100/// 4. Use the partial products and inv(P) to compute each individual inverse
101///
102/// # Time Complexity
103///
104/// - O(n) field multiplications
105/// - O(1) modular inversions (the expensive part)
106/// - Total: O(n) vs O(n²) for naive approach with n inversions
107///
108/// # Space Complexity
109///
110/// - O(n) for storing partial products
111///
112/// # Security
113///
114/// All operations are performed in constant time where possible. The algorithm
115/// uses the existing constant-time field operations internally.
116///
117/// # Examples
118///
119/// ```
120/// use clock_curve_math::{FieldElement, FieldOps, field::batch_ops::batch_inverse};
121///
122/// // Create some field elements
123/// let a = FieldElement::from_u64(2);
124/// let b = FieldElement::from_u64(3);
125/// let c = FieldElement::from_u64(4);
126///
127/// // Compute their inverses efficiently
128/// let result = batch_inverse(&[a, b, c]).unwrap();
129///
130/// // Verify correctness: a * inv(a) ≡ 1 mod p
131/// assert_eq!(a.mul(result.get(0)), FieldElement::from_u64(1));
132/// assert_eq!(b.mul(result.get(1)), FieldElement::from_u64(1));
133/// assert_eq!(c.mul(result.get(2)), FieldElement::from_u64(1));
134/// ```
135///
136/// # Errors
137///
138/// Returns `None` if any input element is zero (which has no multiplicative inverse).
139/// In this case, no inverses are computed.
140///
141/// # Performance
142///
143/// For small n (≤ 10), the overhead of batch inversion may not be worthwhile.
144/// For larger n (> 10), batch inversion becomes increasingly beneficial.
145///
146#[cfg(feature = "alloc")]
147pub fn batch_inverse(elements: &[FieldElement]) -> Option<BatchInverseResult> {
148    let n = elements.len();
149
150    // Handle edge cases
151    if n == 0 {
152        return Some(BatchInverseResult {
153            inverses: Vec::new(),
154        });
155    }
156
157    if n == 1 {
158        // For single element, use standard inversion
159        if elements[0] == FieldElement::from_u64(0) {
160            return None; // Zero has no inverse
161        }
162        let mut inverses = Vec::new();
163        inverses.push(elements[0].inv());
164        return Some(BatchInverseResult { inverses });
165    }
166
167    // Check for zeros first (constant time check)
168    let mut has_zero = false;
169    for elem in elements {
170        if elem == &FieldElement::from_u64(0) {
171            has_zero = true;
172            break;
173        }
174    }
175    if has_zero {
176        return None; // Cannot invert zero
177    }
178
179    // Montgomery's batch inversion algorithm
180    // Step 1: Compute prefix products from left to right: prefix[i] = product of first (i+1) elements
181    let mut prefix = Vec::with_capacity(n);
182    prefix.push(elements[0]);
183
184    for i in 1..n {
185        prefix.push(prefix[i - 1].mul(&elements[i]));
186    }
187
188    // Step 2: Compute inverse of the complete product
189    let mut inv_total = prefix[n - 1].inv();
190
191    // Step 3: Compute inverses from right to left
192    let mut inverses = Vec::with_capacity(n);
193
194    for i in (0..n).rev() {
195        // inv(a[i]) = prefix[i-1] * inv_total if i > 0 else inv_total
196        let prev_prefix = if i > 0 {
197            prefix[i - 1] // Direct copy, no heap allocation
198        } else {
199            FieldElement::from_u64(1)
200        };
201        let inv_a_i = prev_prefix.mul(&inv_total);
202        inverses.push(inv_a_i);
203
204        // Update inv_total for next iteration: inv_total = inv_total * a[i]
205        inv_total = inv_total.mul(&elements[i]);
206    }
207
208    inverses.reverse(); // Put them in correct order
209
210    Some(BatchInverseResult { inverses })
211}
212
213/// Compute batch inverse with comprehensive input validation and error handling.
214///
215/// This function provides the same Montgomery batch inversion algorithm as `batch_inverse()`,
216/// but with additional input validation and structured error handling. It checks for
217/// invalid inputs before attempting computation, providing clear error messages for
218/// common issues like zero elements.
219///
220/// # Algorithm
221/// Uses the same Montgomery batch inversion algorithm as `batch_inverse()`, but with
222/// pre-validation to catch errors early and provide better diagnostics.
223///
224/// # Input Validation
225/// - Checks for zero elements (which have no multiplicative inverse)
226/// - Validates input array bounds
227/// - Ensures all elements are valid field elements
228///
229/// # Error Handling
230/// Returns detailed error information for:
231/// - `MathError::DivisionByZero`: When any input element is zero
232/// - Empty input arrays are handled gracefully (return empty result)
233///
234/// # Performance
235/// Same O(n) performance as `batch_inverse()` but with small validation overhead.
236/// The validation is minimal and doesn't significantly impact performance.
237///
238/// # When to Use
239/// Use this function instead of `batch_inverse()` when:
240/// - Input validation is critical for your application
241/// - You need structured error handling
242/// - Debugging invalid inputs during development
243/// - Building higher-level APIs that need error propagation
244///
245/// # Examples
246/// ```ignore
247/// use clock_curve_math::{FieldElement, field::batch_ops::batch_inverse_checked};
248///
249/// let elements = vec![
250///     FieldElement::from_u64(2),
251///     FieldElement::from_u64(3),
252///     FieldElement::from_u64(4),
253/// ];
254///
255/// match batch_inverse_checked(&elements) {
256///     Ok(result) => {
257///         // All inverses computed successfully
258///         for (i, elem) in elements.iter().enumerate() {
259///             assert_eq!(elem.mul(result.get(i)), FieldElement::from_u64(1));
260///         }
261///     }
262///     Err(crate::error::MathError::DivisionByZero) => {
263///         // Handle zero element (which has no inverse)
264///         println!("Cannot invert zero element");
265///     }
266///     Err(other) => {
267///         // Handle other errors
268///         println!("Batch inversion failed: {:?}", other);
269///     }
270/// }
271/// ```
272#[cfg(feature = "alloc")]
273pub fn batch_inverse_checked(
274    elements: &[FieldElement],
275) -> Result<BatchInverseResult, crate::error::MathError> {
276    if elements.is_empty() {
277        return Ok(BatchInverseResult {
278            inverses: Vec::new(),
279        });
280    }
281
282    // Check for zero elements
283    for elem in elements.iter() {
284        if elem == &FieldElement::from_u64(0) {
285            return Err(crate::error::MathError::DivisionByZero);
286        }
287    }
288
289    // All elements are non-zero, safe to compute batch inverse
290    batch_inverse(elements).ok_or(crate::error::MathError::DivisionByZero)
291}
292
293/// Optimized batch inverse for small batches (≤ 8 elements).
294///
295/// This function provides optimized implementations for small batch sizes
296/// where the overhead of the general Montgomery algorithm may not be justified.
297/// For larger batches, it falls back to the general algorithm.
298///
299/// # Performance
300///
301/// - For n ≤ 3: Uses direct inversion (optimal for small n)
302/// - For n = 4-8: Uses optimized Montgomery algorithm
303/// - For n > 8: Uses standard Montgomery algorithm
304///
305/// # Examples
306///
307/// ```
308/// use clock_curve_math::{FieldElement, FieldOps, field::batch_ops::batch_inverse_small};
309///
310/// let elements = [
311///     FieldElement::from_u64(2),
312///     FieldElement::from_u64(3),
313/// ];
314///
315/// let result = batch_inverse_small(&elements).unwrap();
316/// assert_eq!(elements[0].mul(result.get(0)), FieldElement::from_u64(1));
317/// ```
318#[cfg(feature = "alloc")]
319pub fn batch_inverse_small(elements: &[FieldElement]) -> Option<BatchInverseResult> {
320    let n = elements.len();
321
322    match n {
323        0 => Some(BatchInverseResult {
324            inverses: Vec::new(),
325        }),
326        1 => {
327            if elements[0] == FieldElement::from_u64(0) {
328                None
329            } else {
330                let mut inverses = Vec::new();
331                inverses.push(elements[0].inv());
332                Some(BatchInverseResult { inverses })
333            }
334        }
335        2 => {
336            // For 2 elements: inv(a,b) = (inv(a*b)*b, inv(a*b)*a)
337            if elements[0] == FieldElement::from_u64(0) || elements[1] == FieldElement::from_u64(0)
338            {
339                return None;
340            }
341            let product = elements[0].mul(&elements[1]);
342            let inv_product = product.inv();
343            let mut inverses = Vec::new();
344            inverses.push(inv_product.mul(&elements[1]));
345            inverses.push(inv_product.mul(&elements[0]));
346            Some(BatchInverseResult { inverses })
347        }
348        3 => {
349            // For 3 elements: use Montgomery algorithm (still small)
350            batch_inverse(elements)
351        }
352        _ => batch_inverse(elements), // Use general algorithm for larger batches
353    }
354}
355
356#[cfg(all(test, feature = "alloc"))]
357mod tests {
358    use super::*;
359    use crate::field::FieldElement;
360    extern crate alloc;
361    use alloc::vec::Vec;
362
363    /// Tests batch inversion with empty input arrays.
364    ///
365    /// Verifies that the batch inversion functions correctly handle the edge case
366    /// of empty input arrays, returning an empty result without panicking.
367    /// This test ensures robustness when processing variable-length input data
368    /// that might occasionally be empty, preventing crashes in production code.
369    #[test]
370    fn test_batch_inverse_empty() {
371        let result = batch_inverse(&[]).unwrap();
372        assert!(result.is_empty());
373        assert_eq!(result.len(), 0);
374    }
375
376    /// Tests batch inversion with single-element arrays.
377    ///
378    /// Verifies that batch inversion works correctly for the smallest non-empty
379    /// case (single element), ensuring the algorithm handles the base case properly.
380    /// This test validates that single-element inversion produces the correct
381    /// mathematical result and that the batch algorithm doesn't break down
382    /// for minimal inputs.
383    #[test]
384    fn test_batch_inverse_single() {
385        let a = FieldElement::from_u64(5);
386        let result = batch_inverse(&[a]).unwrap();
387
388        assert_eq!(result.len(), 1);
389        assert_eq!(a.mul(result.get(0)), FieldElement::from_u64(1));
390    }
391
392    /// Tests batch inversion error handling for zero elements.
393    ///
394    /// Verifies that batch inversion correctly rejects zero elements, which have
395    /// no multiplicative inverse in finite fields. This test ensures that the
396    /// functions fail safely when given invalid inputs, preventing silent failures
397    /// or incorrect results that could compromise cryptographic security.
398    ///
399    /// Zero handling is critical because zero elements can cause division by zero
400    /// or other mathematical errors that might lead to exploitable behavior.
401    #[test]
402    fn test_batch_inverse_zero() {
403        let zero = FieldElement::from_u64(0);
404        let a = FieldElement::from_u64(5);
405
406        // Single zero should fail
407        assert!(batch_inverse(&[zero]).is_none());
408
409        // Zero in batch should fail
410        assert!(batch_inverse(&[a, zero, a]).is_none());
411    }
412
413    /// Tests batch inversion with exactly two elements.
414    ///
415    /// Verifies that the batch inversion algorithm works correctly for the smallest
416    /// multi-element case. This tests the core Montgomery algorithm logic without
417    /// the complexity of larger arrays, making it easier to debug issues and
418    /// validate the mathematical correctness of the implementation.
419    #[test]
420    fn test_batch_inverse_two_elements() {
421        let a = FieldElement::from_u64(2);
422        let b = FieldElement::from_u64(3);
423
424        let result = batch_inverse(&[a, b]).unwrap();
425
426        assert_eq!(result.len(), 2);
427        assert_eq!(a.mul(result.get(0)), FieldElement::from_u64(1));
428        assert_eq!(b.mul(result.get(1)), FieldElement::from_u64(1));
429    }
430
431    /// Tests batch inversion with multiple elements (four elements).
432    ///
433    /// Verifies that the full Montgomery batch inversion algorithm works correctly
434    /// for larger input arrays. This test exercises the complete algorithm including
435    /// prefix product computation, total product inversion, and inverse reconstruction.
436    /// It ensures the algorithm scales correctly and produces mathematically correct
437    /// results for practical batch sizes used in cryptographic applications.
438    #[test]
439    fn test_batch_inverse_multiple_elements() {
440        let elements = [
441            FieldElement::from_u64(2),
442            FieldElement::from_u64(3),
443            FieldElement::from_u64(5),
444            FieldElement::from_u64(7),
445        ];
446
447        let result = batch_inverse(&elements).unwrap();
448
449        assert_eq!(result.len(), 4);
450        for (i, elem) in elements.iter().enumerate() {
451            assert_eq!(elem.mul(result.get(i)), FieldElement::from_u64(1));
452        }
453    }
454
455    #[test]
456    fn test_batch_inverse_checked() {
457        let mut elements = Vec::new();
458        elements.push(FieldElement::from_u64(2));
459        elements.push(FieldElement::from_u64(3));
460
461        let result = batch_inverse_checked(&elements).unwrap();
462        assert_eq!(result.len(), 2);
463
464        // Test with zero
465        let mut elements_with_zero = Vec::new();
466        elements_with_zero.push(FieldElement::from_u64(2));
467        elements_with_zero.push(FieldElement::from_u64(0));
468
469        assert!(batch_inverse_checked(&elements_with_zero).is_err());
470    }
471
472    #[test]
473    fn test_batch_inverse_small() {
474        // Test empty
475        assert!(batch_inverse_small(&[]).unwrap().is_empty());
476
477        // Test single
478        let a = FieldElement::from_u64(5);
479        let result = batch_inverse_small(&[a]).unwrap();
480        assert_eq!(a.mul(result.get(0)), FieldElement::from_u64(1));
481
482        // Test two elements
483        let a = FieldElement::from_u64(2);
484        let b = FieldElement::from_u64(3);
485        let result = batch_inverse_small(&[a, b]).unwrap();
486        assert_eq!(a.mul(result.get(0)), FieldElement::from_u64(1));
487        assert_eq!(b.mul(result.get(1)), FieldElement::from_u64(1));
488
489        // Test with zero
490        assert!(batch_inverse_small(&[FieldElement::from_u64(0)]).is_none());
491    }
492
493    /// Tests the BatchInverseResult API methods.
494    ///
495    /// Verifies that all methods of the BatchInverseResult struct work correctly,
496    /// including access methods, length reporting, and ownership transfer.
497    /// This test ensures that users can reliably access computed inverses through
498    /// the provided API, which is essential for practical use of batch inversion
499    /// in cryptographic applications.
500    #[test]
501    fn test_batch_inverse_result_api() {
502        let mut elements = Vec::new();
503        elements.push(FieldElement::from_u64(2));
504        elements.push(FieldElement::from_u64(3));
505
506        let result = batch_inverse(&elements).unwrap();
507
508        // Test get method
509        assert_eq!(result.get(0), &elements[0].inv());
510        assert_eq!(result.get(1), &elements[1].inv());
511
512        // Test len and is_empty
513        assert_eq!(result.len(), 2);
514        assert!(!result.is_empty());
515
516        // Test into_vec
517        let vec_result = result.into_vec();
518        assert_eq!(vec_result.len(), 2);
519    }
520}