Skip to main content

dory_pcs/backends/arkworks/
ark_cache.rs

1//! Prepared point cache for BN254 pairing optimization
2//!
3//! This module provides a global cache for prepared G1/G2 points that are reused
4//! across multiple pairing operations. Prepared points skip the affine conversion
5//! and preprocessing steps, providing ~20-30% speedup for repeated pairings.
6//!
7//! The cache supports smart re-initialization: if a larger setup is needed,
8//! the cache is automatically replaced. Smaller or equal setups reuse the existing cache.
9
10use super::ark_group::{ArkG1, ArkG2};
11use ark_bn254::{Bn254, G1Affine, G2Affine};
12use ark_ec::pairing::Pairing;
13use std::sync::{Arc, RwLock};
14
15/// Global cache for prepared points
16#[derive(Debug, Clone)]
17pub struct PreparedCache {
18    /// Prepared G1 points for efficient pairing operations
19    pub g1_prepared: Vec<<Bn254 as Pairing>::G1Prepared>,
20    /// Prepared G2 points for efficient pairing operations
21    pub g2_prepared: Vec<<Bn254 as Pairing>::G2Prepared>,
22}
23
24static CACHE: RwLock<Option<Arc<PreparedCache>>> = RwLock::new(None);
25
26/// Initialize the global cache with G1 and G2 vectors.
27///
28/// This function implements smart re-initialization:
29/// - If the cache doesn't exist, it creates one
30/// - If the cache exists but is too small, it replaces it with a larger one
31/// - If the cache exists and is large enough, it does nothing (reuses existing)
32///
33/// This allows multiple proofs with different setup sizes to run in the same process.
34///
35/// # Arguments
36/// * `g1_vec` - Vector of G1 points to prepare and cache
37/// * `g2_vec` - Vector of G2 points to prepare and cache
38///
39/// # Panics
40/// Panics if the internal `RwLock` is poisoned.
41///
42/// # Example
43/// ```ignore
44/// use dory_pcs::backends::arkworks::{init_cache, BN254};
45/// use dory_pcs::setup::ProverSetup;
46///
47/// let setup = ProverSetup::<BN254>::new(max_log_n);
48/// init_cache(&setup.g1_vec, &setup.g2_vec);
49/// ```
50pub fn init_cache(g1_vec: &[ArkG1], g2_vec: &[ArkG2]) {
51    // Fast path: check if existing cache is sufficient (read lock only)
52    {
53        let read_guard = CACHE.read().unwrap();
54        if let Some(ref cache) = *read_guard {
55            if cache.g1_prepared.len() >= g1_vec.len() && cache.g2_prepared.len() >= g2_vec.len() {
56                return; // Existing cache is large enough
57            }
58        }
59    }
60
61    // Slow path: need to initialize or grow the cache
62    let mut write_guard = CACHE.write().unwrap();
63
64    // Double-check after acquiring write lock (another thread may have initialized)
65    if let Some(ref cache) = *write_guard {
66        if cache.g1_prepared.len() >= g1_vec.len() && cache.g2_prepared.len() >= g2_vec.len() {
67            return; // Another thread initialized a sufficient cache
68        }
69    }
70
71    // Prepare the new cache
72    let g1_prepared: Vec<<Bn254 as Pairing>::G1Prepared> = g1_vec
73        .iter()
74        .map(|g| {
75            let affine: G1Affine = g.0.into();
76            affine.into()
77        })
78        .collect();
79
80    let g2_prepared: Vec<<Bn254 as Pairing>::G2Prepared> = g2_vec
81        .iter()
82        .map(|g| {
83            let affine: G2Affine = g.0.into();
84            affine.into()
85        })
86        .collect();
87
88    *write_guard = Some(Arc::new(PreparedCache {
89        g1_prepared,
90        g2_prepared,
91    }));
92}
93
94/// Invalidate the global cache, dropping any prepared points.
95///
96/// # Panics
97/// Panics if the internal `RwLock` is poisoned.
98pub fn invalidate_cache() {
99    *CACHE.write().unwrap() = None;
100}
101
102/// Get a shared reference to the prepared cache.
103///
104/// Returns `None` if cache has not been initialized.
105/// The returned `Arc` keeps the cache data alive even if the cache is replaced.
106///
107/// # Panics
108/// Panics if the internal `RwLock` is poisoned.
109///
110/// # Returns
111/// Arc-wrapped cache, or `None` if uninitialized.
112pub fn get_prepared_cache() -> Option<Arc<PreparedCache>> {
113    CACHE.read().unwrap().clone()
114}
115
116/// Check if cache is initialized.
117///
118/// # Panics
119/// Panics if the internal `RwLock` is poisoned.
120///
121/// # Returns
122/// `true` if cache has been initialized, `false` otherwise.
123pub fn is_cached() -> bool {
124    CACHE.read().unwrap().is_some()
125}