use libm::log2;
use crate::error::ErrorBits;
use crate::grinding::{GrindingSites, boost};
use crate::report::SecurityTerm;
use crate::shape::InstanceShape;
pub const LOGUP_LABEL: &str = "logup-fingerprint";
#[derive(Copy, Clone, Debug)]
pub struct LogUpAir {
pub num_interactions: usize,
pub max_message_width: usize,
}
pub fn fingerprint_error(air: &LogUpAir, shape: &InstanceShape) -> ErrorBits {
if air.num_interactions == 0 || shape.modulus_bits == 0 {
return ErrorBits::from_log2(0.0);
}
let log_n = shape.log_trace_length as f64 + log2(air.num_interactions as f64);
let width = air.max_message_width.max(1) as f64;
let bits = shape.modulus_bits as f64 - log_n - log2(width + 2.0);
ErrorBits::from_log2(bits.max(0.0))
}
pub fn security_term(
air: &LogUpAir,
shape: &InstanceShape,
grinding: &GrindingSites,
) -> Option<SecurityTerm> {
if air.num_interactions == 0 {
return None;
}
Some(SecurityTerm::new(
LOGUP_LABEL,
boost(fingerprint_error(air, shape), grinding.lookup_challenge),
))
}
#[cfg(test)]
mod tests {
use super::*;
fn shape(modulus_bits: usize) -> InstanceShape {
InstanceShape {
log_trace_length: 20,
modulus_bits,
collision_resistance: 128,
num_batched_functions: 1,
}
}
#[test]
fn fingerprint_error_regression() {
let air = LogUpAir {
num_interactions: 16,
max_message_width: 3,
};
let bits = fingerprint_error(&air, &shape(128)).bits();
let expected = 128.0 - 24.0 - 5f64.log2();
assert!((bits - expected).abs() < 1e-9, "got {bits}");
}
#[test]
fn fingerprint_error_is_monotone() {
let base = LogUpAir {
num_interactions: 16,
max_message_width: 3,
};
let more_interactions = LogUpAir {
num_interactions: 256,
..base
};
let wider = LogUpAir {
max_message_width: 31,
..base
};
let s = shape(128);
let b0 = fingerprint_error(&base, &s).bits();
assert!(fingerprint_error(&more_interactions, &s).bits() < b0);
assert!(fingerprint_error(&wider, &s).bits() < b0);
}
#[test]
fn security_term_absent_without_interactions() {
let none = LogUpAir {
num_interactions: 0,
max_message_width: 4,
};
assert!(security_term(&none, &shape(128), &GrindingSites::NONE).is_none());
let some = LogUpAir {
num_interactions: 4,
max_message_width: 4,
};
let term =
security_term(&some, &shape(128), &GrindingSites::NONE).expect("has interactions");
assert_eq!(term.label, LOGUP_LABEL);
assert_eq!(term.bits, fingerprint_error(&some, &shape(128)));
}
#[test]
fn lookup_challenge_grinding_boosts_the_term() {
let air = LogUpAir {
num_interactions: 4,
max_message_width: 4,
};
let s = shape(128);
let grinding = GrindingSites {
lookup_challenge: 20,
..GrindingSites::NONE
};
let base = security_term(&air, &s, &GrindingSites::NONE).expect("has interactions");
let ground = security_term(&air, &s, &grinding).expect("has interactions");
assert!((ground.bits.bits() - base.bits.bits() - 20.0).abs() < 1e-12);
}
#[test]
fn logup_term_composes_as_extra() {
use crate::fri::FriRegime;
use crate::shape::StarkAirParams;
use crate::stark::proven_security_report;
let regime = FriRegime {
log_blowup: 1,
num_queries: 100,
log_final_poly_len: 0,
max_log_arity: 3,
commit_pow_bits: 0,
query_pow_bits: 16,
};
let air = StarkAirParams {
num_constraints: 1,
max_constraint_degree: 2,
max_combo: 2,
};
let s = shape(64);
let logup = LogUpAir {
num_interactions: 1 << 10,
max_message_width: 8,
};
let baseline = proven_security_report(®ime, &air, &s, &[], &GrindingSites::NONE);
let term = security_term(&logup, &s, &GrindingSites::NONE).expect("has interactions");
let with_logup = proven_security_report(®ime, &air, &s, &[term], &GrindingSites::NONE);
assert!(with_logup.security_bits() <= baseline.security_bits());
assert!(
with_logup
.udr
.terms()
.iter()
.any(|t| t.label == LOGUP_LABEL)
);
}
}