use alloc::vec::Vec;
use core::cmp::Ordering;
use serde::Serialize;
use crate::error::ErrorBits;
pub const ALI_LABEL: &str = "air-composition";
pub const DEEP_LABEL: &str = "deep-ali";
pub const LDT_LABEL: &str = "low-degree-test";
pub const LDT_QUERY_LABEL: &str = "ldt-query-phase";
pub const LDT_COMMIT_LABEL: &str = "ldt-commit-phase";
pub const BATCH_LABEL: &str = "batch-combination";
pub const COLLISION_LABEL: &str = "commitment-collision";
#[derive(Copy, Clone, Debug, PartialEq, Serialize)]
pub struct SecurityTerm {
pub label: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub component: Option<&'static str>,
pub bits: ErrorBits,
}
impl SecurityTerm {
pub const fn new(label: &'static str, bits: ErrorBits) -> Self {
Self {
label,
component: None,
bits,
}
}
#[must_use]
pub const fn in_component(mut self, component: &'static str) -> Self {
self.component = Some(component);
self
}
#[must_use]
pub fn over_candidates(self, candidates: CandidateSet) -> Self {
Self {
bits: ErrorBits::from_log2((self.bits.bits() - candidates.log2_size()).max(0.0)),
..self
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Serialize)]
pub struct CandidateSet {
log2_size: f64,
}
impl CandidateSet {
pub const UNIQUE: Self = Self { log2_size: 0.0 };
#[must_use]
pub fn from_log2(log2_size: f64) -> Option<Self> {
(log2_size.is_finite() && log2_size >= 0.0).then_some(Self { log2_size })
}
#[must_use]
pub const fn log2_size(self) -> f64 {
self.log2_size
}
#[must_use]
pub fn is_unique(self) -> bool {
self.log2_size == 0.0
}
#[must_use]
pub fn product(self, other: Self) -> Self {
Self {
log2_size: self.log2_size + other.log2_size,
}
}
}
#[cfg(test)]
mod candidate_tests {
use super::*;
fn sixteen() -> CandidateSet {
CandidateSet::from_log2(4.0).expect("sixteen is a set size")
}
#[test]
fn a_draw_before_a_candidate_is_named_pays_for_every_one_left_open() {
let charged =
SecurityTerm::new("r", ErrorBits::from_log2(100.0)).over_candidates(sixteen());
assert_eq!(charged.bits.bits(), 96.0);
assert_eq!(charged.label, "r");
let drowned =
SecurityTerm::new("weak", ErrorBits::from_log2(3.0)).over_candidates(sixteen());
assert_eq!(drowned.bits.bits(), 0.0);
}
#[test]
fn one_candidate_leaves_a_draw_at_its_own_strength() {
let term = SecurityTerm::new("r", ErrorBits::from_log2(100.0));
assert_eq!(
term.over_candidates(CandidateSet::UNIQUE).bits.bits(),
100.0
);
assert!(CandidateSet::UNIQUE.is_unique());
assert_eq!(CandidateSet::from_log2(0.0), Some(CandidateSet::UNIQUE));
}
#[test]
fn the_floor_reports_no_bound_rather_than_hiding_a_negative_margin() {
let drowned =
SecurityTerm::new("weak", ErrorBits::from_log2(1.0)).over_candidates(sixteen());
assert_eq!(drowned.bits.bits(), 0.0);
let composed = ErrorBits::sum(&[drowned.bits, ErrorBits::from_log2(128.0)]);
assert!(composed.bits() <= 0.0);
}
#[test]
fn a_count_that_names_no_set_is_refused_before_it_can_be_charged() {
for count in [-1.0, f64::INFINITY, f64::NEG_INFINITY, f64::NAN] {
assert_eq!(CandidateSet::from_log2(count), None);
}
}
#[test]
fn a_layer_charges_its_own_draw_and_forwards_the_set_untouched() {
let candidates = sixteen();
let commitment = SecurityTerm::new("commitment", ErrorBits::from_log2(90.0));
let inner =
SecurityTerm::new("inner", ErrorBits::from_log2(100.0)).over_candidates(candidates);
let outer =
SecurityTerm::new("outer", ErrorBits::from_log2(100.0)).over_candidates(candidates);
assert_eq!(inner.bits.bits(), 96.0);
assert_eq!(outer.bits.bits(), 96.0);
assert_eq!(commitment.bits.bits(), 90.0);
assert_eq!(inner.over_candidates(candidates).bits.bits(), 92.0);
}
#[test]
fn two_open_commitments_multiply_the_tries_a_single_draw_gets() {
let main = CandidateSet::from_log2(4.0).unwrap();
let preprocessed = CandidateSet::from_log2(3.0).unwrap();
assert_eq!(main.product(preprocessed).log2_size(), 7.0);
let charged = SecurityTerm::new("r", ErrorBits::from_log2(100.0))
.over_candidates(main.product(preprocessed));
assert_eq!(charged.bits.bits(), 93.0);
assert_eq!(main.product(CandidateSet::UNIQUE), main);
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
#[non_exhaustive]
pub enum Regime {
UniqueDecoding,
ListDecoding { m: usize },
Conjectured,
Legacy,
}
#[derive(Clone, Debug, Serialize)]
pub struct RegimeReport {
pub regime: Regime,
terms: Vec<SecurityTerm>,
}
impl RegimeReport {
pub(crate) fn new(regime: Regime, terms: Vec<SecurityTerm>) -> Self {
debug_assert!(
!terms.is_empty(),
"a regime report must carry at least one term"
);
Self { regime, terms }
}
pub fn terms(&self) -> &[SecurityTerm] {
&self.terms
}
pub fn binding(&self) -> SecurityTerm {
self.terms
.iter()
.copied()
.min_by(|a, b| {
a.bits
.bits()
.partial_cmp(&b.bits.bits())
.unwrap_or(Ordering::Equal)
})
.expect("a regime report always carries the ALI/DEEP/LDT/collision terms")
}
pub fn security_bits(&self) -> f64 {
self.binding().bits.bits()
}
}
#[derive(Clone, Debug, Serialize)]
pub struct SecurityReport {
pub udr: RegimeReport,
pub ldr: Option<RegimeReport>,
}
impl SecurityReport {
pub fn security_bits(&self) -> f64 {
let ldr = self.ldr.as_ref().map_or(0.0, RegimeReport::security_bits);
self.udr.security_bits().max(ldr)
}
pub fn binding(&self) -> (Regime, SecurityTerm) {
match &self.ldr {
Some(ldr) if ldr.security_bits() > self.udr.security_bits() => {
(ldr.regime, ldr.binding())
}
_ => (self.udr.regime, self.udr.binding()),
}
}
}