diff --git a/air/src/proof/mod.rs b/air/src/proof/mod.rs index 196d9013f..699db4f76 100644 --- a/air/src/proof/mod.rs +++ b/air/src/proof/mod.rs @@ -108,12 +108,16 @@ impl StarkProof { get_conjectured_security( self.context.options(), self.context.num_modulus_bits(), - self.lde_domain_size() as u64, + self.trace_length() as u64, H::COLLISION_RESISTANCE, ) } else { - // TODO: implement provable security estimation - unimplemented!("proven security estimation has not been implement yet") + get_proven_security( + self.context.options(), + self.context.num_modulus_bits(), + self.lde_domain_size() as u64, + H::COLLISION_RESISTANCE, + ) } } @@ -177,12 +181,12 @@ impl StarkProof { fn get_conjectured_security( options: &ProofOptions, base_field_bits: u32, - lde_domain_size: u64, + trace_domain_size: u64, collision_resistance: u32, ) -> u32 { // compute max security we can get for a given field size let field_size = base_field_bits * options.field_extension().degree(); - let field_security = field_size - lde_domain_size.trailing_zeros(); + let field_security = field_size - trace_domain_size.trailing_zeros(); // compute security we get by executing multiple query rounds let security_per_query = log2(options.blowup_factor()); @@ -198,3 +202,51 @@ fn get_conjectured_security( collision_resistance, ) } + +/// Estimates proven security level for the specified proof parameters. +fn get_proven_security( + options: &ProofOptions, + base_field_bits: u32, + lde_domain_size: u64, + collision_resistance: u32, +) -> u32 { + let extension_field_bits = (base_field_bits * options.field_extension().degree()) as f64; + let blowup_bits = log2(options.blowup_factor()) as f64; + let num_fri_queries = options.num_queries() as f64; + let lde_size_bits = lde_domain_size.trailing_zeros() as f64; + + // m is a parameter greater or equal to 3. + // A larger m gives a worse field security bound but a better query security bound. + // An optimal value of m is then a value that would balance field and query security + // but there is no simple closed form solution. + // This sets m so that field security is equal to the best query security for any value + // of m, unless the calculated value is less than 3 in which case it gets rounded up to 3. + let mut m = extension_field_bits + 1.0; + m -= options.grinding_factor() as f64; + m -= (num_fri_queries + 3.0) / 2.0 * blowup_bits; + m -= 2.0 * lde_size_bits; + m /= 7.0; + m = 2.0_f64.powf(m); + m -= 0.5; + m = m.max(3.0); + + // compute pre-FRI query security + // this considers only the third component given in the corresponding part of eq. 20 + // in https://eprint.iacr.org/2021/582, i.e. (m+1/2)^7.n^2 / (2\rho^1.5.q) as all + // other terms are negligible in comparison. + let pre_query_security = (extension_field_bits + 1.0 + - 3.0 / 2.0 * blowup_bits + - 2.0 * lde_size_bits + - 7.0 * (m + 0.5).log2()) as u32; + + // compute security we get by executing multiple query rounds + let security_per_query = 0.5 * blowup_bits - (1.0 + 1.0 / (2.0 * m)).log2(); + let mut query_security = (security_per_query * num_fri_queries) as u32; + + query_security += options.grinding_factor(); + + cmp::min( + cmp::min(pre_query_security, query_security) - 1, + collision_resistance, + ) +} diff --git a/examples/src/lib.rs b/examples/src/lib.rs index e0d71f8bc..85fc92a76 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -106,14 +106,14 @@ impl ExampleOptions { } /// Returns security level of the input proof in bits. - pub fn get_proof_security_level(&self, proof: &StarkProof) -> usize { + pub fn get_proof_security_level(&self, proof: &StarkProof, conjectured: bool) -> usize { let security_level = match self.hash_fn.as_str() { - "blake3_192" => proof.security_level::(true), - "blake3_256" => proof.security_level::(true), - "sha3_256" => proof.security_level::(true), - "rp64_256" => proof.security_level::(true), - "rp_jive64_256" => proof.security_level::(true), - "griffin_jive64_256" => proof.security_level::(true), + "blake3_192" => proof.security_level::(conjectured), + "blake3_256" => proof.security_level::(conjectured), + "sha3_256" => proof.security_level::(conjectured), + "rp64_256" => proof.security_level::(conjectured), + "rp_jive64_256" => proof.security_level::(conjectured), + "griffin_jive64_256" => proof.security_level::(conjectured), val => panic!("'{val}' is not a valid hash function option"), }; diff --git a/examples/src/main.rs b/examples/src/main.rs index f2803c2c6..48e0073c1 100644 --- a/examples/src/main.rs +++ b/examples/src/main.rs @@ -76,8 +76,12 @@ fn main() { let proof_bytes = proof.to_bytes(); debug!("Proof size: {:.1} KB", proof_bytes.len() as f64 / 1024f64); - let security_level = options.get_proof_security_level(&proof); - debug!("Proof security: {} bits", security_level); + let conjectured_security_level = options.get_proof_security_level(&proof, true); + let proven_security_level = options.get_proof_security_level(&proof, false); + debug!( + "Proof security: {} bits ({} proven)", + conjectured_security_level, proven_security_level, + ); #[cfg(feature = "std")] debug!( "Proof hash: {}",