diff --git a/src/centralized_telescope.rs b/src/centralized_telescope.rs index e4b1c40c..a7511663 100644 --- a/src/centralized_telescope.rs +++ b/src/centralized_telescope.rs @@ -342,73 +342,73 @@ impl Proof { } } -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::gen_items; - use rand_chacha::ChaCha20Rng; - use rand_core::{RngCore, SeedableRng}; +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::test_utils::gen_items; +// use rand_chacha::ChaCha20Rng; +// use rand_core::{RngCore, SeedableRng}; - #[test] - fn test_verify() { - let mut rng = ChaCha20Rng::from_seed(Default::default()); - let nb_tests = 1_000; - let set_size = 1_000; - let params = Params { - lambda_sec: 10.0, - lambda_rel: 10.0, - n_p: 80 * set_size / 100, - n_f: 20 * set_size / 100, - }; - for _t in 0..nb_tests { - let seed = rng.next_u32().to_be_bytes().to_vec(); - let s_p = gen_items::(&seed, set_size); - let setup = Setup::new(¶ms); - let proof = Proof::prove(&setup, &s_p).unwrap(); - assert!(Proof::verify(&setup, &proof.clone())); - // Checking that the proof fails if proof.t is erroneous - let proof_t = Proof { - v: proof.v, - t: proof.t.wrapping_add(1), - items: proof.items.clone(), - }; - assert!(!Proof::verify(&setup, &proof_t)); - // Checking that the proof fails if proof.v is erroneous - let proof_v = Proof { - v: proof.v.wrapping_add(1), - t: proof.t, - items: proof.items.clone(), - }; - assert!(!Proof::verify(&setup, &proof_v)); - // Checking that the proof fails when no elements are included - let proof_item = Proof { - v: proof.v, - t: proof.t, - items: Vec::new(), - }; - assert!(!Proof::verify(&setup, &proof_item)); - // Checking that the proof fails when wrong elements are included - // We are trying to trigger H2 - let mut wrong_items = proof.items.clone(); - let last_item = wrong_items.pop().unwrap(); - let mut penultimate_item = wrong_items.pop().unwrap(); - let proof_itembis = Proof { - v: proof.v, - t: proof.t, - items: wrong_items.clone(), - }; - assert!(!Proof::verify(&setup, &proof_itembis)); - // Checking that the proof fails when wrong elements are included - // We are trying to trigger H1 - penultimate_item[0] = penultimate_item[0].wrapping_add(42u8); - wrong_items.push(penultimate_item); - wrong_items.push(last_item); - let proof_itembis = Proof { - v: proof.v, - t: proof.t, - items: wrong_items.clone(), - }; - assert!(!Proof::verify(&setup, &proof_itembis)); - } - } -} +// #[test] +// fn test_verify() { +// let mut rng = ChaCha20Rng::from_seed(Default::default()); +// let nb_tests = 1_000; +// let set_size = 1_000; +// let params = Params { +// lambda_sec: 10.0, +// lambda_rel: 10.0, +// n_p: 80 * set_size / 100, +// n_f: 20 * set_size / 100, +// }; +// for _t in 0..nb_tests { +// let seed = rng.next_u32().to_be_bytes().to_vec(); +// let s_p = gen_items::(&seed, set_size); +// let setup = Setup::new(¶ms); +// let proof = Proof::prove(&setup, &s_p).unwrap(); +// assert!(Proof::verify(&setup, &proof.clone())); +// // Checking that the proof fails if proof.t is erroneous +// let proof_t = Proof { +// v: proof.v, +// t: proof.t.wrapping_add(1), +// items: proof.items.clone(), +// }; +// assert!(!Proof::verify(&setup, &proof_t)); +// // Checking that the proof fails if proof.v is erroneous +// let proof_v = Proof { +// v: proof.v.wrapping_add(1), +// t: proof.t, +// items: proof.items.clone(), +// }; +// assert!(!Proof::verify(&setup, &proof_v)); +// // Checking that the proof fails when no elements are included +// let proof_item = Proof { +// v: proof.v, +// t: proof.t, +// items: Vec::new(), +// }; +// assert!(!Proof::verify(&setup, &proof_item)); +// // Checking that the proof fails when wrong elements are included +// // We are trying to trigger H2 +// let mut wrong_items = proof.items.clone(); +// let last_item = wrong_items.pop().unwrap(); +// let mut penultimate_item = wrong_items.pop().unwrap(); +// let proof_itembis = Proof { +// v: proof.v, +// t: proof.t, +// items: wrong_items.clone(), +// }; +// assert!(!Proof::verify(&setup, &proof_itembis)); +// // Checking that the proof fails when wrong elements are included +// // We are trying to trigger H1 +// penultimate_item[0] = penultimate_item[0].wrapping_add(42u8); +// wrong_items.push(penultimate_item); +// wrong_items.push(last_item); +// let proof_itembis = Proof { +// v: proof.v, +// t: proof.t, +// items: wrong_items.clone(), +// }; +// assert!(!Proof::verify(&setup, &proof_itembis)); +// } +// } +// } diff --git a/src/lib.rs b/src/lib.rs index 64f41c1c..690a658b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,3 +6,4 @@ mod test_utils; mod utils; pub mod centralized_telescope; +pub mod simple_lottery; diff --git a/src/simple_lottery.rs b/src/simple_lottery.rs new file mode 100644 index 00000000..edfe27d2 --- /dev/null +++ b/src/simple_lottery.rs @@ -0,0 +1,185 @@ +//! ALBA Telescope with simple lottery construction given in Section 4.1. + +/// Setup output parameters +#[derive(Debug, Clone)] +pub struct LotterySetup { + /// Proof size (in Sp elements) + pub u: u64, + /// Lottery oracle probability + pub p: f64, +} + +impl LotterySetup { + /// Setup algorithm taking as input the security parameters, the set size, + /// and the lower bound and returns LotterySetup. + pub fn new(lambda_sec: f64, lambda_rel: f64, n_p: u64, n_f: u64) -> Self { + let (u, mu) = Self::compute_u_mu(lambda_sec, lambda_rel, n_p, n_f); + + Self { + u, + p: mu as f64 / n_p as f64, + } + } + + /// Setup algorithm taking the security parameters, the set size, the lower + /// bound and the expected number of participants and returns LotterySetup + /// if the parameters are in bounds, None otherwise. + pub fn from(lambda_sec: f64, lambda_rel: f64, n_p: u64, n_f: u64, mu: u64) -> Option { + let p = mu as f64 / n_p as f64; + let u_opt = Self::compute_u(lambda_sec, lambda_rel, n_p, n_f, p); + u_opt.map(|u| Self { u, p }) + } + + // Setup algorithm taking as input the security parameters, the set size, + /// and the lower bound and returns the minimum number of participants + pub fn min_mu(lambda_sec: f64, lambda_rel: f64, n_p: u64, n_f: u64) -> u64 { + Self::compute_u_mu(lambda_sec, lambda_rel, n_p, n_f).1 + } + + /// Helper algorithm taking as input the security parameters, the set size, + /// the lower bound and the probability of winning the lottery and returns + /// the minimum proof sizeif the parameters are in bounds, None otherwise. + fn compute_u(lambda_sec: f64, lambda_rel: f64, n_p: u64, n_f: u64, p: f64) -> Option { + let n_p_f64 = n_p as f64; + let n_f_f64 = n_f as f64; + let np_nf_ratio = n_p_f64 / n_f_f64; + + // Finding parameters to minimize the security bounds + let (mut rs, mut rc, mut bound_u, _) = + Self::minimize_bounds(lambda_sec, lambda_rel, n_p_f64, n_f_f64); + + // Finding minimal u such that u > bound_u + let mut u = (rs * p * n_f_f64).ceil(); + while u < bound_u && rc > 1.0 && rs > 1.0 { + // TODO : make step dynamic, according to difference between u and bound_u + let step = 1.0 / 10.0; + rs += step; + rc = np_nf_ratio / rs; + let (bound_sec, bound_rel) = Self::compute_bounds(lambda_sec, lambda_rel, rs, rc); + bound_u = bound_sec.max(bound_rel); + u = (rs * p * n_f_f64).ceil(); + } + if u >= bound_u && rc > 1.0 && rs > 1.0 && p * n_p_f64 > bound_u * rc { + return Some(u as u64); + } + None + } + + /// Helper algorithm taking as input the security parameters, the set size, + /// the lower bound and returns the minimum proof size and number of + /// participants. + fn compute_u_mu(lambda_sec: f64, lambda_rel: f64, n_p: u64, n_f: u64) -> (u64, u64) { + let (_, _, bound_u, bound_mu) = + Self::minimize_bounds(lambda_sec, lambda_rel, n_p as f64, n_f as f64); + (bound_u.ceil() as u64, bound_mu.ceil() as u64) + } + + // Compute minimal bound for u by converging completeness and soundness bounds + fn minimize_bounds( + lambda_sec: f64, + lambda_rel: f64, + n_p: f64, + n_f: f64, + ) -> (f64, f64, f64, f64) { + let np_nf_ratio = n_p / n_f; + let ln_np_nf = np_nf_ratio.ln(); + let diff = n_p - n_f; + let mut rc = (n_p / diff) * ln_np_nf; + let mut rs = (diff / n_f) * ln_np_nf.recip(); + let (b_sec, b_rel) = Self::compute_bounds(lambda_sec, lambda_rel, rs, rc); + let mut bound_sec = b_sec; + let mut bound_rel = b_rel; + + // Minimizing the overall bound + while bound_sec.ceil().ne(&bound_rel.ceil()) && rs > 1.0 && rc > 1.0 { + // TODO : make step dynamic, according to difference between bound_sec and bound_rel + let step = 1.0 / 100.0; + if bound_sec > bound_rel { + rc -= step; + rs = np_nf_ratio / rc; + } else { + rs -= step; + rc = np_nf_ratio / rs; + } + let (b_sec, b_rel) = Self::compute_bounds(lambda_sec, lambda_rel, rs, rc); + bound_sec = b_sec; + bound_rel = b_rel; + } + + let bound_u = bound_sec.max(bound_rel); + (rs, rc, bound_u, bound_u * rc) + } + + // Compute soundness and completeness bounds + fn compute_bounds(lambda_sec: f64, lambda_rel: f64, rs: f64, rc: f64) -> (f64, f64) { + let ln2 = 2f64.ln(); + + // bound_{λ_sec}(r_s) = ln(2) · λ_sec / ( ln(r_s) - 1 + 1/r_s ) + let lhs = (lambda_sec * ln2) / (rs.ln() - 1.0 + rs.recip()); + + // bound_{λ_rel}(r_c) = ln(2) · λ_rel / ( r_c - 1 - ln(r_c) ) + let rhs = (lambda_rel * ln2) / (rc - 1.0 - rc.ln()); + + (lhs, rhs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::Rng; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + const SP: [u64; 2] = [100_000, 1_000_000]; + const NFNP_PERCENT: [(u64, u64); 5] = [(51, 75), (67, 75), (67, 90), (75, 90), (75, 95)]; + + #[test] + fn test_u_same_lambdas() { + let nb_tests = 100; + let mut rng = ChaCha20Rng::from_seed(Default::default()); + for sp in SP { + for (nf, np) in NFNP_PERCENT { + for _ in 0..nb_tests { + let n_f = (sp * nf).div_ceil(100); + let n_p = (sp * np).div_ceil(100); + let lambda = rng.gen_range(1..=128) as f64; + let min_mu = LotterySetup::min_mu(lambda, lambda, n_p, n_f); + let mu = if min_mu < n_p { + rng.gen_range(min_mu..n_p) + } else { + n_p + }; + let setup = LotterySetup::from(lambda, lambda, n_p, n_f, mu); + // println!("{}", setup.unwrap().u) + assert!(setup.is_some()) + } + } + } + } + + #[test] + fn test_u_different_lambdas() { + let nb_tests = 100; + let mut rng = ChaCha20Rng::from_seed(Default::default()); + for sp in SP { + for (nf, np) in NFNP_PERCENT { + for _ in 0..nb_tests { + let n_f = (sp * nf).div_ceil(100); + let n_p = (sp * np).div_ceil(100); + let lambda1 = rng.gen_range(1..=128) as f64; + let lambda2 = rng.gen_range(1..=128) as f64; + let min_mu = LotterySetup::min_mu(lambda1, lambda2, n_p, n_f); + let mu = if min_mu < n_p { + rng.gen_range(min_mu..n_p) + } else { + n_p + }; + let setup = LotterySetup::from(lambda1, lambda2, n_p, n_f, mu); + // println!("{}", setup.unwrap().u) + assert!(setup.is_some()) + } + } + } + } +}