1use std::collections::{BTreeMap, HashSet};
2use std::fs::{create_dir_all, File, OpenOptions};
3use std::io::{self, Read, Seek, SeekFrom, Write};
4use std::path::{Path, PathBuf};
5use std::sync::Mutex;
6use std::time::Instant;
7
8use anyhow::bail;
9use bellperson::{groth16, Circuit};
10use blake2b_simd::Params as Blake2bParams;
11use blstrs::{Bls12, Scalar as Fr};
12use fs2::FileExt;
13use itertools::Itertools;
14use lazy_static::lazy_static;
15use log::info;
16use memmap2::MmapOptions;
17use rand::RngCore;
18use serde::{Deserialize, Serialize};
19use sha2::{Digest, Sha256};
20
21use crate::{
22 error::{Error, Result},
23 settings::SETTINGS,
24};
25
26pub const VERSION: usize = 28;
28pub const SRS_MAX_PROOFS_TO_AGGREGATE: usize = 65536; pub const GROTH_PARAMETER_EXT: &str = "params";
31pub const PARAMETER_METADATA_EXT: &str = "meta";
32pub const VERIFYING_KEY_EXT: &str = "vk";
33pub const SRS_KEY_EXT: &str = "srs";
34pub const SRS_SHARED_KEY_NAME: &str = "fil-inner-product-v1";
35
36#[derive(Debug)]
37pub struct LockedFile(File);
38
39pub type ParameterMap = BTreeMap<String, ParameterData>;
40#[cfg(not(feature = "cuda-supraseal"))]
41pub type Bls12GrothParams = groth16::MappedParameters<Bls12>;
42#[cfg(feature = "cuda-supraseal")]
43pub type Bls12GrothParams = groth16::SuprasealParameters<Bls12>;
44
45#[derive(Debug, Deserialize, Serialize)]
46pub struct ParameterData {
47 pub cid: String,
48 pub digest: String,
49 pub sector_size: u64,
50}
51
52pub const PARAMETERS_DATA: &str = include_str!("../parameters.json");
53pub const SRS_PARAMETERS_DATA: &str = include_str!("../srs-inner-product.json");
54
55lazy_static! {
56 pub static ref PARAMETERS: ParameterMap =
57 serde_json::from_str(PARAMETERS_DATA).expect("Invalid parameters.json");
58 pub static ref SRS_PARAMETERS: ParameterMap =
59 serde_json::from_str(SRS_PARAMETERS_DATA).expect("Invalid srs-inner-product.json");
60 static ref VERIFIED_PARAMETERS: Mutex<HashSet<String>> = Mutex::new(HashSet::new());
63}
64
65pub fn parameter_id(cache_id: &str) -> String {
66 format!("v{}-{}.params", VERSION, cache_id)
67}
68
69pub fn verifying_key_id(cache_id: &str) -> String {
70 format!("v{}-{}.vk", VERSION, cache_id)
71}
72
73pub fn metadata_id(cache_id: &str) -> String {
74 format!("v{}-{}.meta", VERSION, cache_id)
75}
76
77pub fn get_parameter_data_from_id(parameter_id: &str) -> Option<&ParameterData> {
79 PARAMETERS.get(parameter_id)
80}
81
82pub fn get_srs_parameter_data_from_id(parameter_id: &str) -> Option<&ParameterData> {
84 SRS_PARAMETERS.get(parameter_id)
85}
86
87pub fn get_parameter_data(cache_id: &str) -> Option<&ParameterData> {
89 PARAMETERS.get(¶meter_id(cache_id))
90}
91
92pub fn get_verifying_key_data(cache_id: &str) -> Option<&ParameterData> {
94 PARAMETERS.get(&verifying_key_id(cache_id))
95}
96
97impl LockedFile {
100 pub fn open_exclusive_read<P: AsRef<Path>>(p: P) -> io::Result<Self> {
101 let f = OpenOptions::new().read(true).create(false).open(p)?;
102 f.lock_exclusive()?;
103
104 Ok(LockedFile(f))
105 }
106
107 pub fn open_exclusive<P: AsRef<Path>>(p: P) -> io::Result<Self> {
108 let f = OpenOptions::new()
109 .read(true)
110 .write(true)
111 .create_new(true)
112 .open(p)?;
113 f.lock_exclusive()?;
114
115 Ok(LockedFile(f))
116 }
117
118 pub fn open_shared_read<P: AsRef<Path>>(p: P) -> io::Result<Self> {
119 let f = OpenOptions::new().read(true).create(false).open(p)?;
120 f.lock_shared()?;
121
122 Ok(LockedFile(f))
123 }
124}
125
126impl AsRef<File> for LockedFile {
127 fn as_ref(&self) -> &File {
128 &self.0
129 }
130}
131
132impl Write for LockedFile {
133 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
134 self.0.write(buf)
135 }
136
137 fn flush(&mut self) -> io::Result<()> {
138 self.0.flush()
139 }
140}
141
142impl Read for LockedFile {
143 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
144 self.0.read(buf)
145 }
146}
147
148impl Seek for LockedFile {
149 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
150 self.0.seek(pos)
151 }
152}
153
154impl Drop for LockedFile {
155 fn drop(&mut self) {
156 self.0
157 .unlock()
158 .unwrap_or_else(|e| panic!("{}: failed to {:?} unlock file safely", e, &self.0));
159 }
160}
161
162pub fn parameter_cache_dir_name() -> String {
163 SETTINGS.parameter_cache.clone()
164}
165
166pub fn parameter_cache_dir() -> PathBuf {
167 Path::new(¶meter_cache_dir_name()).to_path_buf()
168}
169
170pub fn parameter_cache_params_path(parameter_set_identifier: &str) -> PathBuf {
171 let dir = Path::new(¶meter_cache_dir_name()).to_path_buf();
172 dir.join(format!(
173 "v{}-{}.{}",
174 VERSION, parameter_set_identifier, GROTH_PARAMETER_EXT
175 ))
176}
177
178pub fn parameter_cache_metadata_path(parameter_set_identifier: &str) -> PathBuf {
179 let dir = Path::new(¶meter_cache_dir_name()).to_path_buf();
180 dir.join(format!(
181 "v{}-{}.{}",
182 VERSION, parameter_set_identifier, PARAMETER_METADATA_EXT
183 ))
184}
185
186pub fn parameter_cache_verifying_key_path(parameter_set_identifier: &str) -> PathBuf {
187 let dir = Path::new(¶meter_cache_dir_name()).to_path_buf();
188 dir.join(format!(
189 "v{}-{}.{}",
190 VERSION, parameter_set_identifier, VERIFYING_KEY_EXT
191 ))
192}
193
194pub fn parameter_cache_srs_key_path(
195 _parameter_set_identifier: &str,
196 _num_proofs_to_aggregate: usize,
197) -> PathBuf {
198 let dir = Path::new(¶meter_cache_dir_name()).to_path_buf();
199 dir.join(format!(
200 "v{}-{}.{}",
201 VERSION, SRS_SHARED_KEY_NAME, SRS_KEY_EXT
202 ))
203}
204
205fn ensure_ancestor_dirs_exist(cache_entry_path: PathBuf) -> Result<PathBuf> {
206 info!(
207 "ensuring that all ancestor directories for: {:?} exist",
208 cache_entry_path
209 );
210
211 if let Some(parent_dir) = cache_entry_path.parent() {
212 if let Err(err) = create_dir_all(parent_dir) {
213 match err.kind() {
214 io::ErrorKind::AlreadyExists => {}
215 _ => return Err(From::from(err)),
216 }
217 }
218 } else {
219 bail!("{:?} has no parent directory", cache_entry_path);
220 }
221
222 Ok(cache_entry_path)
223}
224
225pub trait ParameterSetMetadata {
226 fn identifier(&self) -> String;
227 fn sector_size(&self) -> u64;
228}
229
230#[derive(Clone, Debug, Serialize, Deserialize)]
231pub struct CacheEntryMetadata {
232 pub sector_size: u64,
233}
234
235pub trait CacheableParameters<C, P>
236where
237 C: Circuit<Fr>,
238 P: ParameterSetMetadata,
239{
240 fn cache_prefix() -> String;
241
242 fn cache_meta(pub_params: &P) -> CacheEntryMetadata {
243 CacheEntryMetadata {
244 sector_size: pub_params.sector_size(),
245 }
246 }
247
248 fn cache_identifier(pub_params: &P) -> String {
249 let param_identifier = pub_params.identifier();
250 info!("parameter set identifier for cache: {}", param_identifier);
251 let mut hasher = Sha256::default();
252 hasher.update(param_identifier.into_bytes());
253 let circuit_hash = hasher.finalize();
254 format!(
255 "{}-{:02x}",
256 Self::cache_prefix(),
257 circuit_hash.iter().format("")
258 )
259 }
260
261 fn get_param_metadata(_circuit: C, pub_params: &P) -> Result<CacheEntryMetadata> {
262 let id = Self::cache_identifier(pub_params);
263
264 let meta_path = ensure_ancestor_dirs_exist(parameter_cache_metadata_path(&id))?;
266 read_cached_metadata(&meta_path)
267 .or_else(|_| write_cached_metadata(&meta_path, Self::cache_meta(pub_params)))
268 .map_err(Into::into)
269 }
270
271 fn get_groth_params<R: RngCore>(
277 rng: Option<&mut R>,
278 circuit: C,
279 pub_params: &P,
280 ) -> Result<Bls12GrothParams> {
281 let id = Self::cache_identifier(pub_params);
282 let cache_path = ensure_ancestor_dirs_exist(parameter_cache_params_path(&id))?;
283
284 let generate = || -> Result<_> {
285 if let Some(rng) = rng {
286 info!("Actually generating groth params. (id: {})", &id);
287 let start = Instant::now();
288 let parameters = groth16::generate_random_parameters::<Bls12, _, _>(circuit, rng)?;
289 let generation_time = start.elapsed();
290 info!(
291 "groth_parameter_generation_time: {:?} (id: {})",
292 generation_time, &id
293 );
294 Ok(parameters)
295 } else {
296 bail!(
297 "No cached parameters found for {} [failure finding {}]",
298 id,
299 cache_path.display()
300 );
301 }
302 };
303
304 read_cached_params(&cache_path).or_else(|err| match err.downcast::<Error>() {
306 Ok(error @ Error::InvalidParameters(_)) => Err(error.into()),
307 _ => {
308 if !cache_path.exists() {
310 match write_cached_params(&cache_path, generate()?) {
311 Ok(_) => {}
312 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
313 }
315 Err(e) => panic!("{}: failed to write generated parameters to cache", e),
316 }
317 }
318 Ok(read_cached_params(&cache_path)?)
319 }
320 })
321 }
322
323 fn get_inner_product<R: RngCore>(
329 rng: Option<&mut R>,
330 _circuit: C,
331 pub_params: &P,
332 num_proofs_to_aggregate: usize,
333 ) -> Result<groth16::aggregate::GenericSRS<Bls12>> {
334 let id = Self::cache_identifier(pub_params);
335 let cache_path =
336 ensure_ancestor_dirs_exist(parameter_cache_srs_key_path(&id, num_proofs_to_aggregate))?;
337
338 let generate = || -> Result<groth16::aggregate::GenericSRS<Bls12>> {
339 if let Some(rng) = rng {
340 info!(
341 "get_inner_product called with {} [max {}] proofs to aggregate",
342 num_proofs_to_aggregate, SRS_MAX_PROOFS_TO_AGGREGATE
343 );
344 Ok(groth16::aggregate::setup_fake_srs(
345 rng,
346 num_proofs_to_aggregate,
347 ))
348 } else {
349 bail!(
350 "No cached srs key found for {} [failure finding {}]",
351 id,
352 cache_path.display()
353 );
354 }
355 };
356
357 match read_cached_srs_key(&cache_path) {
359 Ok(key) => Ok(key),
360 Err(_) => write_cached_srs_key(&cache_path, generate()?).map_err(Into::into),
361 }
362 }
363
364 fn get_verifying_key<R: RngCore>(
370 #[allow(unused_variables)] rng: Option<&mut R>,
371 #[allow(unused_variables)] circuit: C,
372 pub_params: &P,
373 ) -> Result<groth16::VerifyingKey<Bls12>> {
374 let id = Self::cache_identifier(pub_params);
375
376 #[cfg(not(feature = "cuda-supraseal"))]
377 let generate = || -> Result<groth16::VerifyingKey<Bls12>> {
378 let groth_params = Self::get_groth_params(rng, circuit, pub_params)?;
379 info!("Getting verifying key. (id: {})", &id);
380 Ok(groth_params.vk)
381 };
382 #[cfg(feature = "cuda-supraseal")]
383 let generate = || -> Result<groth16::VerifyingKey<Bls12>> {
384 Err(anyhow::anyhow!("Cannot find parameters file. For SupraSeal it is expected that the parameter files already exist and don't need to be generated."))
385 };
386
387 let cache_path = ensure_ancestor_dirs_exist(parameter_cache_verifying_key_path(&id))?;
389 match read_cached_verifying_key(&cache_path) {
390 Ok(key) => Ok(key),
391 Err(_) => write_cached_verifying_key(&cache_path, generate()?).map_err(Into::into),
392 }
393 }
394}
395
396fn ensure_parent(path: &Path) -> io::Result<()> {
397 match path.parent() {
398 Some(dir) => {
399 create_dir_all(dir)?;
400 Ok(())
401 }
402 None => Ok(()),
403 }
404}
405
406type GetParameterDataCallback = fn(&str) -> Option<&ParameterData>;
407
408pub fn verify_production_entry(
412 cache_entry_path: &Path,
413 cache_key: String,
414 selector: GetParameterDataCallback,
415) -> Result<bool> {
416 match selector(&cache_key) {
417 Some(data) => {
418 let not_yet_verified = VERIFIED_PARAMETERS
420 .lock()
421 .expect("verified parameters lock failed")
422 .get(&cache_key)
423 .is_none();
424 if not_yet_verified {
425 info!("generating consistency digest for parameters");
426 let hash =
427 with_exclusive_read_lock::<_, io::Error, _>(cache_entry_path, |mut file| {
428 let mut hasher = Blake2bParams::new().to_state();
429 io::copy(&mut file, &mut hasher).expect("copying file into hasher failed");
430 Ok(hasher.finalize())
431 })?;
432 info!("generated consistency digest for parameters");
433
434 let digest_hex = &hash.to_hex()[..32];
436 if digest_hex != data.digest {
437 info!("parameter data is INVALID [{}]", digest_hex);
438 return Err(
439 Error::InvalidParameters(cache_entry_path.display().to_string()).into(),
440 );
441 }
442
443 info!("parameter data is VALID [{}]", digest_hex);
444 VERIFIED_PARAMETERS
445 .lock()
446 .expect("verified parameters lock failed")
447 .insert(cache_key);
448 }
449 }
450 None => {
451 return Err(Error::InvalidParameters(cache_entry_path.display().to_string()).into());
452 }
453 }
454
455 Ok(true)
456}
457
458pub fn read_cached_params(cache_entry_path: &Path) -> Result<Bls12GrothParams> {
460 info!("checking cache_path: {:?} for parameters", cache_entry_path);
461
462 let verify_production_params = SETTINGS.verify_production_params;
463 info!(
464 "Verify production parameters is {}",
465 verify_production_params
466 );
467
468 if verify_production_params {
474 let cache_key = cache_entry_path
475 .file_name()
476 .expect("failed to get cached parameter filename")
477 .to_str()
478 .expect("failed to convert to str")
479 .to_string();
480
481 let selector: GetParameterDataCallback = get_parameter_data_from_id;
482 verify_production_entry(cache_entry_path, cache_key, selector)?;
483 }
484
485 read_cached_params_inner(cache_entry_path).map_err(Into::into)
486}
487
488#[cfg(not(feature = "cuda-supraseal"))]
489fn read_cached_params_inner(
490 cache_entry_path: &Path,
491) -> std::result::Result<groth16::MappedParameters<Bls12>, io::Error> {
492 with_exclusive_read_lock(cache_entry_path, |_file| {
493 let mapped_params =
494 groth16::Parameters::build_mapped_parameters(cache_entry_path.to_path_buf(), false);
495 info!("read parameters from cache {:?} ", cache_entry_path);
496 mapped_params
497 })
498}
499
500#[cfg(feature = "cuda-supraseal")]
501fn read_cached_params_inner(
502 cache_entry_path: &Path,
503) -> std::result::Result<groth16::SuprasealParameters<Bls12>, io::Error> {
504 let supraseal_params = Bls12GrothParams::new(cache_entry_path.to_path_buf());
505 info!(
506 "read parameters into SuprasSeal from cache {:?} ",
507 cache_entry_path
508 );
509 supraseal_params
510}
511
512fn read_cached_verifying_key(cache_entry_path: &Path) -> Result<groth16::VerifyingKey<Bls12>> {
513 info!(
514 "checking cache_path: {:?} for verifying key",
515 cache_entry_path
516 );
517
518 let verify_production_params = SETTINGS.verify_production_params;
519 info!(
520 "Verify production parameters is {}",
521 verify_production_params
522 );
523
524 if verify_production_params {
530 let cache_key = cache_entry_path
531 .file_name()
532 .expect("failed to get cached verifying key filename")
533 .to_str()
534 .expect("failed to convert to str")
535 .to_string();
536
537 let selector: GetParameterDataCallback = get_parameter_data_from_id;
538 verify_production_entry(cache_entry_path, cache_key, selector)?;
539 }
540
541 with_exclusive_read_lock(cache_entry_path, |file| {
542 let key = groth16::VerifyingKey::read(file)?;
543 info!("read verifying key from cache {:?} ", cache_entry_path);
544
545 Ok(key)
546 })
547}
548
549fn read_cached_srs_key(cache_entry_path: &Path) -> Result<groth16::aggregate::GenericSRS<Bls12>> {
550 info!("checking cache_path: {:?} for srs", cache_entry_path);
551
552 let verify_production_params = SETTINGS.verify_production_params;
553 info!(
554 "Verify production parameters is {}",
555 verify_production_params
556 );
557
558 if verify_production_params {
564 let cache_key = cache_entry_path
565 .file_name()
566 .expect("failed to get cached srs filename")
567 .to_str()
568 .expect("failed to convert to str")
569 .to_string();
570
571 let selector: GetParameterDataCallback = get_srs_parameter_data_from_id;
572 verify_production_entry(cache_entry_path, cache_key, selector)?;
573 }
574
575 with_exclusive_read_lock(cache_entry_path, |file| {
576 let srs_map = unsafe { MmapOptions::new().map(file.as_ref())? };
577 let max_len = (2 << 14) + 1;
582 let key = groth16::aggregate::GenericSRS::read_mmap(&srs_map, max_len)?;
583 info!("read srs key from cache {:?} ", cache_entry_path);
584
585 Ok(key)
586 })
587}
588
589fn read_cached_metadata(cache_entry_path: &Path) -> io::Result<CacheEntryMetadata> {
590 info!("checking cache_path: {:?} for metadata", cache_entry_path);
591 with_exclusive_read_lock(cache_entry_path, |file| {
592 let value = serde_json::from_reader(file)?;
593 info!("read metadata from cache {:?} ", cache_entry_path);
594
595 Ok(value)
596 })
597}
598
599fn write_cached_metadata(
600 cache_entry_path: &Path,
601 value: CacheEntryMetadata,
602) -> io::Result<CacheEntryMetadata> {
603 with_exclusive_lock(cache_entry_path, |file| {
604 serde_json::to_writer(file, &value)?;
605 info!("wrote metadata to cache {:?} ", cache_entry_path);
606
607 Ok(value)
608 })
609}
610
611fn write_cached_verifying_key(
612 cache_entry_path: &Path,
613 value: groth16::VerifyingKey<Bls12>,
614) -> io::Result<groth16::VerifyingKey<Bls12>> {
615 with_exclusive_lock(cache_entry_path, |mut file| {
616 value.write(&mut file)?;
617 file.flush()?;
618 info!("wrote verifying key to cache {:?} ", cache_entry_path);
619
620 Ok(value)
621 })
622}
623
624fn write_cached_srs_key(
625 cache_entry_path: &Path,
626 value: groth16::aggregate::GenericSRS<Bls12>,
627) -> io::Result<groth16::aggregate::GenericSRS<Bls12>> {
628 with_exclusive_lock(cache_entry_path, |mut file| {
629 value.write(&mut file)?;
630 file.flush()?;
631 info!("wrote srs key to cache {:?} ", cache_entry_path);
632
633 Ok(value)
634 })
635}
636
637fn write_cached_params(
638 cache_entry_path: &Path,
639 value: groth16::Parameters<Bls12>,
640) -> io::Result<groth16::Parameters<Bls12>> {
641 with_exclusive_lock(cache_entry_path, |mut file| {
642 value.write(&mut file)?;
643 file.flush()?;
644 info!("wrote groth parameters to cache {:?} ", cache_entry_path);
645
646 Ok(value)
647 })
648}
649
650pub fn with_exclusive_lock<T, E, F>(file_path: &Path, f: F) -> std::result::Result<T, E>
651where
652 F: FnOnce(&mut LockedFile) -> std::result::Result<T, E>,
653 E: From<io::Error>,
654{
655 with_open_file(file_path, LockedFile::open_exclusive, f)
656}
657
658pub fn with_exclusive_read_lock<T, E, F>(file_path: &Path, f: F) -> std::result::Result<T, E>
659where
660 F: FnOnce(&mut LockedFile) -> std::result::Result<T, E>,
661 E: From<io::Error>,
662{
663 with_open_file(file_path, LockedFile::open_exclusive_read, f)
664}
665
666pub fn with_open_file<'a, T, E, F, G>(
667 file_path: &'a Path,
668 open_file: G,
669 f: F,
670) -> std::result::Result<T, E>
671where
672 F: FnOnce(&mut LockedFile) -> std::result::Result<T, E>,
673 G: FnOnce(&'a Path) -> io::Result<LockedFile>,
674 E: From<io::Error>,
675{
676 ensure_parent(file_path)?;
677 f(&mut open_file(file_path)?)
678}