use alloc::vec::Vec;
use crate::assumption::SecurityAssumption;
use crate::error::ErrorBits;
use crate::grinding::{GrindingSites, boost};
use crate::ldt::LowDegreeTest;
use crate::proximity::{list_size_conjectured, list_size_ldr_m, list_size_udr};
use crate::report::{
ALI_LABEL, BATCH_LABEL, COLLISION_LABEL, DEEP_LABEL, LDT_LABEL, Regime, RegimeReport,
SecurityReport, SecurityTerm,
};
use crate::shape::{InstanceShape, StarkAirParams};
use crate::{air, deep};
pub fn proven_security_regime(
air: &StarkAirParams,
shape: &InstanceShape,
list_size: f64,
ldt_error: ErrorBits,
extras: &[ErrorBits],
) -> ErrorBits {
let ali = air::composition_error(air.num_constraints, list_size, shape.modulus_bits);
let deep = deep::deep_ali_error(air, shape, list_size);
let mut all: Vec<ErrorBits> = Vec::with_capacity(3 + extras.len());
all.push(ali);
all.push(deep);
all.push(ldt_error);
all.extend_from_slice(extras);
let algebraic = ErrorBits::min(&all);
ErrorBits::from_log2(algebraic.bits().min(shape.collision_resistance as f64))
}
pub fn proven_security_udr(
air: &StarkAirParams,
shape: &InstanceShape,
ldt_error: ErrorBits,
extras: &[ErrorBits],
) -> ErrorBits {
proven_security_regime(air, shape, list_size_udr(), ldt_error, extras)
}
pub fn proven_security_ldr_m(
air: &StarkAirParams,
shape: &InstanceShape,
log_blowup: usize,
m: usize,
ldt_error: ErrorBits,
extras: &[ErrorBits],
) -> ErrorBits {
proven_security_regime(
air,
shape,
list_size_ldr_m(log_blowup, m),
ldt_error,
extras,
)
}
pub fn proven_security(
air: &StarkAirParams,
shape: &InstanceShape,
log_blowup: usize,
udr_ldt_error: ErrorBits,
ldr_best_m: usize,
ldr_ldt_error: ErrorBits,
extras: &[ErrorBits],
) -> ErrorBits {
let udr = proven_security_udr(air, shape, udr_ldt_error, extras);
let ldr = proven_security_ldr_m(air, shape, log_blowup, ldr_best_m, ldr_ldt_error, extras);
ErrorBits::from_log2(udr.bits().max(ldr.bits()))
}
fn batching_term(
assumption: SecurityAssumption,
shape: &InstanceShape,
log_blowup: usize,
ldr_m: Option<usize>,
) -> Option<SecurityTerm> {
let num_functions = shape.num_batched_functions;
if num_functions < 2 {
return None;
}
let bits = match (assumption, ldr_m) {
(SecurityAssumption::JohnsonBound, Some(m)) => SecurityAssumption::prox_gaps_error_jb_at_m(
shape.log_trace_length,
log_blowup,
shape.modulus_bits,
num_functions,
m,
),
_ => assumption.prox_gaps_error(
shape.log_trace_length,
log_blowup,
shape.modulus_bits,
num_functions,
),
};
Some(SecurityTerm::new(
BATCH_LABEL,
ErrorBits::from_log2(bits.max(0.0)),
))
}
fn regime_report(
regime: Regime,
air: &StarkAirParams,
shape: &InstanceShape,
list_size: f64,
ldt_error: ErrorBits,
batch: Option<SecurityTerm>,
extras: &[SecurityTerm],
grinding: &GrindingSites,
) -> RegimeReport {
let ali = air::composition_error(air.num_constraints, list_size, shape.modulus_bits);
let deep = boost(
deep::deep_ali_error(air, shape, list_size),
grinding.out_of_domain,
);
let mut terms = Vec::with_capacity(5 + extras.len());
terms.push(SecurityTerm::new(ALI_LABEL, ali));
terms.push(SecurityTerm::new(DEEP_LABEL, deep));
terms.push(SecurityTerm::new(LDT_LABEL, ldt_error));
terms.extend(batch);
terms.extend_from_slice(extras);
terms.push(SecurityTerm::new(
COLLISION_LABEL,
ErrorBits::from_log2(shape.collision_resistance as f64),
));
RegimeReport::new(regime, terms)
}
pub fn proven_security_report<L: LowDegreeTest>(
ldt: &L,
air: &StarkAirParams,
shape: &InstanceShape,
extras: &[SecurityTerm],
grinding: &GrindingSites,
) -> SecurityReport {
let log_blowup = ldt.log_blowup();
let udr_ldt = ldt.proven_error_udr(air, shape);
let udr = regime_report(
Regime::UniqueDecoding,
air,
shape,
list_size_udr(),
udr_ldt,
batching_term(SecurityAssumption::UniqueDecoding, shape, log_blowup, None),
extras,
grinding,
);
let ldr = ldt.best_ldr(air, shape).map(|(m, ldr_ldt)| {
let list_size = list_size_ldr_m(log_blowup, m);
regime_report(
Regime::ListDecoding { m },
air,
shape,
list_size,
ldr_ldt,
batching_term(SecurityAssumption::JohnsonBound, shape, log_blowup, Some(m)),
extras,
grinding,
)
});
SecurityReport { udr, ldr }
}
pub fn conjectured_security<L: LowDegreeTest>(
ldt: &L,
air: &StarkAirParams,
shape: &InstanceShape,
extras: &[ErrorBits],
grinding: &GrindingSites,
) -> ErrorBits {
let list_size = list_size_conjectured();
let ali = air::composition_error(air.num_constraints, list_size, shape.modulus_bits);
let deep = boost(
deep::deep_ali_error(air, shape, list_size),
grinding.out_of_domain,
);
let ldt_terms = ldt.conjectured_terms(shape);
let mut all: Vec<ErrorBits> = Vec::with_capacity(2 + ldt_terms.len() + extras.len());
all.push(ali);
all.push(deep);
all.extend(ldt_terms.iter().map(|t| t.bits));
all.extend_from_slice(extras);
let algebraic = ErrorBits::min(&all);
ErrorBits::from_log2(algebraic.bits().min(shape.collision_resistance as f64))
}
pub fn conjectured_security_report<L: LowDegreeTest>(
ldt: &L,
air: &StarkAirParams,
shape: &InstanceShape,
extras: &[SecurityTerm],
grinding: &GrindingSites,
) -> RegimeReport {
let list_size = list_size_conjectured();
let ali = air::composition_error(air.num_constraints, list_size, shape.modulus_bits);
let deep = boost(
deep::deep_ali_error(air, shape, list_size),
grinding.out_of_domain,
);
let ldt_terms = ldt.conjectured_terms(shape);
let mut terms = Vec::with_capacity(3 + ldt_terms.len() + extras.len());
terms.push(SecurityTerm::new(ALI_LABEL, ali));
terms.push(SecurityTerm::new(DEEP_LABEL, deep));
terms.extend(ldt_terms);
terms.extend_from_slice(extras);
terms.push(SecurityTerm::new(
COLLISION_LABEL,
ErrorBits::from_log2(shape.collision_resistance as f64),
));
RegimeReport::new(Regime::Conjectured, terms)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::report::LDT_QUERY_LABEL;
fn shape() -> InstanceShape {
InstanceShape {
log_trace_length: 20,
modulus_bits: 252,
collision_resistance: 128,
num_batched_functions: 1,
}
}
fn air() -> StarkAirParams {
StarkAirParams {
num_constraints: 1,
max_constraint_degree: 2,
max_combo: 2,
}
}
#[test]
fn extras_tighten_proven_security_regime() {
let air = air();
let shape = shape();
let ldt = ErrorBits::from_log2(80.0);
let baseline = proven_security_regime(&air, &shape, 1.0, ldt, &[]);
let with_loose =
proven_security_regime(&air, &shape, 1.0, ldt, &[ErrorBits::from_log2(200.0)]);
let with_tight =
proven_security_regime(&air, &shape, 1.0, ldt, &[ErrorBits::from_log2(40.0)]);
assert!((baseline.bits() - with_loose.bits()).abs() < 1e-12);
assert!((with_tight.bits() - 40.0).abs() < 1e-12);
assert!(with_tight.bits() <= baseline.bits());
}
#[test]
fn regime_report_matches_proven_security_regime() {
let air = air();
let shape = shape();
let ldt = ErrorBits::from_log2(80.0);
let list_size = 1.0;
let extra = ErrorBits::from_log2(40.0);
let expected = proven_security_regime(&air, &shape, list_size, ldt, &[extra]);
let report = regime_report(
Regime::UniqueDecoding,
&air,
&shape,
list_size,
ldt,
None,
&[SecurityTerm::new("extra", extra)],
&GrindingSites::NONE,
);
assert!((report.security_bits() - expected.bits()).abs() < 1e-12);
assert_eq!(report.binding().label, "extra");
}
#[test]
fn proven_security_report_matches_scalar_composite() {
use crate::fri::{FriRegime, best_ldr_m, proven_error_udr};
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 = air();
let shape = shape();
let report = proven_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
assert_eq!(report.udr.security_bits().floor() as usize, 57);
let ldr = report
.ldr
.as_ref()
.expect("benchmark has a valid LDR regime");
assert_eq!(ldr.security_bits().floor() as usize, 65);
assert_eq!(report.security_bits().floor() as usize, 65);
let (regime_kind, binding) = report.binding();
assert!(matches!(regime_kind, Regime::ListDecoding { .. }));
assert_eq!(binding.label, LDT_LABEL);
let udr_ldt = proven_error_udr(®ime, &air, &shape);
let (best_m, ldr_ldt) = best_ldr_m(®ime, &air, &shape).unwrap();
let scalar = proven_security(
&air,
&shape,
regime.log_blowup,
udr_ldt,
best_m,
ldr_ldt,
&[],
);
assert_eq!(
report.security_bits().floor() as usize,
scalar.bits().floor() as usize
);
}
#[test]
fn fri_regime_ldt_impl_delegates() {
use crate::fri::{FriRegime, conjectured_error, proven_error_udr};
use crate::ldt::LowDegreeTest;
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 = air();
let shape = shape();
assert_eq!(LowDegreeTest::log_blowup(®ime), regime.log_blowup);
assert_eq!(
LowDegreeTest::proven_error_udr(®ime, &air, &shape).bits(),
proven_error_udr(®ime, &air, &shape).bits()
);
assert_eq!(
LowDegreeTest::conjectured_error(®ime, &shape).bits(),
conjectured_error(®ime, &shape).bits()
);
}
fn benchmark_regime() -> crate::fri::FriRegime {
crate::fri::FriRegime {
log_blowup: 1,
num_queries: 100,
log_final_poly_len: 0,
max_log_arity: 3,
commit_pow_bits: 0,
query_pow_bits: 16,
}
}
#[test]
fn no_batch_term_for_single_function() {
let report = proven_security_report(
&benchmark_regime(),
&air(),
&shape(),
&[],
&GrindingSites::NONE,
);
assert!(report.udr.terms().iter().all(|t| t.label != BATCH_LABEL));
if let Some(ldr) = &report.ldr {
assert!(ldr.terms().iter().all(|t| t.label != BATCH_LABEL));
}
}
#[test]
fn batching_lowers_security_when_binding() {
let regime = benchmark_regime();
let air = air();
let base = InstanceShape {
log_trace_length: 20,
modulus_bits: 64,
collision_resistance: 128,
num_batched_functions: 1,
};
let batched = InstanceShape {
num_batched_functions: 1 << 20,
..base
};
let no_batch = proven_security_report(®ime, &air, &base, &[], &GrindingSites::NONE);
let with_batch = proven_security_report(®ime, &air, &batched, &[], &GrindingSites::NONE);
assert!(with_batch.security_bits() <= no_batch.security_bits());
let (_, binding) = with_batch.binding();
assert_eq!(binding.label, BATCH_LABEL);
}
#[test]
fn ldr_batch_term_uses_regime_m_not_fixed_ten() {
let regime = benchmark_regime();
let air = air();
let shape = InstanceShape {
num_batched_functions: 2,
..shape()
};
let report = proven_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let ldr = report
.ldr
.as_ref()
.expect("benchmark has a valid LDR regime");
let Regime::ListDecoding { m } = ldr.regime else {
panic!("expected a list-decoding regime");
};
assert_ne!(m, 10, "test only pins the m != 10 path if best_m != 10");
let batch_term = ldr
.terms()
.iter()
.find(|t| t.label == BATCH_LABEL)
.expect("batching two functions emits a batch-combination term");
let expected_bits = SecurityAssumption::prox_gaps_error_jb_at_m(
shape.log_trace_length,
regime.log_blowup,
shape.modulus_bits,
shape.num_batched_functions,
m,
)
.max(0.0);
assert!((batch_term.bits.bits() - expected_bits).abs() < 1e-9);
let fixed_m_bits = SecurityAssumption::JohnsonBound
.prox_gaps_error(
shape.log_trace_length,
regime.log_blowup,
shape.modulus_bits,
shape.num_batched_functions,
)
.max(0.0);
assert!(batch_term.bits.bits() < fixed_m_bits);
}
#[test]
fn conjectured_report_matches_scalar_composite() {
let regime = benchmark_regime();
let air = air();
let shape = shape();
let extra = ErrorBits::from_log2(40.0);
let bare_report =
conjectured_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let bare_scalar = conjectured_security(®ime, &air, &shape, &[], &GrindingSites::NONE);
assert!((bare_report.security_bits() - bare_scalar.bits()).abs() < 1e-12);
let report = conjectured_security_report(
®ime,
&air,
&shape,
&[SecurityTerm::new("extra", extra)],
&GrindingSites::NONE,
);
let scalar = conjectured_security(®ime, &air, &shape, &[extra], &GrindingSites::NONE);
assert_eq!(report.regime, Regime::Conjectured);
assert!((report.security_bits() - scalar.bits()).abs() < 1e-12);
assert_eq!(report.binding().label, "extra");
let ground = GrindingSites {
out_of_domain: 24,
..GrindingSites::NONE
};
let ground_report = conjectured_security_report(®ime, &air, &shape, &[], &ground);
let ground_scalar = conjectured_security(®ime, &air, &shape, &[], &ground);
assert!((ground_report.security_bits() - ground_scalar.bits()).abs() < 1e-12);
}
#[test]
fn conjectured_ali_and_deep_carry_no_list_size() {
let regime = benchmark_regime();
let air = air();
let shape = shape();
let conjectured =
conjectured_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let proven = proven_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let ldr = proven
.ldr
.as_ref()
.expect("benchmark has a valid LDR regime");
for label in [ALI_LABEL, DEEP_LABEL] {
let find = |r: &RegimeReport| {
r.terms()
.iter()
.find(|t| t.label == label)
.expect("every regime carries the ALI and DEEP terms")
.bits
.bits()
};
assert!((find(&conjectured) - find(&proven.udr)).abs() < 1e-12);
assert!(find(&conjectured) > find(ldr));
}
}
#[test]
fn conjectured_logup_extra_binds_below_the_ldt_term() {
use crate::logup::{LOGUP_LABEL, LogUpAir, security_term};
let regime = crate::fri::FriRegime {
num_queries: 80,
..benchmark_regime()
};
let air = air();
let shape = InstanceShape {
log_trace_length: 28,
modulus_bits: 128,
collision_resistance: 128,
num_batched_functions: 1,
};
let logup = LogUpAir {
num_interactions: 64,
max_message_width: 8,
};
let term = security_term(&logup, &shape, &GrindingSites::NONE).expect("has interactions");
let ldt_only =
conjectured_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let with_logup =
conjectured_security_report(®ime, &air, &shape, &[term], &GrindingSites::NONE);
assert_eq!(ldt_only.binding().label, LDT_QUERY_LABEL);
assert_eq!(with_logup.binding().label, LOGUP_LABEL);
assert!(with_logup.security_bits() < ldt_only.security_bits());
}
#[test]
fn conjectured_more_constraints_is_not_more_security() {
let regime = benchmark_regime();
let shape = shape();
let few = StarkAirParams {
num_constraints: 1,
..air()
};
let many = StarkAirParams {
num_constraints: 1 << 20,
..air()
};
let b_few = conjectured_security_report(®ime, &few, &shape, &[], &GrindingSites::NONE);
let b_many = conjectured_security_report(®ime, &many, &shape, &[], &GrindingSites::NONE);
assert!(b_many.security_bits() <= b_few.security_bits());
}
#[test]
fn conjectured_more_grinding_is_not_less_security() {
let regime = benchmark_regime();
let air = air();
let shape = InstanceShape {
modulus_bits: 100,
..shape()
};
let ground = GrindingSites {
out_of_domain: 24,
..GrindingSites::NONE
};
let b0 = conjectured_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let b24 = conjectured_security_report(®ime, &air, &shape, &[], &ground);
assert!(b24.security_bits() >= b0.security_bits());
let default_sites =
conjectured_security_report(®ime, &air, &shape, &[], &GrindingSites::default());
assert!((default_sites.security_bits() - b0.security_bits()).abs() < 1e-12);
}
#[test]
fn proven_more_grinding_is_not_less_security() {
let regime = benchmark_regime();
let air = air();
let shape = InstanceShape {
modulus_bits: 100,
..shape()
};
let ground = GrindingSites {
out_of_domain: 24,
..GrindingSites::NONE
};
let b0 = proven_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let b24 = proven_security_report(®ime, &air, &shape, &[], &ground);
assert!(b24.udr.security_bits() >= b0.udr.security_bits());
assert!(b24.security_bits() >= b0.security_bits());
}
}