Skip to main content

poly_commitment/
precomputed_srs.rs

1//! Pre-generated SRS (Structured Reference String) for proofs.
2//!
3//! To prove and verify proofs you need a [Structured Reference
4//! String](https://www.cryptologie.net/article/560/zk-faq-whats-a-trusted-setup-whats-a-structured-reference-string-whats-toxic-waste/)
5//! (SRS).
6//! The generation of this SRS is quite expensive, so we provide a pre-generated
7//! SRS in this repo.
8//! Specifically, two of them, one for each pasta curve.
9//!
10//! We generate the SRS within the test in this module.
11//! If you modify the SRS, you will need to regenerate the SRS by passing the
12//! `SRS_OVERWRITE` env var.
13
14use crate::{hash_map_cache::HashMapCache, ipa::SRS, CommitmentCurve, PolyComm};
15use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
16use mina_curves::named::NamedCurve;
17use serde::{Deserialize, Serialize};
18use serde_with::serde_as;
19use std::{collections::HashMap, fs::File, io::BufReader, path::PathBuf};
20
21/// We store several different types of SRS objects. This enum parameterizes
22/// them.
23#[derive(Clone, Copy, PartialEq, Eq)]
24pub enum StoredSRSType {
25    Test,
26    Prod,
27}
28
29/// A clone of the SRS struct that is used for serialization, in a
30/// test-optimised way.
31///
32/// NB: Serialization of these fields is unchecked (and fast). If you
33/// want to make sure the data is checked on deserialization, this code
34/// must be changed; or you can check it externally.
35#[serde_as]
36#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
37#[serde(bound = "G: CanonicalDeserialize + CanonicalSerialize")]
38pub struct TestSRS<G> {
39    /// The vector of group elements for committing to polynomials in
40    /// coefficient form.
41    #[serde_as(as = "Vec<o1_utils::serialization::SerdeAsUnchecked>")]
42    pub g: Vec<G>,
43
44    /// A group element used for blinding commitments
45    #[serde_as(as = "o1_utils::serialization::SerdeAsUnchecked")]
46    pub h: G,
47
48    /// Commitments to Lagrange bases, per domain size
49    #[serde_as(as = "HashMap<_,Vec<PolyComm<o1_utils::serialization::SerdeAsUnchecked>>>")]
50    pub lagrange_bases: HashMap<usize, Vec<PolyComm<G>>>,
51}
52
53impl<G: Clone> From<SRS<G>> for TestSRS<G> {
54    fn from(value: SRS<G>) -> Self {
55        Self {
56            g: value.g,
57            h: value.h,
58            lagrange_bases: value.lagrange_bases.into(),
59        }
60    }
61}
62
63impl<G> From<TestSRS<G>> for SRS<G> {
64    fn from(value: TestSRS<G>) -> Self {
65        Self {
66            g: value.g,
67            h: value.h,
68            lagrange_bases: HashMapCache::new_from_hashmap(value.lagrange_bases),
69        }
70    }
71}
72
73/// The size of the SRS that we serialize.
74pub const SERIALIZED_SRS_SIZE: u32 = 16;
75
76/// The path of the serialized SRS.
77fn get_srs_path<G: NamedCurve>(srs_type: StoredSRSType) -> PathBuf {
78    let test_prefix: String = (match srs_type {
79        StoredSRSType::Test => "test_",
80        StoredSRSType::Prod => "",
81    })
82    .to_owned();
83    let base_path = env!("CARGO_MANIFEST_DIR");
84    PathBuf::from(base_path)
85        .join("../srs")
86        .join(test_prefix + &format!("{}.srs", G::NAME))
87}
88
89/// Generic SRS getter function.
90///
91/// # Panics
92///
93/// Panics if the SRS file does not exist on disk or cannot be
94/// deserialized.
95#[must_use]
96pub fn get_srs_generic<G>(srs_type: StoredSRSType) -> SRS<G>
97where
98    G: NamedCurve + CommitmentCurve,
99{
100    let srs_path = get_srs_path::<G>(srs_type);
101    let file = File::open(srs_path.clone())
102        .unwrap_or_else(|_| panic!("missing SRS file: {}", srs_path.display()));
103    let reader = BufReader::new(file);
104    match srs_type {
105        StoredSRSType::Test => {
106            let test_srs: TestSRS<G> = rmp_serde::from_read(reader).unwrap();
107            From::from(test_srs)
108        }
109        StoredSRSType::Prod => rmp_serde::from_read(reader).unwrap(),
110    }
111}
112
113/// Obtains an SRS for a specific curve from disk.
114///
115/// # Panics
116///
117/// Panics if the SRS does not exist on disk or cannot be deserialized.
118#[must_use]
119pub fn get_srs<G>() -> SRS<G>
120where
121    G: NamedCurve + CommitmentCurve,
122{
123    get_srs_generic(StoredSRSType::Prod)
124}
125
126/// Obtains a Test SRS for a specific curve from disk.
127///
128/// # Panics
129///
130/// Panics if the SRS does not exist on disk or cannot be deserialized.
131#[must_use]
132pub fn get_srs_test<G>() -> SRS<G>
133where
134    G: NamedCurve + CommitmentCurve,
135{
136    get_srs_generic(StoredSRSType::Test)
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    use crate::{hash_map_cache::HashMapCache, SRS as _};
144    use ark_ec::AffineRepr;
145    use ark_ff::PrimeField;
146    use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
147    use ark_serialize::Write;
148    use hex;
149    use mina_curves::pasta::{Pallas, Vesta};
150
151    fn test_regression_serialization_srs_with_generators<G: AffineRepr>(exp_output: &str) {
152        let h = G::generator();
153        let g = vec![h];
154        let lagrange_bases = HashMapCache::new();
155        let srs = SRS::<G> {
156            g,
157            h,
158            lagrange_bases,
159        };
160        let srs_bytes = rmp_serde::to_vec(&srs).unwrap();
161        let output = hex::encode(srs_bytes);
162        assert_eq!(output, exp_output);
163    }
164
165    #[test]
166    fn test_regression_serialization_srs_with_generators_vesta() {
167        // This is the same as Pallas as we encode the coordinate x only.
168        // Generated with commit 4c69a4defdb109b94f1124fe93283e728f1d8758
169        let exp_output = "9291c421010000000000000000000000000000000000000000000000000000000000000000c421010000000000000000000000000000000000000000000000000000000000000000";
170        test_regression_serialization_srs_with_generators::<Vesta>(exp_output);
171    }
172
173    #[test]
174    fn test_regression_serialization_srs_with_generators_pallas() {
175        // This is the same as Pallas as we encode the coordinate x only.
176        // Generated with commit 4c69a4defdb109b94f1124fe93283e728f1d8758
177        let exp_output = "9291c421010000000000000000000000000000000000000000000000000000000000000000c421010000000000000000000000000000000000000000000000000000000000000000";
178        test_regression_serialization_srs_with_generators::<Pallas>(exp_output);
179    }
180
181    fn create_or_check_srs<G>(log2_size: u32, srs_type: StoredSRSType)
182    where
183        G: NamedCurve + CommitmentCurve,
184        G::BaseField: PrimeField,
185    {
186        // generate SRS
187        let domain_size = 1 << log2_size;
188        let srs = SRS::<G>::create(domain_size);
189
190        // Test SRS objects have Lagrange bases precomputed
191        if srs_type == StoredSRSType::Test {
192            // FIXME we should iterate by powers of 2, instead we iterate over all the integers? why?
193            for sub_domain_size in 1..=domain_size {
194                let domain = Radix2EvaluationDomain::new(sub_domain_size).unwrap();
195                srs.get_lagrange_basis(domain);
196            }
197        }
198
199        // overwrite SRS if the env var is set
200        let srs_path = get_srs_path::<G>(srs_type);
201        if std::env::var("SRS_OVERWRITE").is_ok() {
202            let mut file = std::fs::OpenOptions::new()
203                .create(true)
204                .truncate(true)
205                .write(true)
206                .open(srs_path)
207                .expect("failed to open SRS file");
208
209            let srs_bytes = match srs_type {
210                StoredSRSType::Test => {
211                    let srs: TestSRS<G> = From::from(srs.clone());
212                    rmp_serde::to_vec(&srs).unwrap()
213                }
214                StoredSRSType::Prod => rmp_serde::to_vec(&srs).unwrap(),
215            };
216
217            file.write_all(&srs_bytes).expect("failed to write file");
218            file.flush().expect("failed to flush file");
219        }
220
221        // get SRS from disk
222        let srs_on_disk: SRS<G> = get_srs_generic::<G>(srs_type);
223
224        // check that it matches what we just generated
225        assert_eq!(srs, srs_on_disk);
226    }
227
228    /// Checks if `get_srs` (prod) succeeds for Pallas. Can be used for time-profiling.
229    #[test]
230    pub fn heavy_check_get_srs_prod_pallas() {
231        let _ = get_srs::<Pallas>();
232    }
233
234    /// Checks if `get_srs` (prod) succeeds for Vesta. Can be used for time-profiling.
235    #[test]
236    pub fn heavy_check_get_srs_prod_vesta() {
237        let _ = get_srs::<Vesta>();
238    }
239
240    /// Checks if `get_srs` (test) succeeds for Pallas. Can be used for time-profiling.
241    #[test]
242    pub fn check_get_srs_test_pallas() {
243        let _ = get_srs_test::<Pallas>();
244    }
245
246    /// Checks if `get_srs` (test) succeeds for Vesta. Can be used for time-profiling.
247    #[test]
248    pub fn check_get_srs_test_vesta() {
249        let _ = get_srs_test::<Vesta>();
250    }
251
252    /// This test checks that the two serialized SRS on disk are correct.
253    #[test]
254    pub fn heavy_test_srs_serialization() {
255        create_or_check_srs::<Vesta>(SERIALIZED_SRS_SIZE, StoredSRSType::Prod);
256        create_or_check_srs::<Pallas>(SERIALIZED_SRS_SIZE, StoredSRSType::Prod);
257        create_or_check_srs::<Vesta>(SERIALIZED_SRS_SIZE, StoredSRSType::Test);
258        create_or_check_srs::<Pallas>(SERIALIZED_SRS_SIZE, StoredSRSType::Test);
259    }
260}