use lens::WhirParams;
use lens::rspcs::soundness::{Regime, ext_field_bits, log2_add, mca_log2, proximity};
use lens::rspcs::whir::{LeafLayout, WhirConfig};
pub const MIN_BITS: f64 = 128.0;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AccConfig {
pub whir: WhirParams,
pub num_vars: usize,
pub layout: LeafLayout,
pub regime: Regime,
pub ood: usize,
pub queries: usize,
pub query_pow: u32,
pub comb_pow: u32,
pub max_inputs: usize,
pub max_claims: usize,
}
pub const COMB_POW_MAX: u32 = 32;
fn ood_count(log_list: f64, num_vars: usize) -> Result<usize, String> {
if log_list == 0.0 {
return Ok(0);
}
let k = ext_field_bits();
(1..64)
.find(|&s| -(2.0 * log_list - 1.0 + s as f64 * (num_vars as f64 - k)) >= MIN_BITS)
.ok_or_else(|| "acc: no OOD count reaches the target".into())
}
pub fn fresh_ood(whir: &WhirParams, num_vars: usize) -> Result<usize, String> {
let wc = WhirConfig::derive(whir, num_vars).map_err(|e| format!("ood: whir: {e}"))?;
let spec = wc.rounds[0];
ood_count(
proximity(spec.regime, spec.log_inv_rate, spec.log_domain).log_list,
num_vars,
)
}
impl AccConfig {
pub fn derive(
whir: &WhirParams,
num_vars: usize,
max_inputs: usize,
max_claims: usize,
) -> Result<Self, String> {
let wc = WhirConfig::derive(whir, num_vars).map_err(|e| format!("acc: whir: {e}"))?;
let whir_bits = wc.security_bits();
if whir_bits < MIN_BITS {
return Err(format!(
"acc: the decider's WHIR proves {whir_bits:.2} bits < {MIN_BITS}"
));
}
let spec = wc.rounds[0];
let layout = LeafLayout::derive(whir, num_vars).map_err(|e| format!("acc: {e}"))?;
let r = spec.log_inv_rate;
let log_n = spec.log_domain;
let prox = proximity(spec.regime, r, log_n);
let budget = u32::from(whir.pow_bits);
let ood = ood_count(prox.log_list, num_vars)?;
let need = (MIN_BITS - f64::from(budget)).max(0.0);
let queries = ((need / -prox.log_one_minus_delta).ceil() as usize).max(1);
let query_pow = (MIN_BITS - queries as f64 * -prox.log_one_minus_delta)
.ceil()
.max(0.0) as u32;
let mut cfg = Self {
whir: *whir,
num_vars,
layout,
regime: spec.regime,
ood,
queries,
query_pow,
comb_pow: 0,
max_inputs,
max_claims,
};
cfg.comb_pow = cfg.comb_pow_for(max_inputs);
if cfg.comb_pow > COMB_POW_MAX || cfg.query_pow > budget {
return Err(format!(
"acc: grinding {}/{} exceeds the budget {COMB_POW_MAX}/{budget}",
cfg.comb_pow, cfg.query_pow
));
}
let bits = cfg.security_bits(max_inputs, max_claims);
if bits < MIN_BITS {
return Err(format!("acc: {bits:.2} bits < {MIN_BITS}"));
}
Ok(cfg)
}
fn log_n(&self) -> u32 {
self.layout.log_domain
}
fn rate_bits(&self) -> u32 {
self.layout.log_domain - self.num_vars as u32
}
pub fn log_list(&self) -> f64 {
proximity(self.regime, self.rate_bits(), self.log_n()).log_list
}
pub fn log_one_minus_delta(&self) -> f64 {
proximity(self.regime, self.rate_bits(), self.log_n()).log_one_minus_delta
}
pub fn comb_pow_for(&self, m: usize) -> u32 {
(MIN_BITS + self.comb_log_err(m)).ceil().max(0.0) as u32
}
fn comb_log_err(&self, m: usize) -> f64 {
if m <= 1 {
return f64::NEG_INFINITY;
}
let d = ((m - 1) as f64).log2();
let mca = mca_log2(self.regime, self.rate_bits(), self.log_n()) + d;
log2_add(mca, self.log_list() + d - ext_field_bits())
}
pub fn terms(&self, m: usize, j: usize) -> Vec<(String, f64)> {
let k = ext_field_bits();
let list = self.log_list();
let mut out = Vec::new();
if j > 1 {
out.push(("batch".into(), k - list - ((j - 1) as f64).log2()));
}
out.push(("sumcheck".into(), k - list - 1.0));
if m > 1 {
out.push((
"combine".into(),
-self.comb_log_err(m) + f64::from(self.comb_pow_for(m)),
));
}
if list > 0.0 {
let ood = 2.0 * list - 1.0 + self.ood as f64 * (self.num_vars as f64 - k);
out.push(("ood".into(), -ood));
}
out.push((
"spot".into(),
-(self.queries as f64 * self.log_one_minus_delta()) + f64::from(self.query_pow),
));
out
}
pub fn security_bits(&self, m: usize, j: usize) -> f64 {
self.terms(m, j)
.into_iter()
.map(|(_, b)| b)
.fold(f64::INFINITY, f64::min)
}
pub fn acc_claims(&self) -> usize {
1 + self.ood + self.queries
}
}