Skip to main content

dory_pcs/
setup.rs

1//! Setup structures for Dory PCS
2//!
3//! The setup consists of:
4//! - Prover setup: generators and parameters needed for proving
5//! - Verifier setup: precomputed values for efficient verification
6
7use crate::primitives::arithmetic::{Group, PairingCurve};
8use crate::primitives::serialization::{DoryDeserialize, DorySerialize};
9
10#[cfg(all(feature = "disk-persistence", not(target_arch = "wasm32")))]
11use std::fs::{self, File};
12#[cfg(all(feature = "disk-persistence", not(target_arch = "wasm32")))]
13use std::io::{BufReader, BufWriter};
14#[cfg(all(feature = "disk-persistence", not(target_arch = "wasm32")))]
15use std::path::PathBuf;
16
17/// Prover setup parameters
18///
19/// Contains the generators and parameters needed to create proofs.
20/// The setup is transparent (no trusted setup) and can be generated
21/// from public randomness.
22///
23/// For square matrices: |Γ₁| = |Γ₂| = 2^((max_log_n+1)/2)
24#[derive(Clone, Debug, DorySerialize, DoryDeserialize)]
25pub struct ProverSetup<E: PairingCurve> {
26    /// Γ₁ - column generators in G1
27    pub g1_vec: Vec<E::G1>,
28
29    /// Γ₂ - row generators in G2
30    pub g2_vec: Vec<E::G2>,
31
32    /// h₁ - blinding generator in G1
33    pub h1: E::G1,
34
35    /// h₂ - blinding generator in G2
36    pub h2: E::G2,
37
38    /// h_t = e(h₁, h₂) - precomputed pairing
39    pub ht: E::GT,
40}
41
42/// Verifier setup parameters
43///
44/// Contains precomputed pairing values for efficient verification.
45/// Derived from the prover setup.
46#[derive(Clone, Debug, DorySerialize, DoryDeserialize)]
47pub struct VerifierSetup<E: PairingCurve> {
48    /// Δ₁L\[k\] = e(Γ₁\[..2^(k-1)\], Γ₂\[..2^(k-1)\])
49    pub delta_1l: Vec<E::GT>,
50
51    /// Δ₁R\[k\] = e(Γ₁\[2^(k-1)..2^k\], Γ₂\[..2^(k-1)\])
52    pub delta_1r: Vec<E::GT>,
53
54    /// Δ₂L\[k\] = same as Δ₁L\[k\]
55    pub delta_2l: Vec<E::GT>,
56
57    /// Δ₂R\[k\] = e(Γ₁\[..2^(k-1)\], Γ₂\[2^(k-1)..2^k\])
58    pub delta_2r: Vec<E::GT>,
59
60    /// χ\[k\] = e(Γ₁\[..2^k\], Γ₂\[..2^k\])
61    pub chi: Vec<E::GT>,
62
63    /// First G1 generator
64    pub g1_0: E::G1,
65
66    /// First G2 generator
67    pub g2_0: E::G2,
68
69    /// Blinding generator in G1
70    pub h1: E::G1,
71
72    /// Blinding generator in G2
73    pub h2: E::G2,
74
75    /// h_t = e(h₁, h₂)
76    pub ht: E::GT,
77
78    /// Maximum log₂ of polynomial size supported
79    pub max_log_n: usize,
80}
81
82impl<E: PairingCurve> ProverSetup<E> {
83    /// Generate new prover setup with transparent randomness
84    ///
85    /// For square matrices, generates n = 2^((max_log_n+1)/2) generators for both G1 and G2,
86    /// supporting polynomials up to 2^max_log_n coefficients arranged as n×n matrices.
87    ///
88    /// # Parameters
89    /// - `max_log_n`: Maximum log₂ of polynomial size (for n×n matrix with n² = 2^max_log_n)
90    ///
91    /// # Returns
92    /// A new `ProverSetup` with randomly generated parameters
93    pub fn new(max_log_n: usize) -> Self {
94        // For square matrices: n = 2^((max_log_n+1)/2)
95        let n = 1 << max_log_n.div_ceil(2);
96        // Generate n random G1 generators (Γ₁)
97        let g1_vec: Vec<E::G1> = (0..n).map(|_| E::G1::random()).collect();
98        // Generate n random G2 generators (Γ₂)
99        let g2_vec: Vec<E::G2> = (0..n).map(|_| E::G2::random()).collect();
100
101        // Generate blinding generators
102        let h1 = E::G1::random();
103        let h2 = E::G2::random();
104
105        // Precompute e(h₁, h₂)
106        let ht = E::pair(&h1, &h2);
107
108        Self {
109            g1_vec,
110            g2_vec,
111            h1,
112            h2,
113            ht,
114        }
115    }
116
117    /// Derive verifier setup from prover setup
118    ///
119    /// Precomputes pairing values for efficient verification by computing
120    /// delta and chi values for all rounds of the inner product protocol.
121    pub fn to_verifier_setup(&self) -> VerifierSetup<E> {
122        let max_num_rounds = self.g1_vec.len().trailing_zeros() as usize;
123
124        let mut delta_1l = Vec::with_capacity(max_num_rounds + 1);
125        let mut delta_1r = Vec::with_capacity(max_num_rounds + 1);
126        let mut delta_2r = Vec::with_capacity(max_num_rounds + 1);
127        let mut chi = Vec::with_capacity(max_num_rounds + 1);
128
129        for k in 0..=max_num_rounds {
130            if k == 0 {
131                // Base case: identities for deltas, single pairing for chi
132                delta_1l.push(E::GT::identity());
133                delta_1r.push(E::GT::identity());
134                delta_2r.push(E::GT::identity());
135                chi.push(E::pair(&self.g1_vec[0], &self.g2_vec[0]));
136            } else {
137                let half_len = 1 << (k - 1);
138                let full_len = 1 << k;
139
140                let g1_first_half = &self.g1_vec[..half_len];
141                let g1_second_half = &self.g1_vec[half_len..full_len];
142                let g2_first_half = &self.g2_vec[..half_len];
143                let g2_second_half = &self.g2_vec[half_len..full_len];
144
145                // Δ₁L[k] = χ[k-1] (reuse previous chi)
146                delta_1l.push(chi[k - 1]);
147
148                // Δ₁R[k] = e(Γ₁[2^(k-1)..2^k], Γ₂[..2^(k-1)])
149                delta_1r.push(E::multi_pair(g1_second_half, g2_first_half));
150
151                // Δ₂R[k] = e(Γ₁[..2^(k-1)], Γ₂[2^(k-1)..2^k])
152                delta_2r.push(E::multi_pair(g1_first_half, g2_second_half));
153
154                // χ[k] = χ[k-1] + e(Γ₁[2^(k-1)..2^k], Γ₂[2^(k-1)..2^k]) (incremental)
155                chi.push(chi[k - 1].add(&E::multi_pair(g1_second_half, g2_second_half)));
156            }
157        }
158
159        VerifierSetup {
160            delta_1l: delta_1l.clone(),
161            delta_1r,
162            delta_2l: delta_1l, // Δ₂L = Δ₁L
163            delta_2r,
164            chi,
165            g1_0: self.g1_vec[0],
166            g2_0: self.g2_vec[0],
167            h1: self.h1,
168            h2: self.h2,
169            ht: self.ht,
170            max_log_n: max_num_rounds * 2, // Since square matrices: max_log_n = 2 * max_nu
171        }
172    }
173
174    /// Returns the maximum nu (log column dimension) supported by this setup
175    #[inline]
176    pub fn max_nu(&self) -> usize {
177        self.g1_vec.len().trailing_zeros() as usize
178    }
179
180    /// Returns the maximum sigma (log row dimension) supported by this setup
181    ///
182    /// For square matrices, this always equals max_nu()
183    #[inline]
184    pub fn max_sigma(&self) -> usize {
185        self.max_nu()
186    }
187
188    /// Returns the maximum log₂ of polynomial size supported
189    ///
190    /// For n×n matrices: max_log_n = 2 * max_nu
191    #[inline]
192    pub fn max_log_n(&self) -> usize {
193        self.max_nu() * 2
194    }
195}
196
197/// Get the full path to the setup file for a given max_log_n
198///
199/// Determines the appropriate storage directory based on the OS:
200/// - Linux: `~/.cache/dory/`
201/// - macOS: `~/Library/Caches/dory/`
202/// - Windows: `{FOLDERID_LocalAppData}\dory\`
203///
204/// Note: Detects OS at runtime by checking environment variables then chooses XDG cache directory for persistent storage.
205#[cfg(all(feature = "disk-persistence", not(target_arch = "wasm32")))]
206fn get_storage_path(max_log_n: usize) -> Option<PathBuf> {
207    let cache_directory = {
208        // Check for Windows first (LOCALAPPDATA is Windows-specific)
209        if let Ok(local_app_data) = std::env::var("LOCALAPPDATA") {
210            Some(PathBuf::from(local_app_data))
211        } else if let Ok(home) = std::env::var("HOME") {
212            let mut path = PathBuf::from(&home);
213
214            // Check if Library/Caches exists (macOS indicator)
215            let macos_cache = {
216                let mut test_path = PathBuf::from(&home);
217                test_path.push("Library");
218                test_path.push("Caches");
219                test_path.exists()
220            };
221
222            if macos_cache {
223                path.push("Library");
224                path.push("Caches");
225            } else {
226                // Linux and other Unix-like systems
227                path.push(".cache");
228            }
229            Some(path)
230        } else {
231            None
232        }
233    };
234
235    cache_directory.map(|mut path| {
236        path.push("dory");
237        path.push(format!("dory_{max_log_n}.urs"));
238        path
239    })
240}
241
242/// Save prover and verifier setups to disk
243///
244/// Serializes both setups to a `.urs` file in the storage directory.
245/// If the storage directory doesn't exist, it will be created.
246///
247/// # Panics
248/// Panics if:
249/// - Storage directory cannot be determined
250/// - Directory creation fails
251/// - File creation fails
252/// - Serialization of prover or verifier setup fails
253#[cfg(all(feature = "disk-persistence", not(target_arch = "wasm32")))]
254pub fn save_setup<E: PairingCurve>(
255    prover: &ProverSetup<E>,
256    verifier: &VerifierSetup<E>,
257    max_log_n: usize,
258) where
259    ProverSetup<E>: DorySerialize,
260    VerifierSetup<E>: DorySerialize,
261{
262    let storage_path = get_storage_path(max_log_n).expect("Failed to determine storage directory");
263
264    if let Some(parent) = storage_path.parent() {
265        fs::create_dir_all(parent)
266            .unwrap_or_else(|e| panic!("Failed to create storage directory: {e}"));
267    }
268
269    tracing::info!("Saving setup to {}", storage_path.display());
270
271    let file =
272        File::create(&storage_path).unwrap_or_else(|e| panic!("Failed to create setup file: {e}"));
273
274    let mut writer = BufWriter::new(file);
275
276    DorySerialize::serialize_compressed(prover, &mut writer)
277        .unwrap_or_else(|e| panic!("Failed to serialize prover setup: {e}"));
278
279    DorySerialize::serialize_compressed(verifier, &mut writer)
280        .unwrap_or_else(|e| panic!("Failed to serialize verifier setup: {e}"));
281
282    tracing::info!("Successfully saved setup to disk");
283}
284
285/// Load prover and verifier setups from disk
286///
287/// Attempts to deserialize both setups from the saved `.urs` file.
288///
289/// # Errors
290/// Returns `DoryError::InvalidURS` if:
291/// - Storage directory cannot be determined
292/// - Setup file doesn't exist
293/// - File cannot be opened
294/// - Deserialization fails
295#[cfg(all(feature = "disk-persistence", not(target_arch = "wasm32")))]
296pub fn load_setup<E: PairingCurve>(
297    max_log_n: usize,
298) -> Result<(ProverSetup<E>, VerifierSetup<E>), crate::DoryError>
299where
300    ProverSetup<E>: DoryDeserialize,
301    VerifierSetup<E>: DoryDeserialize,
302{
303    let storage_path = get_storage_path(max_log_n).ok_or_else(|| {
304        crate::DoryError::InvalidURS("Failed to determine storage directory".to_string())
305    })?;
306
307    if !storage_path.exists() {
308        return Err(crate::DoryError::InvalidURS(format!(
309            "Setup file not found at {}",
310            storage_path.display()
311        )));
312    }
313
314    tracing::info!("Looking for saved setup at {}", storage_path.display());
315
316    let file = File::open(&storage_path)
317        .map_err(|e| crate::DoryError::InvalidURS(format!("Failed to open setup file: {e}")))?;
318
319    let mut reader = BufReader::new(file);
320
321    let prover = DoryDeserialize::deserialize_compressed(&mut reader).map_err(|e| {
322        crate::DoryError::InvalidURS(format!("Failed to deserialize prover setup: {e}"))
323    })?;
324
325    let verifier = DoryDeserialize::deserialize_compressed(&mut reader).map_err(|e| {
326        crate::DoryError::InvalidURS(format!("Failed to deserialize verifier setup: {e}"))
327    })?;
328
329    tracing::info!("Loaded setup for max_log_n={}", max_log_n);
330
331    Ok((prover, verifier))
332}