use alloc::vec::Vec;
use core::cmp::Ordering;
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, LDT_QUERY_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>,
pow_bits: 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,
boost(ErrorBits::from_log2(bits.max(0.0)), pow_bits),
))
}
fn conjectured_batching_term(
shape: &InstanceShape,
log_blowup: usize,
pow_bits: usize,
) -> Option<SecurityTerm> {
batching_term(
SecurityAssumption::UniqueDecoding,
shape,
log_blowup,
None,
pow_bits,
)
}
fn regime_report(
regime: Regime,
air: &StarkAirParams,
shape: &InstanceShape,
list_size: f64,
ldt_term: SecurityTerm,
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(ldt_term);
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(),
SecurityTerm::new(LDT_LABEL, udr_ldt),
batching_term(
SecurityAssumption::UniqueDecoding,
shape,
log_blowup,
None,
grinding.batch_combination,
),
extras,
grinding,
);
let ldr = ldt
.ldr_candidates(air, shape)
.into_iter()
.map(|(m, ldr_ldt)| {
let list_size = list_size_ldr_m(log_blowup, m);
regime_report(
Regime::ListDecoding { m },
air,
shape,
list_size,
SecurityTerm::new(LDT_LABEL, ldr_ldt),
batching_term(
SecurityAssumption::JohnsonBound,
shape,
log_blowup,
Some(m),
grinding.batch_combination,
),
extras,
grinding,
)
})
.max_by(|a, b| {
a.security_bits()
.partial_cmp(&b.security_bits())
.unwrap_or(Ordering::Equal)
});
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 batch = conjectured_batching_term(shape, ldt.log_blowup(), grinding.batch_combination);
let mut all: Vec<ErrorBits> = Vec::with_capacity(3 + ldt_terms.len() + extras.len());
all.push(ali);
all.push(deep);
all.extend(ldt_terms.iter().map(|t| t.bits));
all.extend(batch.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 batch = conjectured_batching_term(shape, ldt.log_blowup(), grinding.batch_combination);
let mut terms = Vec::with_capacity(4 + 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(batch);
terms.extend_from_slice(extras);
terms.push(SecurityTerm::new(
COLLISION_LABEL,
ErrorBits::from_log2(shape.collision_resistance as f64),
));
RegimeReport::new(Regime::Conjectured, terms)
}
pub fn legacy_security<L: LowDegreeTest>(
ldt: &L,
air: &StarkAirParams,
shape: &InstanceShape,
extras: &[ErrorBits],
grinding: &GrindingSites,
) -> Option<ErrorBits> {
let ldt_error = ldt.legacy_conjectured_error(shape)?;
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 batch = conjectured_batching_term(shape, ldt.log_blowup(), grinding.batch_combination);
let mut all = Vec::with_capacity(4 + extras.len());
all.push(ali);
all.push(deep);
all.push(ldt_error);
all.extend(batch.map(|t| t.bits));
all.extend_from_slice(extras);
let algebraic = ErrorBits::min(&all);
Some(ErrorBits::from_log2(
algebraic.bits().min(shape.collision_resistance as f64),
))
}
pub fn legacy_security_report<L: LowDegreeTest>(
ldt: &L,
air: &StarkAirParams,
shape: &InstanceShape,
extras: &[SecurityTerm],
grinding: &GrindingSites,
) -> Option<RegimeReport> {
let ldt_error = ldt.legacy_conjectured_error(shape)?;
Some(regime_report(
Regime::Legacy,
air,
shape,
list_size_conjectured(),
SecurityTerm::new(LDT_QUERY_LABEL, ldt_error),
conjectured_batching_term(shape, ldt.log_blowup(), grinding.batch_combination),
extras,
grinding,
))
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
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,
num_quotient_chunks: 1,
max_combo: 2,
}
}
fn batched_shape() -> InstanceShape {
InstanceShape {
modulus_bits: 100,
num_batched_functions: 1 << 10,
..shape()
}
}
#[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,
SecurityTerm::new(LDT_LABEL, 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);
}
struct LdtOnlyChoice(crate::fri::FriRegime);
impl LowDegreeTest for LdtOnlyChoice {
fn log_blowup(&self) -> usize {
self.0.log_blowup()
}
fn proven_error_udr(&self, air: &StarkAirParams, shape: &InstanceShape) -> ErrorBits {
self.0.proven_error_udr(air, shape)
}
fn best_ldr(
&self,
air: &StarkAirParams,
shape: &InstanceShape,
) -> Option<(usize, ErrorBits)> {
self.0.best_ldr(air, shape)
}
fn conjectured_error(&self, shape: &InstanceShape) -> ErrorBits {
self.0.conjectured_error(shape)
}
}
fn grindable_regime(commit_pow_bits: usize) -> crate::fri::FriRegime {
crate::fri::FriRegime {
log_blowup: 1,
num_queries: 64,
log_final_poly_len: 0,
max_log_arity: 2,
commit_pow_bits,
query_pow_bits: 20,
}
}
#[test]
fn proven_report_picks_m_by_the_composite_not_the_low_degree_test() {
let air = air();
let shape = batched_shape();
for commit_pow_bits in [0, 8, 16, 20, 24] {
let regime = grindable_regime(commit_pow_bits);
let composite =
proven_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let ldt_only = proven_security_report(
&LdtOnlyChoice(regime),
&air,
&shape,
&[],
&GrindingSites::NONE,
);
assert!(
composite.security_bits() >= ldt_only.security_bits() - 1e-9,
"composite choice lost to the LDT-only choice at commit_pow_bits {commit_pow_bits}: \
{} < {}",
composite.security_bits(),
ldt_only.security_bits()
);
}
let regime = grindable_regime(0);
let composite = proven_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let ldt_only = proven_security_report(
&LdtOnlyChoice(regime),
&air,
&shape,
&[],
&GrindingSites::NONE,
);
assert!(
composite.security_bits() > ldt_only.security_bits(),
"expected the composite choice to beat the LDT-only choice"
);
}
#[test]
fn commit_grinding_never_lowers_the_proven_level_when_openings_are_batched() {
let air = air();
let shape = batched_shape();
let mut previous = f64::NEG_INFINITY;
for commit_pow_bits in [0, 4, 8, 12, 16, 20, 24, 28] {
let regime = grindable_regime(commit_pow_bits);
let level = proven_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE)
.security_bits();
assert!(
level >= previous - 1e-9,
"grinding {commit_pow_bits} bits lowered the level: {previous} -> {level}"
);
previous = level;
}
}
#[test]
fn batch_grinding_lifts_only_the_batch_term() {
let regime = benchmark_regime();
let air = air();
let shape = InstanceShape {
modulus_bits: 100,
num_batched_functions: 1 << 10,
..shape()
};
let ground = GrindingSites {
batch_combination: 12,
..GrindingSites::NONE
};
let b0 = proven_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let b12 = proven_security_report(®ime, &air, &shape, &[], &ground);
for (before, after) in
core::iter::once((&b0.udr, &b12.udr)).chain(b0.ldr.iter().zip(b12.ldr.iter()))
{
if before.regime != after.regime {
assert!(
after.security_bits() >= before.security_bits() - 1e-12,
"grinding lowered the level by moving the regime: {} -> {}",
before.security_bits(),
after.security_bits()
);
continue;
}
for (t0, t12) in before.terms().iter().zip(after.terms()) {
assert_eq!(t0.label, t12.label, "term order changed");
let expected = if t0.label == BATCH_LABEL {
t0.bits.bits() + 12.0
} else {
t0.bits.bits()
};
assert!(
(t12.bits.bits() - expected).abs() < 1e-12,
"{} moved to {} (expected {expected})",
t0.label,
t12.bits.bits()
);
}
}
}
#[test]
fn batch_grinding_buys_nothing_when_nothing_is_batched() {
let regime = benchmark_regime();
let air = air();
let shape = InstanceShape {
modulus_bits: 100,
num_batched_functions: 1,
..shape()
};
let b0 = proven_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let b32 = proven_security_report(
®ime,
&air,
&shape,
&[],
&GrindingSites {
batch_combination: 32,
..GrindingSites::NONE
},
);
assert!((b32.security_bits() - b0.security_bits()).abs() < 1e-12);
assert!(b32.udr.terms().iter().all(|t| t.label != BATCH_LABEL));
}
#[test]
fn legacy_report_uses_ethstark_query_bound() {
let regime = benchmark_regime();
let report =
legacy_security_report(®ime, &air(), &shape(), &[], &GrindingSites::NONE).unwrap();
assert_eq!(report.regime, Regime::Legacy);
assert_eq!(report.security_bits(), 116.0);
assert_eq!(report.binding().label, LDT_QUERY_LABEL);
assert_eq!(report.terms().len(), 4);
assert_eq!(
legacy_security(®ime, &air(), &shape(), &[], &GrindingSites::NONE)
.unwrap()
.bits(),
116.0,
);
let small_shape = InstanceShape {
modulus_bits: 96,
..shape()
};
let small =
legacy_security_report(®ime, &air(), &small_shape, &[], &GrindingSites::NONE)
.unwrap();
assert_eq!(small.binding().label, DEEP_LABEL);
assert_eq!(small.security_bits() as usize, 74);
assert_eq!(
legacy_security(®ime, &air(), &small_shape, &[], &GrindingSites::NONE)
.unwrap()
.bits(),
small.security_bits(),
);
}
#[test]
fn legacy_report_preserves_composite_caps_and_grinding() {
let regime = benchmark_regime();
let air = air();
let shape = InstanceShape {
modulus_bits: 100,
num_batched_functions: 1025,
..shape()
};
let cases = [
(GrindingSites::NONE, 128, None, BATCH_LABEL, 69),
(
GrindingSites {
batch_combination: 8,
..GrindingSites::NONE
},
128,
None,
BATCH_LABEL,
77,
),
(
GrindingSites {
batch_combination: 20,
out_of_domain: 8,
..GrindingSites::NONE
},
128,
None,
DEEP_LABEL,
86,
),
(GrindingSites::NONE, 64, None, COLLISION_LABEL, 64),
(GrindingSites::NONE, 128, Some(40.0), "extra", 40),
];
for (grinding, collision_resistance, extra, label, bits) in cases {
let shape = InstanceShape {
collision_resistance,
..shape
};
let extras: Vec<_> = extra.into_iter().map(ErrorBits::from_log2).collect();
let terms: Vec<_> = extras
.iter()
.map(|&bits| SecurityTerm::new("extra", bits))
.collect();
let report = legacy_security_report(®ime, &air, &shape, &terms, &grinding).unwrap();
let scalar = legacy_security(®ime, &air, &shape, &extras, &grinding).unwrap();
assert_eq!(report.binding().label, label);
assert_eq!(report.security_bits() as usize, bits);
assert_eq!(scalar.bits(), report.security_bits());
}
}
#[test]
fn legacy_report_requires_explicit_ldt_support() {
let ldt = LdtOnlyChoice(benchmark_regime());
assert!(
legacy_security_report(&ldt, &air(), &shape(), &[], &GrindingSites::NONE).is_none()
);
assert!(legacy_security(&ldt, &air(), &shape(), &[], &GrindingSites::NONE).is_none());
}
proptest! {
#[test]
fn fri_legacy_report_stays_above_modern_reports(
fri in (0usize..=4, 0usize..=64, 0usize..=32, 0usize..=4, 0usize..=24, 0usize..=24),
instance in (0usize..=24, 0usize..=256, 0usize..=160, 0usize..=64),
air_shape in (0usize..=64, 0usize..=16, 0usize..=4),
grinding in (0usize..=24, 0usize..=24),
extra in proptest::option::of(0usize..=160),
) {
let (log_blowup, num_queries, log_final_poly_len, max_log_arity, commit_pow_bits, query_pow_bits) = fri;
let regime = crate::fri::FriRegime {
log_blowup,
num_queries,
log_final_poly_len,
max_log_arity,
commit_pow_bits,
query_pow_bits,
};
let (log_trace_length, modulus_bits, collision_resistance, num_batched_functions) = instance;
let shape = InstanceShape {
log_trace_length,
modulus_bits,
collision_resistance,
num_batched_functions,
};
let (num_constraints, max_constraint_degree, max_combo) = air_shape;
let air = StarkAirParams {
num_constraints,
max_constraint_degree,
max_combo,
num_quotient_chunks: (max_constraint_degree.max(2) - 1).next_power_of_two(),
};
let grinding = GrindingSites {
out_of_domain: grinding.0,
batch_combination: grinding.1,
..GrindingSites::NONE
};
let extras: Vec<_> = extra.into_iter()
.map(|bits| SecurityTerm::new("extra", ErrorBits::from_log2(bits as f64)))
.collect();
let legacy = legacy_security_report(®ime, &air, &shape, &extras, &grinding)
.unwrap().security_bits();
let conjectured = conjectured_security_report(®ime, &air, &shape, &extras, &grinding)
.security_bits();
let proven = proven_security_report(®ime, &air, &shape, &extras, &grinding)
.security_bits();
prop_assert!(legacy >= conjectured);
prop_assert!(legacy >= proven);
if modulus_bits >= 32 {
prop_assert!(conjectured >= proven);
}
}
}
#[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);
let batched = batched_shape();
let batched_report =
conjectured_security_report(®ime, &air, &batched, &[], &GrindingSites::NONE);
let batched_scalar =
conjectured_security(®ime, &air, &batched, &[], &GrindingSites::NONE);
assert!((batched_report.security_bits() - batched_scalar.bits()).abs() < 1e-12);
}
#[test]
fn conjectured_report_charges_the_batching_round() {
let regime = benchmark_regime();
let air = air();
let batched = batched_shape();
let unbatched = InstanceShape {
num_batched_functions: 1,
..batched
};
let with_batch =
conjectured_security_report(®ime, &air, &batched, &[], &GrindingSites::NONE);
let without =
conjectured_security_report(®ime, &air, &unbatched, &[], &GrindingSites::NONE);
assert!(
with_batch.terms().iter().any(|t| t.label == BATCH_LABEL),
"batched openings must be charged"
);
assert!(
!without.terms().iter().any(|t| t.label == BATCH_LABEL),
"a protocol that batches nothing samples no such challenge"
);
assert!(
with_batch.security_bits() < without.security_bits(),
"the batching round must constrain the conjectured level: {} vs {}",
with_batch.security_bits(),
without.security_bits()
);
}
#[test]
fn conjectured_stays_above_proven_when_openings_are_batched() {
let regime = benchmark_regime();
let air = air();
let shape = batched_shape();
let conjectured =
conjectured_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
let proven = proven_security_report(®ime, &air, &shape, &[], &GrindingSites::NONE);
assert!(
conjectured.security_bits() >= proven.security_bits(),
"conjectured {} fell below proven {}",
conjectured.security_bits(),
proven.security_bits()
);
}
#[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());
}
}