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