From 066b8697544defe45116cb79fe745422d18147c7 Mon Sep 17 00:00:00 2001 From: Raphael Toledo Date: Fri, 13 Dec 2024 12:54:34 +0000 Subject: [PATCH] CentralizedTelescope: Adding doc --- src/centralized_telescope/cases.rs | 204 ++++++++++++++++++++++ src/centralized_telescope/mod.rs | 18 +- src/centralized_telescope/params.rs | 81 ++++++++- src/centralized_telescope/proof.rs | 230 ++++++++++++++++++++++++- src/centralized_telescope/round.rs | 2 +- src/centralized_telescope/telescope.rs | 126 ++++++++++++++ 6 files changed, 636 insertions(+), 25 deletions(-) create mode 100644 src/centralized_telescope/cases.rs create mode 100644 src/centralized_telescope/telescope.rs diff --git a/src/centralized_telescope/cases.rs b/src/centralized_telescope/cases.rs new file mode 100644 index 00000000..50d7337e --- /dev/null +++ b/src/centralized_telescope/cases.rs @@ -0,0 +1,204 @@ +//! Centralized Telescope's helper structures, traits and functions for +//! generating parameters + +use super::params::Params; +use std::f64::consts::LOG2_E; + +pub(super) enum Cases { + Small, + Mid, + High, +} + +impl Cases { + /// Returns which parameters' case the user parameters correspond to + pub(super) fn which(completeness_param: f64, set_size: u64, proof_size: u64) -> Cases { + let set_size_f64 = set_size as f64; + let proof_size_f64 = proof_size as f64; + let ratio = 9.0 * set_size_f64 * LOG2_E / ((17.0 * proof_size_f64).powi(2)); + let s1 = ratio - 7.0; + let s2 = ratio - 2.0; + + if s1 < 1.0 || s2 < 1.0 { + // Small case, i.e. set_size <= λ^2 + Cases::Small + } else { + let completeness_param2 = completeness_param.min(s2); + if proof_size_f64 < completeness_param2 { + // Case 3, Theorem 14, i.e. set_size >= λ^3 + Cases::High + } else { + // Case 2, Theorem 13, i.e. λ^2 < set_size < λ^3 + Cases::Mid + } + } + } +} + +/// Trait to compute internal parameters depending on which case we are +pub(super) trait Case { + /// Trait constructor + fn new(completeness_param: f64, set_size: u64, proof_size: f64) -> Self; + + /// Returns the maximum number of retries + fn max_retries(&self) -> u64; + + /// Returns the search width + fn search_width(&self, proof_size: f64) -> u64; + + /// Returns the valid proof probability + fn valid_proof_probability(&self, search_width: u64) -> f64; + + /// Returns the DFS bound + fn dfs_bound(&self, proof_size: f64, search_width: u64) -> u64; + + /// Returns Params + fn create_params(&self, proof_size: f64) -> Params { + let search_width = self.search_width(proof_size); + Params { + proof_size: proof_size as u64, + max_retries: self.max_retries(), + search_width, + valid_proof_probability: self.valid_proof_probability(search_width), + dfs_bound: self.dfs_bound(proof_size, search_width), + } + } +} + +pub(super) struct Small { + completeness_param: f64, +} + +pub(super) struct Mid { + completeness_param: f64, + completeness_param1: f64, + set_size: u64, +} + +pub(super) struct High { + completeness_param: f64, + completeness_param2: f64, +} + +impl Case for Small { + fn new(completeness_param: f64, _set_size: u64, _proof_size: f64) -> Self { + Self { completeness_param } + } + + fn max_retries(&self) -> u64 { + self.completeness_param as u64 + } + + fn search_width(&self, proof_size: f64) -> u64 { + (32.0 * (12f64).ln() * proof_size).ceil() as u64 + } + + fn valid_proof_probability(&self, search_width: u64) -> f64 { + 2.0 * 12f64.ln() / search_width as f64 + } + + fn dfs_bound(&self, proof_size: f64, search_width: u64) -> u64 { + (8.0 * (proof_size + 1.0) * search_width as f64 / (12f64).ln()).ceil() as u64 + } +} + +impl Case for High { + fn new(completeness_param: f64, set_size: u64, proof_size: f64) -> Self { + let ratio = 9.0 * set_size as f64 * LOG2_E / ((17.0 * proof_size).powi(2)); + let s2 = ratio - 2.0; + let completeness_param2 = completeness_param.min(s2); + + Self { + completeness_param, + completeness_param2, + } + } + + fn max_retries(&self) -> u64 { + (self.completeness_param / self.completeness_param2).ceil() as u64 + } + + fn search_width(&self, proof_size: f64) -> u64 { + (16.0 * proof_size * (self.completeness_param2 + 2.0) / LOG2_E).ceil() as u64 + } + + fn valid_proof_probability(&self, search_width: u64) -> f64 { + (4.0 + 2.0 * self.completeness_param2) / (search_width as f64 * LOG2_E) + } + + fn dfs_bound(&self, proof_size: f64, search_width: u64) -> u64 { + let search_width_f64 = search_width as f64; + ((1.0 + proof_size.log2() * (self.completeness_param2 + 2.0).recip()) + * 3.0 + * proof_size + * search_width_f64 + / 4.0 + + proof_size + + search_width_f64) + .floor() as u64 + } +} + +impl Mid { + fn max_vertices_visited(proof_size: f64, l1: f64) -> f64 { + fn factorial_check(max_v: f64, l1: f64) -> bool { + let bound = (-l1).exp2(); + let mut factor = (max_v.ceil() as u64).saturating_add(1); + let max_v_2 = max_v + 2.0; + let exp_1_over_max_v = max_v.recip().exp(); + let mut ratio = + (14.0 * max_v * max_v * max_v_2 * exp_1_over_max_v) / (max_v_2 - exp_1_over_max_v); + while factor != 0 { + ratio /= factor as f64; + if ratio <= bound { + return true; + } + factor = factor.saturating_sub(1); + } + false + } + let mut max_v = proof_size; + while !factorial_check(max_v, l1) { + max_v += 1.0; + } + max_v + } +} + +impl Case for Mid { + fn new(completeness_param: f64, set_size: u64, proof_size: f64) -> Self { + let ratio = 9.0 * set_size as f64 * LOG2_E / ((17.0 * proof_size).powi(2)); + let s1 = ratio - 7.0; + let completeness_param1 = completeness_param.min(s1); + + Self { + completeness_param, + completeness_param1, + set_size, + } + } + + fn max_retries(&self) -> u64 { + (self.completeness_param / self.completeness_param1).ceil() as u64 + } + + fn search_width(&self, proof_size: f64) -> u64 { + (16.0 * proof_size * (self.completeness_param1 + 7.0) / LOG2_E).ceil() as u64 + } + + fn valid_proof_probability(&self, search_width: u64) -> f64 { + 2.0 * (self.completeness_param1 + 7.0) / (LOG2_E * search_width as f64) + } + + fn dfs_bound(&self, proof_size: f64, search_width: u64) -> u64 { + let search_width_f64 = search_width as f64; + let lbar = (self.completeness_param1 + 7.0) / LOG2_E; + let max_v = Mid::max_vertices_visited(proof_size, self.completeness_param1); + let exponential = (2.0 * proof_size * max_v * lbar / self.set_size as f64 + + 7.0 * proof_size / max_v) + .exp(); + + ((max_v * lbar + search_width_f64) * proof_size * exponential + search_width_f64).floor() + as u64 + } +} diff --git a/src/centralized_telescope/mod.rs b/src/centralized_telescope/mod.rs index 0529ee58..3946e3b5 100644 --- a/src/centralized_telescope/mod.rs +++ b/src/centralized_telescope/mod.rs @@ -1,23 +1,15 @@ //! ALBA's bounded DFS scheme using Blake2b as hash function. //! (c.f. Section 3.2.2 of Alba paper) -#![doc = include_str!("../../docs/rustdoc/centralized_telescope/main.md")] - -mod algorithm; - -pub mod init; - -pub mod params; +mod cases; pub mod proof; mod round; -pub mod setup; - -mod types; +pub mod params; -mod wrapper; +mod telescope; +pub use telescope::Telescope; -// Re-exports -pub use wrapper::Wrapper as CentralizedTelescope; +pub mod types; \ No newline at end of file diff --git a/src/centralized_telescope/params.rs b/src/centralized_telescope/params.rs index 8c511602..9df91d28 100644 --- a/src/centralized_telescope/params.rs +++ b/src/centralized_telescope/params.rs @@ -1,16 +1,79 @@ -//! ALBA's Setup structure +//! Centralized Telescope's `Params` structure comprising the internal parameters #![doc = include_str!("../../docs/rustdoc/centralized_telescope/params.md")] +use super::cases::{Case, Cases, High, Mid, Small}; +use std::f64::consts::LOG2_E; + /// Setup input parameters #[derive(Debug, Clone, Copy)] pub struct Params { - /// Soundness security parameter - pub soundness_param: f64, - /// Completeness security parameter - pub completeness_param: f64, - /// Approximate size of the prover set to lower bound - pub set_size: u64, - /// Lower bound to prove on prover set - pub lower_bound: u64, + /// Number of prover set's elements + pub proof_size: u64, + /// Maximum number of retries to find a proof + pub max_retries: u64, + /// Maximum number of subtrees to search to find a proof + pub search_width: u64, + /// Probability that a tuple of element is a valid proof + pub valid_proof_probability: f64, + /// Maximum number of DFS calls permitted to find a proof + pub dfs_bound: u64, +} + +impl Params { + /// Returns a `Params` structure from user parameters + /// + /// # Arguments + /// + /// * `soundness_param` - the protocol soundness parameter, typically set at 128 + /// * `completeness_param` - the protocol completeness parameter, typically set at 128 + /// * `set_size` - the size of the prover set to lower bound + /// * `lower_bound` - the lower bound to prove + /// + /// # Returns + /// + /// A `Params` structure + /// + /// # Example + /// + /// ``` + /// use alba::centralized_telescope::params::Params; + /// let params = Params::new(128.0, 128.0, 1_000, 750); + /// ``` + pub fn new( + soundness_param: f64, + completeness_param: f64, + set_size: u64, + lower_bound: u64, + ) -> Self { + let proof_size_f64 = Self::proof_size( + soundness_param, + completeness_param, + set_size as f64, + lower_bound as f64, + ); + + match Cases::which(completeness_param, set_size, proof_size_f64 as u64) { + Cases::Small => Small::new(completeness_param, set_size, proof_size_f64) + .create_params(proof_size_f64), + Cases::Mid => { + Mid::new(completeness_param, set_size, proof_size_f64).create_params(proof_size_f64) + } + Cases::High => High::new(completeness_param, set_size, proof_size_f64) + .create_params(proof_size_f64), + } + } + + /// Compute the proof size out of the security parameters, the set size and + /// the lower bound + fn proof_size( + soundness_param: f64, + completeness_param: f64, + set_size: f64, + lower_bound: f64, + ) -> f64 { + let numerator = soundness_param + completeness_param.log2() + 5.0 - LOG2_E.log2(); + let denominator = (set_size / lower_bound).log2(); + (numerator / denominator).ceil() + } } diff --git a/src/centralized_telescope/proof.rs b/src/centralized_telescope/proof.rs index 59e9a94d..e0d86a6f 100644 --- a/src/centralized_telescope/proof.rs +++ b/src/centralized_telescope/proof.rs @@ -1,8 +1,15 @@ -//! Centralized Telescope Proof structure +//! Centralized Telescope's `Proof` structure #![doc = include_str!("../../docs/rustdoc/centralized_telescope/proof.md")] -use crate::utils::types::Element; +use super::params::Params; +use super::round::Round; +use crate::utils::{ + sample, + types::Element, +}; +use super::types::Hash; +use blake2::{Blake2s256, Digest}; /// Centralized Telescope proof #[derive(Debug, Clone)] @@ -14,3 +21,222 @@ pub struct Proof { /// Sequence of elements from prover's set pub element_sequence: Vec, } + +impl Proof { + /// Centralized Telescope's proving algorithm, based on a DFS algorithm. + /// Calls up to params.max_retries times the prove_index function and + /// returns a `Proof` if a suitable candidate tuple is found. + /// + /// # Arguments + /// + /// * `set_size` - the size of the prover set to lower bound + /// * `params` - the internal parameters to generate a proof from + /// * `prover_set` - the dataset to generate a proof from + /// + /// # Returns + /// + /// A `Proof` structure + /// + /// # Example + /// + /// ``` + /// use alba::centralized_telescope::params::Params; + /// use alba::centralized_telescope::proof::Proof; + /// let set_size = 200; + /// let params = Params::new(128.0, 128.0, set_size, 100); + /// let mut prover_set = Vec::new(); + /// for i in 0..set_size { + /// prover_set.push([(i % 256) as u8 ;32]); + /// } + /// let proof = Proof::new(set_size, ¶ms, &prover_set).unwrap(); + /// ``` + pub fn new(set_size: u64, params: &Params, prover_set: &[Element]) -> Option { + // Run prove_index up to max_retries times + (0..params.max_retries).find_map(|retry_counter| { + Self::prove_index(set_size, params, prover_set, retry_counter).1 + }) + } + + /// Centralized Telescope's verification algorithm, returns true if the + /// proof is successfully verified, following the DFS verification, false + /// otherwise. + /// + /// # Arguments + /// + /// * `self` - the proof to verify + /// * `set_size` - the size of the prover set to lower bound + /// * `params` - the internal parameters to generate a proof from + /// + /// # Returns + /// + /// A boolean, true if the proof verifies successfully otherwise false + /// + /// # Example + /// + /// ``` + /// use alba::centralized_telescope::params::Params; + /// use alba::centralized_telescope::proof::Proof; + /// let set_size = 200; + /// let params = Params::new(128.0, 128.0, set_size, 100); + /// let mut prover_set = Vec::new(); + /// for i in 0..set_size { + /// prover_set.push([(i % 256) as u8 ;32]); + /// } + /// let proof = Proof::new(set_size, ¶ms, &prover_set).unwrap(); + /// let b = proof.verify(set_size, ¶ms); + /// assert!(b); + /// ``` + pub fn verify(&self, set_size: u64, params: &Params) -> bool { + if self.search_counter >= params.search_width + || self.retry_counter >= params.max_retries + || self.element_sequence.len() as u64 != params.proof_size + { + return false; + } + + // Initialise a round with given retry and search counters + let Some(mut round) = Round::new(self.retry_counter, self.search_counter, set_size) else { + return false; + }; + + // For each element in the proof's sequence + for &element in &self.element_sequence { + // Retrieve the bin id associated to this new element + let Some(bin_id) = Proof::bin_hash(set_size, self.retry_counter, element) else { + return false; + }; + // Check that the new element was chosen correctly + // i.e. that we chose the new element such that its bin id equals the round id + if round.id == bin_id { + match Round::update(&round, element) { + Some(r) => round = r, + None => return false, + } + } else { + return false; + } + } + Proof::proof_hash(params.valid_proof_probability, &round) + } + + /// Indexed proving algorithm, returns the total number of DFS calls done + /// to find a proof and Some(proof) if found within params.dfs_bound calls + /// of DFS, otherwise None + fn prove_index( + set_size: u64, + params: &Params, + prover_set: &[Element], + retry_counter: u64, + ) -> (u64, Option) { + // Initialize set_size bins + let mut bins: Vec> = Vec::with_capacity(set_size as usize); + for _ in 0..set_size { + bins.push(Vec::new()); + } + + // Take only up to 2*set_size elements for efficiency and fill the bins + // with them + for &element in prover_set.iter().take(set_size.saturating_mul(2) as usize) { + match Proof::bin_hash(set_size, retry_counter, element) { + Some(bin_index) => { + bins[bin_index as usize].push(element); + } + None => return (0, None), + } + } + + // Run the DFS algorithm on up to params.search_width different trees + let mut step = 0; + for search_counter in 0..params.search_width { + // If DFS was called more than dfs_bound times, abort this retry + if step >= params.dfs_bound { + return (step, None); + } + // Initialize new round + if let Some(r) = Round::new(retry_counter, search_counter, set_size) { + // Run DFS on such round, incrementing step + let (dfs_calls, proof_opt) = Self::dfs(params, &bins, &r, step.saturating_add(1)); + // Return proof if found + if proof_opt.is_some() { + return (dfs_calls, proof_opt); + } + // Update step, that is the number of DFS calls + step = dfs_calls; + } + } + (step, None) + } + + /// Depth-First Search (DFS) algorithm which goes through all potential + /// round candidates and returns the total number of recursive DFS calls + /// done and, if not found under params.dfs_bound calls, None otherwise + /// Some(Proof), that is the first "round", i.e. the first proof candidate, + /// Round{retry_counter, search_counter, x_1, ..., x_u)} such that: + /// - ∀i ∈ [0, u-1], bin_hash(x_i+1) ∈ bins[round_hash(...round_hash(round_hash(v, t), x_1), ..., x_i)] + /// - proof_hash(round_hash(... round_hash((round_hash(v, t), x_1), ..., x_u)) = true + fn dfs( + params: &Params, + bins: &[Vec], + round: &Round, + mut step: u64, + ) -> (u64, Option) { + // If current round comprises params.proof_size elements and satisfies + // the proof_hash check, return it cast as a Proof + if round.element_sequence.len() as u64 == params.proof_size { + let proof_opt = if Proof::proof_hash(params.valid_proof_probability, round) { + Some(Self { + retry_counter: round.retry_counter, + search_counter: round.search_counter, + element_sequence: round.element_sequence.clone(), + }) + } else { + None + }; + return (step, proof_opt); + } + + // For each element in bin numbered id + for &element in &bins[round.id as usize] { + // If DFS was called more than params.dfs_bound times, abort this + // round + if step == params.dfs_bound { + return (step, None); + } + // Update round with such element + if let Some(r) = Round::update(round, element) { + // Run DFS on updated round, incrementing step + let (dfs_calls, proof_opt) = Self::dfs(params, bins, &r, step.saturating_add(1)); + // Return proof if found + if proof_opt.is_some() { + return (dfs_calls, proof_opt); + } + // Update step, i.e. is the number of DFS calls + step = dfs_calls; + } + } + // If no proof was found, return number of steps and None + (step, None) + } + + /// Oracle producing a uniformly random value in [0, set_size[ used for + /// prehashing S_p + fn bin_hash(set_size: u64, retry_counter: u64, element: Element) -> Option { + let retry_bytes: [u8; 8] = retry_counter.to_be_bytes(); + let mut hasher = Blake2s256::new(); + hasher.update(b"Telescope-bin_hash"); + hasher.update(retry_bytes); + hasher.update(element); + let digest: Hash = hasher.finalize().into(); + sample::sample_uniform(&digest, set_size) + } + + /// Oracle defined as Bernoulli(q) returning 1 with probability q and 0 + /// otherwise + fn proof_hash(valid_proof_probability: f64, r: &Round) -> bool { + let mut hasher = Blake2s256::new(); + hasher.update(b"Telescope-proof_hash"); + hasher.update(r.hash); + let digest: Hash = hasher.finalize().into(); + sample::sample_bernoulli(&digest, valid_proof_probability) + } +} diff --git a/src/centralized_telescope/round.rs b/src/centralized_telescope/round.rs index 4b3a59bc..a2a8198d 100644 --- a/src/centralized_telescope/round.rs +++ b/src/centralized_telescope/round.rs @@ -1,4 +1,4 @@ -//! ALBA's Round structure and associated functions +//! Centralized Telescope's `Round` structure and associated functions #![doc = include_str!("../../docs/rustdoc/centralized_telescope/round.md")] diff --git a/src/centralized_telescope/telescope.rs b/src/centralized_telescope/telescope.rs new file mode 100644 index 00000000..cc94bcf4 --- /dev/null +++ b/src/centralized_telescope/telescope.rs @@ -0,0 +1,126 @@ +//! Customer facing Centralized Telescope structure +use super::params::Params; +use super::proof::Proof; +use crate::utils::types::Element; + +/// The main centralized Telescope struct with prove and verify functions. +#[derive(Debug, Clone, Copy)] +pub struct Telescope { + /// Approximate size of the prover set to lower bound + set_size: u64, + /// Internal parameters + params: Params, +} + +impl Telescope { + /// Returns a `Telescope` structure from input parameters + /// + /// # Arguments + /// + /// * `soundness_param` - the protocol soundness parameter, typically set at 128 + /// * `completeness_param` - the protocol completeness parameter, typically set at 128 + /// * `set_size` - the size of the prover set to lower bound + /// * `lower_bound` - the lower bound to prove + /// + /// # Returns + /// + /// A `Telescope`` structure + /// + /// # Example + /// + /// ``` + /// use alba::centralized_telescope::Telescope; + /// let telescope = Telescope::create(128.0, 128.0, 1_000, 750); + /// ``` + pub fn create( + soundness_param: f64, + completeness_param: f64, + set_size: u64, + lower_bound: u64, + ) -> Self { + let params = Params::new(soundness_param, completeness_param, set_size, lower_bound); + Self { set_size, params } + } + + /// Use with caution. Returns a `Telescope` structure from input and + /// internal parameters without checking the consistency between parameters + /// + /// # Arguments + /// + /// * `set_size` - the size of the prover set to lower bound + /// * `params` - some centralized Telescope internal parameters + /// + /// # Returns + /// + /// A `Telescope` structure + /// + /// # Example + /// + /// ``` + /// use alba::centralized_telescope::Telescope; + /// use alba::centralized_telescope::params::Params; + /// let params = Params {proof_size : 200, max_retries: 128, search_width: 10, valid_proof_probability: 0.001, dfs_bound: 40_000}; + /// let telescope = Telescope::setup_unsafe(1_000, ¶ms); + /// ``` + pub fn setup_unsafe(set_size: u64, params: &Params) -> Self { + Self { + set_size, + params: *params, + } + } + + /// Generates a Centralized Telescope proof. + /// + /// # Arguments + /// + /// * `self` - the current `Telescope` structure + /// * `prover_set` - an array of elements to generate an Alba proof on + /// + /// # Returns + /// + /// A `Proof` if found, `None` otherwise + /// + /// # Example + /// + /// ``` + /// use alba::centralized_telescope::Telescope; + /// let set_size = 200; + /// let telescope = Telescope::create(64.0, 64.0, set_size, 100); + /// let mut prover_set = Vec::new(); + /// for i in 0..set_size { + /// prover_set.push([(i % 256) as u8 ;32]); + /// } + /// let proof = telescope.prove(&prover_set).unwrap(); + /// ``` + pub fn prove(&self, prover_set: &[Element]) -> Option { + Proof::new(self.set_size, &self.params, prover_set) + } + + /// Verifies a Centralized Telescope proof. + /// + /// # Arguments + /// + /// * `self` - the current `Telescope` structure + /// * `proof` - a centralized Telescope proof + /// + /// # Returns + /// + /// True if the verification is successful, false otherwise + /// + /// # Example + /// + /// ``` + /// use alba::centralized_telescope::Telescope; + /// let set_size = 200; + /// let telescope = Telescope::create(64.0, 64.0, set_size, 100); + /// let mut prover_set = Vec::new(); + /// for i in 0..set_size { + /// prover_set.push([(i % 256) as u8 ;32]); + /// } + /// let proof = telescope.prove(&prover_set).unwrap(); + /// assert!(telescope.verify(&proof)); + /// ``` + pub fn verify(&self, proof: &Proof) -> bool { + proof.verify(self.set_size, &self.params) + } +}