1use alloc::collections::btree_map::Entry;
9use alloc::collections::{BTreeMap, BTreeSet};
10use alloc::string::String;
11use alloc::vec;
12use alloc::vec::Vec;
13use hekate_core::errors;
14use hekate_core::trace::ColumnType;
15use hekate_math::{Block128, Flat, HardwareField, TowerField};
16
17use crate::permutation::{BusKind, PermutationCheckSpec, Side, Source};
18use crate::{FixedColumn, FixedShape, eq_index, residue_sums, validate_shape};
19
20pub const OMEGA: Block128 = Block128(0xf90a_c8aa_f4e3_ef21_c3a6_4356_fb12_7046);
23
24pub const OMEGA_ORDER: u64 = 67_280_421_310_721;
27
28const OMEGA_ORDER_BITS: usize = (u64::BITS - OMEGA_ORDER.leading_zeros()) as usize;
29
30pub struct RankTable<'a, F> {
33 pub specs: &'a [(String, PermutationCheckSpec)],
34 pub fixed: &'a [FixedColumn<F>],
35 pub height: TableHeight,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum TableHeight {
42 Main(usize),
43 Chiplet(Option<usize>),
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
47enum Schedule {
48 Rows(Vec<usize>),
49 Runs(Vec<Run>),
50}
51
52impl Schedule {
53 fn emits(&self) -> u64 {
54 match self {
55 Schedule::Rows(rows) => rows.len() as u64,
56 Schedule::Runs(runs) => runs.iter().map(Run::emits).sum(),
57 }
58 }
59
60 fn fits(&self, height: usize) -> bool {
61 match self {
62 Schedule::Rows(rows) => rows.last().is_none_or(|&row| row < height),
63 Schedule::Runs(runs) => runs
64 .iter()
65 .all(|run| run.origin + run.stride * run.count <= height),
66 }
67 }
68
69 fn for_each_row(&self, mut f: impl FnMut(usize)) {
70 match self {
71 Schedule::Rows(rows) => rows.iter().for_each(|&row| f(row)),
72 Schedule::Runs(runs) => {
73 for run in runs {
74 for k in 0..run.count {
75 let start = run.origin + k * run.stride;
76 for &j in &run.offsets {
77 f(start + j);
78 }
79 }
80 }
81 }
82 }
83 }
84}
85
86#[derive(Clone, Debug, PartialEq, Eq)]
87struct Run {
88 stride: usize,
89 count: usize,
90 origin: usize,
91 offsets: Vec<usize>,
92}
93
94impl Run {
95 fn emits(&self) -> u64 {
96 self.count as u64 * self.offsets.len() as u64
97 }
98
99 fn evaluate<F>(&self, r: &[Flat<F>], base: u64, powers: &OmegaPowers<F>) -> Flat<F>
100 where
101 F: TowerField + HardwareField,
102 {
103 let zero = Flat::from_raw(F::ZERO);
104 let calls = self.offsets.len();
105
106 if calls == 0 || self.count == 0 {
107 return zero;
108 }
109
110 let stride = self.stride;
111 let call_step = powers.pow(calls as u64);
112
113 let mut steps = Vec::with_capacity(r.len());
114 let mut step = call_step;
115
116 for _ in 0..r.len() {
117 steps.push(step);
118
119 step *= step;
120 }
121
122 let end = self.origin + stride * self.count;
123
124 let upper = residue_sums(r, end, stride, Some(steps.as_slice()));
125 let lower = residue_sums(r, self.origin, stride, Some(steps.as_slice()));
126
127 let (q_origin, r_origin) = (self.origin / stride, self.origin % stride);
128
129 let order = OMEGA_ORDER as u128;
130
131 let mut acc = zero;
132 for (i, &j) in self.offsets.iter().enumerate() {
133 let wrap = u128::from(j + r_origin >= stride);
134 let residue = (j + r_origin) % stride;
135
136 let shift = calls as u128 * (q_origin as u128 + wrap) % order;
137 let exponent = (base as u128 + i as u128 + order - shift) % order;
138
139 acc += (upper[residue] + lower[residue]) * powers.pow(exponent as u64);
140 }
141
142 acc
143 }
144}
145
146#[derive(Clone, Debug, PartialEq, Eq)]
147enum Selector {
148 Fixed(Schedule),
149 Tiled { period: usize, offsets: Vec<usize> },
150}
151
152impl Selector {
153 fn parse<F: TowerField>(shape: Option<&FixedShape<F>>) -> errors::Result<Self> {
154 if let Some(shape) = shape {
155 validate_shape(shape, ColumnType::B128, None)?;
156 }
157
158 match shape {
159 None => Ok(Selector::Tiled {
160 period: 1,
161 offsets: vec![0],
162 }),
163 Some(FixedShape::Periodic { period, values }) => Ok(Selector::Tiled {
164 period: *period,
165 offsets: indicator_offsets(values)?,
166 }),
167 Some(FixedShape::Cadence {
168 stride,
169 count,
170 origin,
171 values,
172 }) => Ok(Selector::Fixed(Schedule::Runs(vec![Run {
173 stride: *stride,
174 count: *count,
175 origin: *origin,
176 offsets: indicator_offsets(values)?,
177 }]))),
178 Some(FixedShape::Segments(segments)) => {
179 let mut runs = Vec::with_capacity(segments.len());
180 for seg in segments {
181 runs.push(Run {
182 stride: seg.stride,
183 count: seg.count,
184 origin: seg.origin,
185 offsets: indicator_offsets(&seg.values)?,
186 });
187 }
188
189 Ok(Selector::Fixed(Schedule::Runs(runs)))
190 }
191 Some(FixedShape::Sparse(entries)) => {
192 let mut rows = Vec::with_capacity(entries.len());
193 for &(row, value) in entries {
194 if indicator(value)? {
195 rows.push(row);
196 }
197 }
198
199 rows.sort_unstable();
200
201 Ok(Selector::Fixed(Schedule::Rows(rows)))
202 }
203 Some(
204 FixedShape::Dense(_)
205 | FixedShape::LastRow
206 | FixedShape::FirstRow
207 | FixedShape::Custom(_),
208 ) => Err(errors::Error::Protocol {
209 protocol: "logup_bus",
210 message: "ordered bus selector must be None or pinned to Cadence, \
211 Segments, Periodic or Sparse",
212 }),
213 }
214 }
215
216 fn emits(&self, num_vars: Option<usize>) -> errors::Result<Option<u64>> {
217 match (self, num_vars) {
218 (_, Some(num_vars)) => self.resolve(num_vars).map(|s| Some(s.emits())),
219 (Selector::Fixed(schedule), None) => Ok(Some(schedule.emits())),
220 (Selector::Tiled { .. }, None) => Ok(None),
221 }
222 }
223
224 fn resolve(&self, num_vars: usize) -> errors::Result<Schedule> {
225 let height = table_height(num_vars)?;
226
227 match self {
228 Selector::Fixed(schedule) if schedule.fits(height) => Ok(schedule.clone()),
229 Selector::Tiled { period, offsets } if *period <= height => {
230 Ok(Schedule::Runs(vec![Run {
231 stride: *period,
232 count: height / *period,
233 origin: 0,
234 offsets: offsets.clone(),
235 }]))
236 }
237 Selector::Fixed(_) | Selector::Tiled { .. } => Err(errors::Error::Protocol {
238 protocol: "logup_bus",
239 message: "ordered bus selector reaches past the table height",
240 }),
241 }
242 }
243}
244
245struct Ordered<'a> {
246 table: usize,
247 spec: usize,
248 bus_id: &'a str,
249 side: Side,
250 selector: Selector,
251 num_vars: Option<usize>,
252}
253
254struct OmegaPowers<F: HardwareField> {
255 squares: [Flat<F>; OMEGA_ORDER_BITS],
256}
257
258impl<F: TowerField + HardwareField> OmegaPowers<F> {
259 fn new() -> errors::Result<Self> {
260 if F::BITS != 128 {
261 return Err(errors::Error::Protocol {
262 protocol: "emit_rank",
263 message: "ordered buses need GF(2^128); omega has order p nowhere below",
264 });
265 }
266
267 let mut squares = [Flat::from_raw(F::ZERO); OMEGA_ORDER_BITS];
268 let mut power = F::from(OMEGA.0).to_hardware();
269
270 for slot in squares.iter_mut() {
271 *slot = power;
272 power *= power;
273 }
274
275 Ok(Self { squares })
276 }
277
278 fn omega(&self) -> Flat<F> {
279 self.squares[0]
280 }
281
282 fn pow(&self, exponent: u64) -> Flat<F> {
283 let exponent = exponent % OMEGA_ORDER;
284
285 let mut acc = Flat::from_raw(F::ONE);
286 for (bit, &square) in self.squares.iter().enumerate() {
287 if (exponent >> bit) & 1 == 1 {
288 acc *= square;
289 }
290 }
291
292 acc
293 }
294}
295
296#[derive(Clone, Debug, PartialEq, Eq)]
299pub struct RankClock {
300 base: u64,
301 num_vars: usize,
302 schedule: Schedule,
303}
304
305impl RankClock {
306 pub fn for_spec(clocks: &[Option<Self>], spec_idx: usize) -> errors::Result<&Self> {
309 clocks
310 .get(spec_idx)
311 .and_then(Option::as_ref)
312 .ok_or(errors::Error::Protocol {
313 protocol: "emit_rank",
314 message: "ordered bus spec has no rank clock",
315 })
316 }
317
318 pub fn evaluate<F>(&self, r: &[Flat<F>]) -> errors::Result<Flat<F>>
321 where
322 F: TowerField + HardwareField,
323 {
324 if r.len() != self.num_vars {
325 return Err(errors::Error::Protocol {
326 protocol: "emit_rank",
327 message: "evaluation point length differs from the table's num_vars",
328 });
329 }
330
331 let powers = OmegaPowers::<F>::new()?;
332
333 let mut acc = Flat::from_raw(F::ZERO);
334
335 match &self.schedule {
336 Schedule::Rows(rows) => {
337 let omega = powers.omega();
338
339 let mut label = powers.pow(self.base);
340 for &row in rows {
341 acc += eq_index(r, row) * label;
342 label *= omega;
343 }
344 }
345 Schedule::Runs(runs) => {
346 let mut base = self.base;
347 for run in runs {
348 acc += run.evaluate(r, base, &powers);
349 base += run.emits();
350 }
351 }
352 }
353
354 Ok(acc)
355 }
356
357 pub fn accumulate<F>(&self, out: &mut [Flat<F>], coeff: Flat<F>) -> errors::Result<()>
360 where
361 F: TowerField + HardwareField,
362 {
363 if out.len() != 1usize << self.num_vars {
364 return Err(errors::Error::Protocol {
365 protocol: "emit_rank",
366 message: "clock buffer length differs from the table height",
367 });
368 }
369
370 let powers = OmegaPowers::<F>::new()?;
371 let omega = powers.omega();
372
373 let mut term = coeff * powers.pow(self.base);
374
375 self.schedule.for_each_row(|row| {
376 out[row] += term;
377 term *= omega;
378 });
379
380 Ok(())
381 }
382}
383
384pub fn rank_clocks<F: TowerField>(
388 tables: &[RankTable<'_, F>],
389) -> errors::Result<Vec<Vec<Option<RankClock>>>> {
390 let mut totals: BTreeMap<(&str, Side), u64> = BTreeMap::new();
391 let mut clocks: Vec<Vec<Option<RankClock>>> =
392 tables.iter().map(|t| vec![None; t.specs.len()]).collect();
393
394 for ep in ordered_endpoints(tables)? {
395 let num_vars = ep.num_vars.ok_or(errors::Error::Protocol {
396 protocol: "emit_rank",
397 message: "rank clocks need the height of every table",
398 })?;
399
400 let schedule = ep.selector.resolve(num_vars)?;
401
402 let total = totals.entry((ep.bus_id, ep.side)).or_insert(0);
403 let base = *total;
404
405 *total = side_total(base, schedule.emits())?;
406
407 clocks[ep.table][ep.spec] = Some(RankClock {
408 base,
409 num_vars,
410 schedule,
411 });
412 }
413
414 Ok(clocks)
415}
416
417pub fn validate_ordered_buses<F: TowerField>(tables: &[RankTable<'_, F>]) -> errors::Result<()> {
420 emit_totals(tables).map(|_| ())
421}
422
423pub fn validate_ordered_emits<F: TowerField>(tables: &[RankTable<'_, F>]) -> errors::Result<()> {
426 let totals = emit_totals(tables)?;
427
428 for (&(bus_id, side), &total) in &totals {
429 let responses = totals.get(&(bus_id, Side::Response));
430
431 if let (Side::Request, Some(requests), Some(&Some(responses))) = (side, total, responses)
432 && requests != responses
433 {
434 return Err(errors::Error::Protocol {
435 protocol: "logup_bus",
436 message: "ordered bus sides emit different counts; the bus cannot balance",
437 });
438 }
439 }
440
441 Ok(())
442}
443
444fn ordered_endpoints<'a, F: TowerField>(
445 tables: &[RankTable<'a, F>],
446) -> errors::Result<Vec<Ordered<'a>>> {
447 let ordered = ordered_bus_ids(tables);
448
449 if !ordered.is_empty() && F::BITS != 128 {
450 return Err(errors::Error::Protocol {
451 protocol: "logup_bus",
452 message: "ordered buses need GF(2^128); omega has order p nowhere below",
453 });
454 }
455
456 let mut layouts: BTreeMap<&str, (usize, usize)> = BTreeMap::new();
457 let mut tiled_chiplet_sides: BTreeSet<(&str, Side)> = BTreeSet::new();
458
459 let mut out = Vec::new();
460
461 for (table, t) in tables.iter().enumerate() {
462 let num_vars = match t.height {
463 TableHeight::Main(num_vars) => Some(num_vars),
464 TableHeight::Chiplet(num_vars) => num_vars,
465 };
466
467 for (spec_idx, (bus_id, spec)) in t.specs.iter().enumerate() {
468 let bus_id = bus_id.as_str();
469
470 if !ordered.contains(bus_id) {
471 continue;
472 }
473
474 let (side, layout) = rank_slot(spec)?;
475
476 match layouts.entry(bus_id) {
477 Entry::Vacant(slot) => {
478 slot.insert(layout);
479 }
480 Entry::Occupied(slot) if *slot.get() != layout => {
481 return Err(errors::Error::Protocol {
482 protocol: "logup_bus",
483 message: "ordered bus endpoints disagree on the EmitRank slot \
484 position or on the key width",
485 });
486 }
487 Entry::Occupied(_) => {}
488 }
489
490 let selector = endpoint_selector(spec, t.fixed)?;
491
492 if tiled_chiplet_sides.contains(&(bus_id, side)) {
493 return Err(errors::Error::Protocol {
494 protocol: "logup_bus",
495 message: "ordered bus chiplet endpoint with a None or Periodic selector \
496 must be last on its side; the prover picks the chiplet's height",
497 });
498 }
499
500 if matches!(selector, Selector::Tiled { .. })
501 && matches!(t.height, TableHeight::Chiplet(_))
502 {
503 tiled_chiplet_sides.insert((bus_id, side));
504 }
505
506 out.push(Ordered {
507 table,
508 spec: spec_idx,
509 bus_id,
510 side,
511 selector,
512 num_vars,
513 });
514 }
515 }
516
517 Ok(out)
518}
519
520fn ordered_bus_ids<'a, F>(tables: &[RankTable<'a, F>]) -> BTreeSet<&'a str> {
521 tables
522 .iter()
523 .flat_map(|t| t.specs.iter())
524 .filter(|(_, spec)| {
525 spec.sources
526 .iter()
527 .any(|(source, _)| matches!(source, Source::EmitRank(_)))
528 })
529 .map(|(bus_id, _)| bus_id.as_str())
530 .collect()
531}
532
533fn rank_slot(spec: &PermutationCheckSpec) -> errors::Result<(Side, (usize, usize))> {
534 let mut rank = None;
535 let mut width = 0usize;
536
537 for (source, _) in &spec.sources {
538 if let Source::EmitRank(side) = source
539 && rank.replace((*side, width)).is_some()
540 {
541 return Err(errors::Error::Protocol {
542 protocol: "logup_bus",
543 message: "ordered bus endpoint carries more than one EmitRank source",
544 });
545 }
546
547 width += slot_width(source);
548 }
549
550 match rank {
551 Some((side, position)) => Ok((side, (position, width))),
552 None => Err(errors::Error::Protocol {
553 protocol: "logup_bus",
554 message: "ordered bus endpoint has no EmitRank source; a witness column \
555 in the clock slot mimics any rank",
556 }),
557 }
558}
559
560fn slot_width(source: &Source) -> usize {
561 match source {
562 Source::Columns(cols) => cols.len(),
563 Source::Column(_)
564 | Source::RowIndexLeBytes(_)
565 | Source::Const(_)
566 | Source::RowIndexByte(_)
567 | Source::PhaseColumn(_)
568 | Source::EmitRank(_) => 1,
569 }
570}
571
572fn endpoint_selector<F: TowerField>(
573 spec: &PermutationCheckSpec,
574 fixed: &[FixedColumn<F>],
575) -> errors::Result<Selector> {
576 if spec.kind != BusKind::Permutation {
577 return Err(errors::Error::Protocol {
578 protocol: "logup_bus",
579 message: "ordered bus endpoint is Lookup kind; \
580 ranks order Permutation buses only",
581 });
582 }
583
584 if spec.recv_selector.is_some() {
585 return Err(errors::Error::Protocol {
586 protocol: "logup_bus",
587 message: "ordered bus endpoint has a recv_selector; \
588 a paired row has no single rank",
589 });
590 }
591
592 let shape = match spec.selector {
593 None => None,
594 Some(col) => {
595 let pin = fixed
596 .iter()
597 .find(|fc| fc.col_idx == col)
598 .ok_or(errors::Error::Protocol {
599 protocol: "logup_bus",
600 message: "ordered bus selector is a witness column; \
601 the verifier counts ranks over the pinned shape",
602 })?;
603
604 Some(&pin.shape)
605 }
606 };
607
608 Selector::parse(shape)
609}
610
611fn emit_totals<'a, F: TowerField>(
612 tables: &[RankTable<'a, F>],
613) -> errors::Result<BTreeMap<(&'a str, Side), Option<u64>>> {
614 let mut totals: BTreeMap<(&str, Side), Option<u64>> = BTreeMap::new();
615 for ep in ordered_endpoints(tables)? {
616 let emits = ep.selector.emits(ep.num_vars)?;
617 let total = totals.entry((ep.bus_id, ep.side)).or_insert(Some(0));
618
619 *total = match (*total, emits) {
620 (Some(sum), Some(emits)) => Some(side_total(sum, emits)?),
621 _ => None,
622 };
623 }
624
625 Ok(totals)
626}
627
628fn side_total(sum: u64, emits: u64) -> errors::Result<u64> {
629 sum.checked_add(emits)
630 .filter(|&total| total < OMEGA_ORDER)
631 .ok_or(errors::Error::Protocol {
632 protocol: "logup_bus",
633 message: "an ordered bus side emits p = 67280421310721 times or more; \
634 rank labels would repeat",
635 })
636}
637
638fn indicator_offsets<F: TowerField>(values: &[F]) -> errors::Result<Vec<usize>> {
639 let mut offsets = Vec::new();
640 for (offset, &value) in values.iter().enumerate() {
641 if indicator(value)? {
642 offsets.push(offset);
643 }
644 }
645
646 Ok(offsets)
647}
648
649fn indicator<F: TowerField>(value: F) -> errors::Result<bool> {
650 match value {
651 v if v == F::ONE => Ok(true),
652 v if v == F::ZERO => Ok(false),
653 _ => Err(errors::Error::Protocol {
654 protocol: "logup_bus",
655 message: "ordered bus selector takes a value outside {0, 1}; \
656 a rank counts rows where it is one",
657 }),
658 }
659}
660
661fn table_height(num_vars: usize) -> errors::Result<usize> {
662 u32::try_from(num_vars)
663 .ok()
664 .and_then(|n| 1usize.checked_shl(n))
665 .ok_or(errors::Error::Protocol {
666 protocol: "emit_rank",
667 message: "table num_vars exceeds the address width",
668 })
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674 use crate::CadenceSegment;
675 use crate::permutation::{ChallengeLabel, EMIT_RANK_LABEL};
676
677 use alloc::string::ToString;
678 use hekate_math::Block64;
679
680 type F = Block128;
681
682 const BUS: &str = "svc";
683
684 struct Rng(u64);
685
686 impl Rng {
687 fn next(&mut self) -> u64 {
688 self.0 ^= self.0 << 13;
689 self.0 ^= self.0 >> 7;
690 self.0 ^= self.0 << 17;
691
692 self.0
693 }
694
695 fn below(&mut self, bound: usize) -> usize {
696 (self.next() % bound as u64) as usize
697 }
698
699 fn bits(&mut self, len: usize) -> Vec<F> {
700 (0..len)
701 .map(|_| match self.next() & 1 {
702 1 => F::ONE,
703 _ => F::ZERO,
704 })
705 .collect()
706 }
707
708 fn point(&mut self, len: usize) -> Vec<Flat<F>> {
709 (0..len)
710 .map(|_| F::from(((self.next() as u128) << 64) | self.next() as u128).to_hardware())
711 .collect()
712 }
713 }
714
715 fn assert_matches_brute_force(shape: Option<&FixedShape<F>>, num_vars: usize, rng: &mut Rng) {
716 let base = rng.next() % (1 << 40);
717
718 let clock = RankClock {
719 base,
720 num_vars,
721 schedule: Selector::parse(shape).unwrap().resolve(num_vars).unwrap(),
722 };
723
724 let expected = brute_column(shape, num_vars, base);
725
726 let coeff = rng.point(1)[0];
727 let mut column = rng.point(1 << num_vars);
728
729 let accumulated: Vec<Flat<F>> = column
730 .iter()
731 .zip(&expected)
732 .map(|(&prior, &label)| prior + coeff * label)
733 .collect();
734
735 clock.accumulate(&mut column, coeff).unwrap();
736
737 assert_eq!(column, accumulated);
738
739 for _ in 0..3 {
740 let r = rng.point(num_vars);
741
742 let mut mle = Flat::from_raw(F::ZERO);
743 for (row, &value) in expected.iter().enumerate() {
744 mle += eq_index(&r, row) * value;
745 }
746
747 assert_eq!(clock.evaluate(&r).unwrap(), mle);
748 }
749 }
750
751 fn brute_column(shape: Option<&FixedShape<F>>, num_vars: usize, base: u64) -> Vec<Flat<F>> {
752 let one = Flat::from_raw(F::ONE);
753 let omega = OMEGA.to_hardware();
754
755 let mut label = pow(omega, base as u128);
756
757 (0..1usize << num_vars)
758 .map(|row| {
759 let live = match shape {
760 None => true,
761 Some(shape) => shape.value_at_row(row, num_vars) == one,
762 };
763
764 match live {
765 true => {
766 let value = label;
767 label *= omega;
768
769 value
770 }
771 false => Flat::from_raw(F::ZERO),
772 }
773 })
774 .collect()
775 }
776
777 fn pow(base: Flat<F>, mut exponent: u128) -> Flat<F> {
778 let mut square = base;
779 let mut acc = Flat::from_raw(F::ONE);
780
781 while exponent > 0 {
782 if exponent & 1 == 1 {
783 acc *= square;
784 }
785
786 square *= square;
787 exponent >>= 1;
788 }
789
790 acc
791 }
792
793 fn assert_rejected_against_host(
794 server: &[(String, PermutationCheckSpec)],
795 pins: &[FixedColumn<F>],
796 ) {
797 let host = vec![endpoint(ordered(Side::Request), Some(1))];
798 let host_pins = calls(2);
799
800 assert_rejected(&[table(&host, &host_pins), table(server, pins)]);
801 }
802
803 fn assert_rejected(tables: &[RankTable<'_, F>]) {
804 let unsized_: Vec<RankTable<'_, F>> = tables
805 .iter()
806 .map(|t| unsized_table(t.specs, t.fixed))
807 .collect();
808
809 assert!(rank_clocks(tables).is_err());
810 assert!(validate_ordered_buses(&unsized_).is_err());
811 }
812
813 fn main_table<'a>(
814 specs: &'a [(String, PermutationCheckSpec)],
815 fixed: &'a [FixedColumn<F>],
816 ) -> RankTable<'a, F> {
817 RankTable {
818 specs,
819 fixed,
820 height: TableHeight::Main(4),
821 }
822 }
823
824 fn table<'a>(
825 specs: &'a [(String, PermutationCheckSpec)],
826 fixed: &'a [FixedColumn<F>],
827 ) -> RankTable<'a, F> {
828 RankTable {
829 specs,
830 fixed,
831 height: TableHeight::Chiplet(Some(4)),
832 }
833 }
834
835 fn unsized_table<'a>(
836 specs: &'a [(String, PermutationCheckSpec)],
837 fixed: &'a [FixedColumn<F>],
838 ) -> RankTable<'a, F> {
839 RankTable {
840 specs,
841 fixed,
842 height: TableHeight::Chiplet(None),
843 }
844 }
845
846 fn endpoint(
847 sources: Vec<(Source, ChallengeLabel)>,
848 selector: Option<usize>,
849 ) -> (String, PermutationCheckSpec) {
850 (
851 BUS.to_string(),
852 PermutationCheckSpec::new(sources, selector),
853 )
854 }
855
856 fn ordered(side: Side) -> Vec<(Source, ChallengeLabel)> {
857 vec![
858 (Source::Column(0), b"k_v"),
859 (Source::EmitRank(side), EMIT_RANK_LABEL),
860 ]
861 }
862
863 fn calls(count: usize) -> Vec<FixedColumn<F>> {
864 vec![FixedColumn {
865 col_idx: 1,
866 shape: FixedShape::Cadence {
867 stride: 4,
868 count,
869 origin: 0,
870 values: vec![F::ONE, F::ZERO, F::ZERO, F::ONE],
871 },
872 }]
873 }
874
875 #[test]
876 fn omega_has_prime_order() {
877 let p = OMEGA_ORDER;
878
879 assert_eq!(((1u128 << 64) | 1) % p as u128, 0);
880 assert!(
881 (3u64..)
882 .step_by(2)
883 .take_while(|d| d * d <= p)
884 .all(|d| !p.is_multiple_of(d))
885 );
886
887 let omega = OMEGA.to_hardware();
888 let one = Flat::from_raw(F::ONE);
889 let generator = Block128(1 << 64).to_hardware();
890
891 assert_ne!(omega, one);
892 assert_eq!(pow(omega, p as u128), one);
893 assert_eq!(pow(generator, u128::MAX / p as u128), omega);
894 }
895
896 #[test]
897 fn every_row_clock_matches_brute_force() {
898 let mut rng = Rng(0x9E37_79B9_7F4A_7C15);
899 for num_vars in 0..=10 {
900 assert_matches_brute_force(None, num_vars, &mut rng);
901 }
902 }
903
904 #[test]
905 fn periodic_clock_matches_brute_force() {
906 let mut rng = Rng(0xD1B5_4A32_D192_ED03);
907 for num_vars in 0..=10 {
908 for log_period in 0..=num_vars {
909 let shape = FixedShape::Periodic {
910 period: 1 << log_period,
911 values: rng.bits(1 << log_period),
912 };
913
914 assert_matches_brute_force(Some(&shape), num_vars, &mut rng);
915 }
916 }
917 }
918
919 #[test]
920 fn cadence_clock_matches_brute_force() {
921 let mut rng = Rng(0x2545_F491_4F6C_DD1D);
922
923 for num_vars in [6, 8, 10] {
924 let height = 1usize << num_vars;
925
926 for stride in 1..=64 {
927 let count = rng.below(height / stride + 1);
928 let origin = rng.below(height - stride * count + 1);
929
930 let shape = FixedShape::Cadence {
931 stride,
932 count,
933 origin,
934 values: rng.bits(stride),
935 };
936
937 assert_matches_brute_force(Some(&shape), num_vars, &mut rng);
938 }
939 }
940 }
941
942 #[test]
943 fn segments_clock_matches_brute_force() {
944 let mut rng = Rng(0x6A09_E667_F3BC_C909);
945 let num_vars = 10;
946 let height = 1usize << num_vars;
947
948 for _ in 0..64 {
949 let mut segments = Vec::new();
950 let mut cursor = rng.below(64);
951
952 for _ in 0..1 + rng.below(4) {
953 let stride = 1 + rng.below(16);
954 let count = 1 + rng.below(8);
955
956 if cursor + stride * count > height {
957 break;
958 }
959
960 segments.push(CadenceSegment {
961 stride,
962 count,
963 origin: cursor,
964 values: rng.bits(stride),
965 });
966
967 cursor += stride * count + rng.below(32);
968 }
969
970 if segments.is_empty() {
971 continue;
972 }
973
974 let shape = FixedShape::Segments(segments);
975
976 assert_matches_brute_force(Some(&shape), num_vars, &mut rng);
977 }
978 }
979
980 #[test]
981 fn sparse_clock_matches_brute_force() {
982 let mut rng = Rng(0xBB67_AE85_84CA_A73B);
983 let num_vars = 10;
984
985 for _ in 0..32 {
986 let mut entries = Vec::new();
987 for row in 0..1usize << num_vars {
988 if rng.next().is_multiple_of(8) {
989 entries.push((row, rng.bits(1)[0]));
990 }
991 }
992
993 entries.reverse();
994
995 let shape = FixedShape::Sparse(entries);
996
997 assert_matches_brute_force(Some(&shape), num_vars, &mut rng);
998 }
999 }
1000
1001 #[test]
1002 fn requester_tables_tile_one_responder() {
1003 let host = vec![endpoint(ordered(Side::Request), Some(1))];
1004 let server = vec![endpoint(ordered(Side::Response), Some(1))];
1005
1006 let (pins_a, pins_b, pins_server) = (calls(2), calls(1), calls(3));
1007
1008 let tables = [
1009 table(&host, &pins_a),
1010 table(&host, &pins_b),
1011 table(&server, &pins_server),
1012 ];
1013
1014 let clocks = rank_clocks(&tables).unwrap();
1015 let base = |t: usize| clocks[t][0].as_ref().unwrap().base;
1016
1017 assert_eq!((base(0), base(1), base(2)), (0, 4, 0));
1018
1019 validate_ordered_emits(&tables).unwrap();
1020
1021 let short = calls(2);
1022 let unbalanced = [
1023 table(&host, &pins_a),
1024 table(&host, &pins_b),
1025 table(&server, &short),
1026 ];
1027
1028 rank_clocks(&unbalanced).unwrap();
1029
1030 assert!(validate_ordered_emits(&unbalanced).is_err());
1031 }
1032
1033 #[test]
1034 fn tiled_chiplet_endpoint_must_be_last_on_its_side() {
1035 let every_row = vec![endpoint(ordered(Side::Request), None)];
1036 let host = vec![endpoint(ordered(Side::Request), Some(1))];
1037 let server = vec![endpoint(ordered(Side::Response), Some(1))];
1038
1039 let periodic = vec![FixedColumn {
1040 col_idx: 1,
1041 shape: FixedShape::Periodic {
1042 period: 2,
1043 values: vec![F::ONE, F::ZERO],
1044 },
1045 }];
1046
1047 let (pins, server_pins) = (calls(1), calls(3));
1048
1049 for tiled in [table(&every_row, &[]), table(&host, &periodic)] {
1050 assert_rejected(&[tiled, table(&host, &pins), table(&server, &server_pins)]);
1051 }
1052
1053 let last = [
1054 table(&host, &pins),
1055 table(&host, &periodic),
1056 table(&server, &server_pins),
1057 ];
1058
1059 rank_clocks(&last).unwrap();
1060 validate_ordered_buses(&last).unwrap();
1061
1062 let on_main = [
1063 main_table(&host, &periodic),
1064 table(&host, &pins),
1065 table(&server, &server_pins),
1066 ];
1067
1068 rank_clocks(&on_main).unwrap();
1069 }
1070
1071 #[test]
1072 fn lone_side_leaves_balance_to_its_partner() {
1073 let server = vec![endpoint(ordered(Side::Response), Some(1))];
1074 let pins = calls(3);
1075
1076 validate_ordered_emits(&[table(&server, &pins)]).unwrap();
1077 }
1078
1079 #[test]
1080 fn honest_pair_is_accepted_both_ways() {
1081 let host = vec![endpoint(ordered(Side::Request), Some(1))];
1082 let server = vec![endpoint(ordered(Side::Response), Some(1))];
1083 let pins = calls(2);
1084
1085 rank_clocks(&[table(&host, &pins), table(&server, &pins)]).unwrap();
1086 validate_ordered_buses(&[unsized_table(&host, &pins), unsized_table(&server, &pins)])
1087 .unwrap();
1088 }
1089
1090 #[test]
1091 fn witness_column_in_the_clock_slot_is_rejected() {
1092 let server = vec![endpoint(
1093 vec![
1094 (Source::Column(0), b"k_v"),
1095 (Source::Column(2), EMIT_RANK_LABEL),
1096 ],
1097 Some(1),
1098 )];
1099
1100 assert_rejected_against_host(&server, &calls(2));
1101 }
1102
1103 #[test]
1104 fn misaligned_rank_slot_is_rejected() {
1105 let server = vec![endpoint(
1106 vec![
1107 (Source::EmitRank(Side::Response), EMIT_RANK_LABEL),
1108 (Source::Column(0), b"k_v"),
1109 ],
1110 Some(1),
1111 )];
1112
1113 assert_rejected_against_host(&server, &calls(2));
1114 }
1115
1116 #[test]
1117 fn differing_key_width_is_rejected() {
1118 let mut sources = ordered(Side::Response);
1119 sources.push((Source::Const(7), b"k_dom"));
1120
1121 assert_rejected_against_host(&[endpoint(sources, Some(1))], &calls(2));
1122 }
1123
1124 #[test]
1125 fn rank_slot_is_measured_in_beta_positions() {
1126 let server = vec![endpoint(
1127 vec![
1128 (Source::Columns(vec![0, 2]), b"k_v"),
1129 (Source::EmitRank(Side::Response), EMIT_RANK_LABEL),
1130 ],
1131 Some(1),
1132 )];
1133
1134 assert_rejected_against_host(&server, &calls(2));
1135 }
1136
1137 #[test]
1138 fn second_rank_source_is_rejected() {
1139 let mut sources = ordered(Side::Response);
1140 sources.push((Source::EmitRank(Side::Response), EMIT_RANK_LABEL));
1141
1142 assert_rejected(&[table(&[endpoint(sources, Some(1))], &calls(2))]);
1143 }
1144
1145 #[test]
1146 fn dense_selector_is_rejected() {
1147 let pins = vec![FixedColumn {
1148 col_idx: 1,
1149 shape: FixedShape::Dense(vec![F::ONE; 16]),
1150 }];
1151
1152 assert_rejected_against_host(&[endpoint(ordered(Side::Response), Some(1))], &pins);
1153 }
1154
1155 #[test]
1156 fn witness_selector_is_rejected() {
1157 assert_rejected_against_host(&[endpoint(ordered(Side::Response), Some(1))], &[]);
1158 }
1159
1160 #[test]
1161 fn recv_selector_is_rejected() {
1162 let paired =
1163 PermutationCheckSpec::new_paired(ordered(Side::Response), 1, 2, BusKind::Permutation);
1164
1165 let mut pins = calls(1);
1166 pins.push(FixedColumn {
1167 col_idx: 2,
1168 shape: FixedShape::Sparse(vec![(9, F::ONE)]),
1169 });
1170
1171 assert_rejected_against_host(&[(BUS.to_string(), paired)], &pins);
1172 }
1173
1174 #[test]
1175 fn lookup_kind_is_rejected() {
1176 let lookup = PermutationCheckSpec::new_lookup(ordered(Side::Response), Some(1));
1177
1178 assert_rejected_against_host(&[(BUS.to_string(), lookup)], &calls(2));
1179 }
1180
1181 #[test]
1182 fn non_indicator_selector_is_rejected() {
1183 let pins = vec![FixedColumn {
1184 col_idx: 1,
1185 shape: FixedShape::Cadence {
1186 stride: 4,
1187 count: 2,
1188 origin: 0,
1189 values: vec![F::from(2u128), F::ZERO, F::ZERO, F::ONE],
1190 },
1191 }];
1192
1193 assert_rejected_against_host(&[endpoint(ordered(Side::Response), Some(1))], &pins);
1194 }
1195
1196 #[test]
1197 fn side_total_stays_below_p() {
1198 let host = vec![endpoint(ordered(Side::Request), None)];
1199
1200 let tall = RankTable::<F> {
1201 specs: &host,
1202 fixed: &[],
1203 height: TableHeight::Main(46),
1204 };
1205
1206 assert!(rank_clocks(&[tall]).is_err());
1207
1208 let host = vec![endpoint(ordered(Side::Request), Some(1))];
1209 let pins = vec![FixedColumn {
1210 col_idx: 1,
1211 shape: FixedShape::Cadence {
1212 stride: 1,
1213 count: OMEGA_ORDER as usize,
1214 origin: 0,
1215 values: vec![F::ONE],
1216 },
1217 }];
1218
1219 assert!(validate_ordered_buses(&[unsized_table(&host, &pins)]).is_err());
1220 }
1221
1222 #[test]
1223 fn subfields_carry_no_rank_clock() {
1224 let host = vec![endpoint(ordered(Side::Request), None)];
1225
1226 let narrow = RankTable::<Block64> {
1227 specs: &host,
1228 fixed: &[],
1229 height: TableHeight::Main(4),
1230 };
1231
1232 assert!(rank_clocks(&[narrow]).is_err());
1233 }
1234}