use alloc::vec::Vec;
use crate::{CandidateSet, ErrorBits, SecurityTerm};
pub const WORD_SHIFT_LABEL: &str = "word-shift-reduction";
pub const WORD_SHIFT_BATCHING_LABEL: &str = "word-shift-batching";
pub const WORD_SHIFT_SUMCHECK_LABEL: &str = "word-shift-sumcheck";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WordShiftSecurityModel {
field_bits: usize,
batching_variables: usize,
sumcheck_rounds: usize,
}
impl WordShiftSecurityModel {
#[must_use]
pub const fn new(
field_bits: usize,
batching_variables: usize,
sumcheck_rounds: usize,
) -> Option<Self> {
if field_bits == 0 {
return None;
}
Some(Self {
field_bits,
batching_variables,
sumcheck_rounds,
})
}
#[must_use]
pub fn components(self) -> Vec<SecurityTerm> {
let mut terms = Vec::with_capacity(2);
if self.batching_variables != 0 {
terms.push(SecurityTerm::new(
WORD_SHIFT_BATCHING_LABEL,
error_from_numerator(self.field_bits, self.batching_variables as u128),
));
}
if self.sumcheck_rounds != 0 {
terms.push(SecurityTerm::new(
WORD_SHIFT_SUMCHECK_LABEL,
error_from_numerator(self.field_bits, 2 * self.sumcheck_rounds as u128),
));
}
terms
}
#[must_use]
pub fn combined_term(self) -> SecurityTerm {
let errors = self
.components()
.iter()
.map(|component| component.bits)
.collect::<Vec<_>>();
SecurityTerm::new(WORD_SHIFT_LABEL, ErrorBits::sum(&errors))
}
}
pub const WORD_INTEGER_MUL_LABEL: &str = "word-integer-mul";
pub const WORD_INTEGER_MUL_POINT_LABEL: &str = "word-integer-mul-point";
pub const WORD_INTEGER_MUL_PRODUCT_LABEL: &str = "word-integer-mul-product-check";
const INTEGER_MUL_DEGREE: u128 = 3;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WordIntegerMulSecurityModel {
field_bits: usize,
row_variables: usize,
bit_variables: usize,
}
impl WordIntegerMulSecurityModel {
#[must_use]
pub const fn new(
field_bits: usize,
row_variables: usize,
bit_variables: usize,
) -> Option<Self> {
if field_bits == 0 {
return None;
}
Some(Self {
field_bits,
row_variables,
bit_variables,
})
}
const fn tree_numerator(&self, depth: usize) -> u128 {
let rows = self.row_variables as u128;
let depth = depth as u128;
let layers = INTEGER_MUL_DEGREE * (rows * depth + depth * depth.saturating_sub(1) / 2);
layers + depth + INTEGER_MUL_DEGREE * (rows + depth)
}
#[must_use]
pub fn components(self) -> Vec<SecurityTerm> {
let mut terms = Vec::with_capacity(2);
if self.row_variables != 0 {
terms.push(SecurityTerm::new(
WORD_INTEGER_MUL_POINT_LABEL,
error_from_numerator(self.field_bits, self.row_variables as u128),
));
}
let numerator = self.tree_numerator(2 * self.bit_variables)
+ self.tree_numerator(self.bit_variables + 1);
terms.push(SecurityTerm::new(
WORD_INTEGER_MUL_PRODUCT_LABEL,
error_from_numerator(self.field_bits, numerator),
));
terms
}
#[must_use]
pub fn combined_term(self) -> SecurityTerm {
let errors = self
.components()
.iter()
.map(|component| component.bits)
.collect::<Vec<_>>();
SecurityTerm::new(WORD_INTEGER_MUL_LABEL, ErrorBits::sum(&errors))
}
}
pub const WORD_PROOF_LABEL: &str = "word-proof";
pub const WORD_RELATION_BATCHING_LABEL: &str = "word-relation-batching";
pub const WORD_ZEROCHECK_POINT_LABEL: &str = "word-relation-zerocheck-point";
pub const WORD_ZEROCHECK_ROUNDS_LABEL: &str = "word-relation-zerocheck-rounds";
#[derive(Clone, Debug, PartialEq)]
pub struct WordProofSecurityModel {
field_bits: usize,
relation_families: usize,
zerocheck_variables: usize,
zerocheck_degree: usize,
integer_mul: Option<WordIntegerMulSecurityModel>,
shift: WordShiftSecurityModel,
commitment: Vec<SecurityTerm>,
candidates: CandidateSet,
}
impl WordProofSecurityModel {
#[must_use]
#[allow(
clippy::too_many_arguments,
reason = "each argument is one independently derived stage of the schedule"
)]
pub fn new(
field_bits: usize,
relation_families: usize,
zerocheck_variables: usize,
zerocheck_degree: usize,
integer_mul: Option<WordIntegerMulSecurityModel>,
shift: WordShiftSecurityModel,
commitment: Vec<SecurityTerm>,
log2_candidates: f64,
) -> Option<Self> {
if field_bits == 0 || zerocheck_degree == 0 {
return None;
}
let candidates = CandidateSet::from_log2(log2_candidates)?;
Some(Self {
field_bits,
relation_families,
zerocheck_variables,
zerocheck_degree,
integer_mul,
shift,
commitment,
candidates,
})
}
#[must_use]
pub fn components(&self) -> Vec<SecurityTerm> {
let mut terms = Vec::new();
if let Some(integer_mul) = self.integer_mul {
terms.extend(integer_mul.components());
}
let separated = self.relation_families.saturating_sub(1);
if separated != 0 {
terms.push(SecurityTerm::new(
WORD_RELATION_BATCHING_LABEL,
error_from_numerator(self.field_bits, separated as u128),
));
}
if self.zerocheck_variables != 0 {
terms.push(SecurityTerm::new(
WORD_ZEROCHECK_POINT_LABEL,
error_from_numerator(self.field_bits, self.zerocheck_variables as u128),
));
terms.push(SecurityTerm::new(
WORD_ZEROCHECK_ROUNDS_LABEL,
error_from_numerator(
self.field_bits,
self.zerocheck_degree as u128 * self.zerocheck_variables as u128,
),
));
}
terms.extend(self.shift.components());
for term in &mut terms {
*term = term.over_candidates(self.candidates);
}
terms.extend(self.commitment.iter().copied());
terms
}
#[must_use]
pub fn combined_term(&self) -> SecurityTerm {
let errors = self
.components()
.iter()
.map(|component| component.bits)
.collect::<Vec<_>>();
SecurityTerm::new(WORD_PROOF_LABEL, ErrorBits::sum(&errors))
}
}
fn error_from_numerator(field_bits: usize, numerator: u128) -> ErrorBits {
let mut rounded = numerator as f64;
if rounded as u128 != numerator {
rounded = rounded.next_up();
}
ErrorBits::from_log2((field_bits as f64 - libm::log2(rounded)).max(0.0))
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
use crate::binary::BinaryPcsRegime;
use crate::multilinear::bit_ring_switch_term;
#[test]
fn word64_profile_charges_both_random_experiments() {
let model = WordShiftSecurityModel::new(128, 4, 26).unwrap();
let components = model.components();
assert_eq!(components[0].label, WORD_SHIFT_BATCHING_LABEL);
assert_eq!(components[0].bits.bits(), 126.0);
assert_eq!(components[1].label, WORD_SHIFT_SUMCHECK_LABEL);
assert_eq!(components[1].bits.bits(), 128.0 - libm::log2(52.0));
let expected = ErrorBits::sum(&[components[0].bits, components[1].bits]);
assert_eq!(model.combined_term().bits, expected);
}
#[test]
fn the_complete_proof_charges_every_stage_once() {
let shift = WordShiftSecurityModel::new(128, 4, 26).unwrap();
let pcs = BinaryPcsRegime::new(128, 6, 2, 1, 40, 0).unwrap();
let commitment = vec![bit_ring_switch_term(1, 7, 6, 128), pcs.opening_term(1)];
let model = WordProofSecurityModel::new(128, 2, 9, 3, None, shift, commitment.clone(), 0.0)
.unwrap();
let components = model.components();
assert_eq!(components[0].label, WORD_RELATION_BATCHING_LABEL);
assert_eq!(components[0].bits.bits(), 128.0);
assert_eq!(components[1].label, WORD_ZEROCHECK_POINT_LABEL);
assert_eq!(components[1].bits.bits(), 128.0 - libm::log2(9.0));
assert_eq!(components[2].label, WORD_ZEROCHECK_ROUNDS_LABEL);
assert_eq!(components[2].bits.bits(), 128.0 - libm::log2(27.0));
assert_eq!(components[3].label, WORD_SHIFT_BATCHING_LABEL);
assert_eq!(components[3].bits.bits(), 126.0);
assert_eq!(components[4].label, WORD_SHIFT_SUMCHECK_LABEL);
assert_eq!(components[4].bits.bits(), 128.0 - libm::log2(52.0));
assert_eq!(components[5..], commitment);
assert_eq!(components.len(), 7);
let errors = components
.iter()
.map(|component| component.bits)
.collect::<Vec<_>>();
assert_eq!(model.combined_term().bits, ErrorBits::sum(&errors));
}
#[test]
fn a_proof_model_rejects_a_shape_it_cannot_charge_honestly() {
let shift = WordShiftSecurityModel::new(128, 4, 26).unwrap();
assert!(WordProofSecurityModel::new(0, 2, 9, 3, None, shift, Vec::new(), 0.0).is_none());
assert!(WordProofSecurityModel::new(128, 2, 9, 0, None, shift, Vec::new(), 0.0).is_none());
for count in [f64::NAN, f64::INFINITY, -1.0] {
assert!(
WordProofSecurityModel::new(128, 2, 9, 3, None, shift, Vec::new(), count).is_none()
);
}
}
#[test]
fn candidates_left_open_are_charged_to_every_draw_that_precedes_them() {
let shift = WordShiftSecurityModel::new(128, 4, 26).unwrap();
let pcs = BinaryPcsRegime::new(128, 6, 2, 1, 40, 0).unwrap();
let commitment = vec![bit_ring_switch_term(1, 7, 6, 128), pcs.opening_term(1)];
let settled =
WordProofSecurityModel::new(128, 2, 9, 3, None, shift, commitment.clone(), 0.0)
.unwrap();
let open = WordProofSecurityModel::new(128, 2, 9, 3, None, shift, commitment.clone(), 5.0)
.unwrap();
let before = settled.components();
let after = open.components();
for (settled, open) in before.iter().zip(&after).take(5) {
assert_eq!(open.label, settled.label);
assert_eq!(open.bits.bits(), settled.bits.bits() - 5.0);
}
assert_eq!(after[5..], commitment);
assert_eq!(before[5..], commitment);
}
#[test]
fn a_multiplication_reduction_charges_its_point_and_both_trees() {
let model = WordIntegerMulSecurityModel::new(128, 3, 6).unwrap();
let components = model.components();
assert_eq!(components[0].label, WORD_INTEGER_MUL_POINT_LABEL);
assert_eq!(components[0].bits.bits(), 128.0 - libm::log2(3.0));
assert_eq!(components[1].label, WORD_INTEGER_MUL_PRODUCT_LABEL);
assert_eq!(components[1].bits.bits(), 128.0 - libm::log2(526.0));
let single = WordIntegerMulSecurityModel::new(128, 0, 6).unwrap();
let components = single.components();
assert_eq!(components.len(), 1);
assert_eq!(components[0].label, WORD_INTEGER_MUL_PRODUCT_LABEL);
assert_eq!(WordIntegerMulSecurityModel::new(0, 3, 6), None);
}
#[test]
fn the_multiplication_terms_precede_the_vanishing_check() {
let shift = WordShiftSecurityModel::new(128, 4, 26).unwrap();
let integer_mul = WordIntegerMulSecurityModel::new(128, 3, 6).unwrap();
let model =
WordProofSecurityModel::new(128, 7, 9, 3, Some(integer_mul), shift, Vec::new(), 2.0)
.unwrap();
let components = model.components();
let labels = components.iter().map(|term| term.label).collect::<Vec<_>>();
assert_eq!(
labels,
[
WORD_INTEGER_MUL_POINT_LABEL,
WORD_INTEGER_MUL_PRODUCT_LABEL,
WORD_RELATION_BATCHING_LABEL,
WORD_ZEROCHECK_POINT_LABEL,
WORD_ZEROCHECK_ROUNDS_LABEL,
WORD_SHIFT_BATCHING_LABEL,
WORD_SHIFT_SUMCHECK_LABEL,
]
);
assert_eq!(components[2].bits.bits(), 128.0 - libm::log2(6.0) - 2.0);
let expected = integer_mul.components();
assert_eq!(components[0].bits.bits(), expected[0].bits.bits() - 2.0);
assert_eq!(components[1].bits.bits(), expected[1].bits.bits() - 2.0);
}
#[test]
fn deterministic_shape_omits_empty_terms() {
let model = WordShiftSecurityModel::new(128, 0, 0).unwrap();
assert!(model.components().is_empty());
assert_eq!(WordShiftSecurityModel::new(0, 4, 6), None);
}
}