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}