1use alloc::vec::Vec;
4
5use crate::{CandidateSet, ErrorBits, SecurityTerm};
6
7pub const WORD_SHIFT_LABEL: &str = "word-shift-reduction";
9
10pub const WORD_SHIFT_BATCHING_LABEL: &str = "word-shift-batching";
12
13pub const WORD_SHIFT_SUMCHECK_LABEL: &str = "word-shift-sumcheck";
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct WordShiftSecurityModel {
19 field_bits: usize,
21 batching_variables: usize,
23 sumcheck_rounds: usize,
25}
26
27impl WordShiftSecurityModel {
28 #[must_use]
30 pub const fn new(
31 field_bits: usize,
32 batching_variables: usize,
33 sumcheck_rounds: usize,
34 ) -> Option<Self> {
35 if field_bits == 0 {
37 return None;
38 }
39
40 Some(Self {
41 field_bits,
42 batching_variables,
43 sumcheck_rounds,
44 })
45 }
46
47 #[must_use]
49 pub fn components(self) -> Vec<SecurityTerm> {
50 let mut terms = Vec::with_capacity(2);
52 if self.batching_variables != 0 {
53 terms.push(SecurityTerm::new(
54 WORD_SHIFT_BATCHING_LABEL,
55 error_from_numerator(self.field_bits, self.batching_variables as u128),
56 ));
57 }
58
59 if self.sumcheck_rounds != 0 {
61 terms.push(SecurityTerm::new(
62 WORD_SHIFT_SUMCHECK_LABEL,
63 error_from_numerator(self.field_bits, 2 * self.sumcheck_rounds as u128),
64 ));
65 }
66
67 terms
68 }
69
70 #[must_use]
72 pub fn combined_term(self) -> SecurityTerm {
73 let errors = self
75 .components()
76 .iter()
77 .map(|component| component.bits)
78 .collect::<Vec<_>>();
79 SecurityTerm::new(WORD_SHIFT_LABEL, ErrorBits::sum(&errors))
80 }
81}
82
83pub const WORD_INTEGER_MUL_LABEL: &str = "word-integer-mul";
85
86pub const WORD_INTEGER_MUL_POINT_LABEL: &str = "word-integer-mul-point";
88
89pub const WORD_INTEGER_MUL_PRODUCT_LABEL: &str = "word-integer-mul-product-check";
91
92const INTEGER_MUL_DEGREE: u128 = 3;
94
95#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97pub struct WordIntegerMulSecurityModel {
98 field_bits: usize,
100 row_variables: usize,
102 bit_variables: usize,
104}
105
106impl WordIntegerMulSecurityModel {
107 #[must_use]
109 pub const fn new(
110 field_bits: usize,
111 row_variables: usize,
112 bit_variables: usize,
113 ) -> Option<Self> {
114 if field_bits == 0 {
116 return None;
117 }
118
119 Some(Self {
120 field_bits,
121 row_variables,
122 bit_variables,
123 })
124 }
125
126 const fn tree_numerator(&self, depth: usize) -> u128 {
135 let rows = self.row_variables as u128;
136 let depth = depth as u128;
137
138 let layers = INTEGER_MUL_DEGREE * (rows * depth + depth * depth.saturating_sub(1) / 2);
140 layers + depth + INTEGER_MUL_DEGREE * (rows + depth)
141 }
142
143 #[must_use]
145 pub fn components(self) -> Vec<SecurityTerm> {
146 let mut terms = Vec::with_capacity(2);
147
148 if self.row_variables != 0 {
150 terms.push(SecurityTerm::new(
151 WORD_INTEGER_MUL_POINT_LABEL,
152 error_from_numerator(self.field_bits, self.row_variables as u128),
153 ));
154 }
155
156 let numerator = self.tree_numerator(2 * self.bit_variables)
158 + self.tree_numerator(self.bit_variables + 1);
159 terms.push(SecurityTerm::new(
160 WORD_INTEGER_MUL_PRODUCT_LABEL,
161 error_from_numerator(self.field_bits, numerator),
162 ));
163
164 terms
165 }
166
167 #[must_use]
169 pub fn combined_term(self) -> SecurityTerm {
170 let errors = self
172 .components()
173 .iter()
174 .map(|component| component.bits)
175 .collect::<Vec<_>>();
176 SecurityTerm::new(WORD_INTEGER_MUL_LABEL, ErrorBits::sum(&errors))
177 }
178}
179
180pub const WORD_PROOF_LABEL: &str = "word-proof";
182
183pub const WORD_RELATION_BATCHING_LABEL: &str = "word-relation-batching";
185
186pub const WORD_ZEROCHECK_POINT_LABEL: &str = "word-relation-zerocheck-point";
188
189pub const WORD_ZEROCHECK_ROUNDS_LABEL: &str = "word-relation-zerocheck-rounds";
191
192#[derive(Clone, Debug, PartialEq)]
194pub struct WordProofSecurityModel {
195 field_bits: usize,
197 relation_families: usize,
199 zerocheck_variables: usize,
201 zerocheck_degree: usize,
203 integer_mul: Option<WordIntegerMulSecurityModel>,
205 shift: WordShiftSecurityModel,
207 commitment: Vec<SecurityTerm>,
209 candidates: CandidateSet,
215}
216
217impl WordProofSecurityModel {
218 #[must_use]
230 #[allow(
231 clippy::too_many_arguments,
232 reason = "each argument is one independently derived stage of the schedule"
233 )]
234 pub fn new(
235 field_bits: usize,
236 relation_families: usize,
237 zerocheck_variables: usize,
238 zerocheck_degree: usize,
239 integer_mul: Option<WordIntegerMulSecurityModel>,
240 shift: WordShiftSecurityModel,
241 commitment: Vec<SecurityTerm>,
242 log2_candidates: f64,
243 ) -> Option<Self> {
244 if field_bits == 0 || zerocheck_degree == 0 {
246 return None;
247 }
248
249 let candidates = CandidateSet::from_log2(log2_candidates)?;
251
252 Some(Self {
253 field_bits,
254 relation_families,
255 zerocheck_variables,
256 zerocheck_degree,
257 integer_mul,
258 shift,
259 commitment,
260 candidates,
261 })
262 }
263
264 #[must_use]
266 pub fn components(&self) -> Vec<SecurityTerm> {
267 let mut terms = Vec::new();
268
269 if let Some(integer_mul) = self.integer_mul {
271 terms.extend(integer_mul.components());
272 }
273
274 let separated = self.relation_families.saturating_sub(1);
276 if separated != 0 {
277 terms.push(SecurityTerm::new(
278 WORD_RELATION_BATCHING_LABEL,
279 error_from_numerator(self.field_bits, separated as u128),
280 ));
281 }
282
283 if self.zerocheck_variables != 0 {
285 terms.push(SecurityTerm::new(
286 WORD_ZEROCHECK_POINT_LABEL,
287 error_from_numerator(self.field_bits, self.zerocheck_variables as u128),
288 ));
289 terms.push(SecurityTerm::new(
290 WORD_ZEROCHECK_ROUNDS_LABEL,
291 error_from_numerator(
292 self.field_bits,
293 self.zerocheck_degree as u128 * self.zerocheck_variables as u128,
294 ),
295 ));
296 }
297
298 terms.extend(self.shift.components());
300
301 for term in &mut terms {
305 *term = term.over_candidates(self.candidates);
306 }
307
308 terms.extend(self.commitment.iter().copied());
314
315 terms
316 }
317
318 #[must_use]
320 pub fn combined_term(&self) -> SecurityTerm {
321 let errors = self
323 .components()
324 .iter()
325 .map(|component| component.bits)
326 .collect::<Vec<_>>();
327 SecurityTerm::new(WORD_PROOF_LABEL, ErrorBits::sum(&errors))
328 }
329}
330
331fn error_from_numerator(field_bits: usize, numerator: u128) -> ErrorBits {
333 let mut rounded = numerator as f64;
335 if rounded as u128 != numerator {
336 rounded = rounded.next_up();
337 }
338 ErrorBits::from_log2((field_bits as f64 - libm::log2(rounded)).max(0.0))
339}
340
341#[cfg(test)]
342mod tests {
343 use alloc::vec;
344
345 use super::*;
346 use crate::binary::BinaryPcsRegime;
347 use crate::multilinear::bit_ring_switch_term;
348
349 #[test]
350 fn word64_profile_charges_both_random_experiments() {
351 let model = WordShiftSecurityModel::new(128, 4, 26).unwrap();
359 let components = model.components();
360
361 assert_eq!(components[0].label, WORD_SHIFT_BATCHING_LABEL);
363 assert_eq!(components[0].bits.bits(), 126.0);
364
365 assert_eq!(components[1].label, WORD_SHIFT_SUMCHECK_LABEL);
367 assert_eq!(components[1].bits.bits(), 128.0 - libm::log2(52.0));
368
369 let expected = ErrorBits::sum(&[components[0].bits, components[1].bits]);
371 assert_eq!(model.combined_term().bits, expected);
372 }
373
374 #[test]
375 fn the_complete_proof_charges_every_stage_once() {
376 let shift = WordShiftSecurityModel::new(128, 4, 26).unwrap();
383 let pcs = BinaryPcsRegime::new(128, 6, 2, 1, 40, 0).unwrap();
384 let commitment = vec![bit_ring_switch_term(1, 7, 6, 128), pcs.opening_term(1)];
385 let model = WordProofSecurityModel::new(128, 2, 9, 3, None, shift, commitment.clone(), 0.0)
386 .unwrap();
387 let components = model.components();
388
389 assert_eq!(components[0].label, WORD_RELATION_BATCHING_LABEL);
391 assert_eq!(components[0].bits.bits(), 128.0);
392
393 assert_eq!(components[1].label, WORD_ZEROCHECK_POINT_LABEL);
395 assert_eq!(components[1].bits.bits(), 128.0 - libm::log2(9.0));
396
397 assert_eq!(components[2].label, WORD_ZEROCHECK_ROUNDS_LABEL);
399 assert_eq!(components[2].bits.bits(), 128.0 - libm::log2(27.0));
400
401 assert_eq!(components[3].label, WORD_SHIFT_BATCHING_LABEL);
403 assert_eq!(components[3].bits.bits(), 126.0);
404
405 assert_eq!(components[4].label, WORD_SHIFT_SUMCHECK_LABEL);
407 assert_eq!(components[4].bits.bits(), 128.0 - libm::log2(52.0));
408
409 assert_eq!(components[5..], commitment);
411 assert_eq!(components.len(), 7);
412
413 let errors = components
415 .iter()
416 .map(|component| component.bits)
417 .collect::<Vec<_>>();
418 assert_eq!(model.combined_term().bits, ErrorBits::sum(&errors));
419 }
420
421 #[test]
422 fn a_proof_model_rejects_a_shape_it_cannot_charge_honestly() {
423 let shift = WordShiftSecurityModel::new(128, 4, 26).unwrap();
424
425 assert!(WordProofSecurityModel::new(0, 2, 9, 3, None, shift, Vec::new(), 0.0).is_none());
427
428 assert!(WordProofSecurityModel::new(128, 2, 9, 0, None, shift, Vec::new(), 0.0).is_none());
430
431 for count in [f64::NAN, f64::INFINITY, -1.0] {
433 assert!(
434 WordProofSecurityModel::new(128, 2, 9, 3, None, shift, Vec::new(), count).is_none()
435 );
436 }
437 }
438
439 #[test]
440 fn candidates_left_open_are_charged_to_every_draw_that_precedes_them() {
441 let shift = WordShiftSecurityModel::new(128, 4, 26).unwrap();
443 let pcs = BinaryPcsRegime::new(128, 6, 2, 1, 40, 0).unwrap();
444 let commitment = vec![bit_ring_switch_term(1, 7, 6, 128), pcs.opening_term(1)];
445 let settled =
446 WordProofSecurityModel::new(128, 2, 9, 3, None, shift, commitment.clone(), 0.0)
447 .unwrap();
448 let open = WordProofSecurityModel::new(128, 2, 9, 3, None, shift, commitment.clone(), 5.0)
449 .unwrap();
450
451 let before = settled.components();
453 let after = open.components();
454 for (settled, open) in before.iter().zip(&after).take(5) {
455 assert_eq!(open.label, settled.label);
456 assert_eq!(open.bits.bits(), settled.bits.bits() - 5.0);
457 }
458
459 assert_eq!(after[5..], commitment);
461 assert_eq!(before[5..], commitment);
462 }
463
464 #[test]
465 fn a_multiplication_reduction_charges_its_point_and_both_trees() {
466 let model = WordIntegerMulSecurityModel::new(128, 3, 6).unwrap();
471 let components = model.components();
472
473 assert_eq!(components[0].label, WORD_INTEGER_MUL_POINT_LABEL);
475 assert_eq!(components[0].bits.bits(), 128.0 - libm::log2(3.0));
476
477 assert_eq!(components[1].label, WORD_INTEGER_MUL_PRODUCT_LABEL);
480 assert_eq!(components[1].bits.bits(), 128.0 - libm::log2(526.0));
481
482 let single = WordIntegerMulSecurityModel::new(128, 0, 6).unwrap();
484 let components = single.components();
485 assert_eq!(components.len(), 1);
486 assert_eq!(components[0].label, WORD_INTEGER_MUL_PRODUCT_LABEL);
487
488 assert_eq!(WordIntegerMulSecurityModel::new(0, 3, 6), None);
490 }
491
492 #[test]
493 fn the_multiplication_terms_precede_the_vanishing_check() {
494 let shift = WordShiftSecurityModel::new(128, 4, 26).unwrap();
496 let integer_mul = WordIntegerMulSecurityModel::new(128, 3, 6).unwrap();
497 let model =
498 WordProofSecurityModel::new(128, 7, 9, 3, Some(integer_mul), shift, Vec::new(), 2.0)
499 .unwrap();
500 let components = model.components();
501
502 let labels = components.iter().map(|term| term.label).collect::<Vec<_>>();
504 assert_eq!(
505 labels,
506 [
507 WORD_INTEGER_MUL_POINT_LABEL,
508 WORD_INTEGER_MUL_PRODUCT_LABEL,
509 WORD_RELATION_BATCHING_LABEL,
510 WORD_ZEROCHECK_POINT_LABEL,
511 WORD_ZEROCHECK_ROUNDS_LABEL,
512 WORD_SHIFT_BATCHING_LABEL,
513 WORD_SHIFT_SUMCHECK_LABEL,
514 ]
515 );
516
517 assert_eq!(components[2].bits.bits(), 128.0 - libm::log2(6.0) - 2.0);
519
520 let expected = integer_mul.components();
522 assert_eq!(components[0].bits.bits(), expected[0].bits.bits() - 2.0);
523 assert_eq!(components[1].bits.bits(), expected[1].bits.bits() - 2.0);
524 }
525
526 #[test]
527 fn deterministic_shape_omits_empty_terms() {
528 let model = WordShiftSecurityModel::new(128, 0, 0).unwrap();
530 assert!(model.components().is_empty());
531
532 assert_eq!(WordShiftSecurityModel::new(0, 4, 6), None);
534 }
535}